|
1 | | -# ---------------------------------------------------------------------------------- |
| 1 | +# ---------------------------------------------------------------------------------- |
2 | 2 | # |
3 | 3 | # Copyright Microsoft Corporation |
4 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
@@ -551,3 +551,273 @@ function Test-SimpleNewVmssSkipExtOverprovision |
551 | 551 | Clean-ResourceGroup $vmssname |
552 | 552 | } |
553 | 553 | } |
| 554 | + |
| 555 | +<# |
| 556 | +.SYNOPSIS |
| 557 | +Test New-AzVmssLifecycleHookConfig creates an in-memory lifecycle hook config object. |
| 558 | +#> |
| 559 | +function Test-VmssLifecycleHookConfig |
| 560 | +{ |
| 561 | + # Test creating a lifecycle hook config object in memory (no Azure calls needed) |
| 562 | + $hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT8H' |
| 563 | + Assert-NotNull $hook |
| 564 | + Assert-AreEqual 'UpgradeAutoOSScheduling' $hook.Type |
| 565 | + Assert-AreEqual ([System.TimeSpan]::FromHours(8)) $hook.WaitDuration |
| 566 | + # When -DefaultAction is omitted, the cmdlet leaves it null so the SDK omits the field |
| 567 | + # from the request and the service applies its own default. |
| 568 | + Assert-Null $hook.DefaultAction |
| 569 | + |
| 570 | + # With explicit default action |
| 571 | + $hook2 = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSRollingBatchStarting' -WaitDuration 'PT30M' -DefaultAction 'Approve' |
| 572 | + Assert-NotNull $hook2 |
| 573 | + Assert-AreEqual 'UpgradeAutoOSRollingBatchStarting' $hook2.Type |
| 574 | + Assert-AreEqual ([System.TimeSpan]::FromMinutes(30)) $hook2.WaitDuration |
| 575 | + Assert-AreEqual 'Approve' $hook2.DefaultAction |
| 576 | + |
| 577 | + # Without wait duration |
| 578 | + $hook3 = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' |
| 579 | + Assert-NotNull $hook3 |
| 580 | + Assert-Null $hook3.WaitDuration |
| 581 | + Assert-Null $hook3.DefaultAction |
| 582 | +} |
| 583 | + |
| 584 | +<# |
| 585 | +.SYNOPSIS |
| 586 | +Test Set-AzVmssLifecycleHooksProfile attaches hooks to a VMSS config and round-trips through the service. |
| 587 | +#> |
| 588 | +function Test-SetVmssLifecycleHooksProfile |
| 589 | +{ |
| 590 | + # Tests the Set-AzVmssLifecycleHooksProfile builder cmdlet end-to-end: |
| 591 | + # Build VMSS config -> attach hook via Set-AzVmssLifecycleHooksProfile -> deploy -> verify hook round-tripped from service. |
| 592 | + $rgname = Get-ComputeTestResourceName |
| 593 | + |
| 594 | + try |
| 595 | + { |
| 596 | + $loc = "eastus2euap" |
| 597 | + New-AzResourceGroup -Name $rgname -Location $loc -Force |
| 598 | + |
| 599 | + # NRP |
| 600 | + $subnet = New-AzVirtualNetworkSubnetConfig -Name ('subnet' + $rgname) -AddressPrefix "10.0.0.0/24" -DefaultOutboundAccess $false |
| 601 | + $vnet = New-AzVirtualNetwork -Force -Name ('vnet' + $rgname) -ResourceGroupName $rgname -Location $loc -AddressPrefix "10.0.0.0/16" -Subnet $subnet |
| 602 | + $subnetId = $vnet.Subnets[0].Id |
| 603 | + |
| 604 | + $vmssName = 'vmss' + $rgname |
| 605 | + $adminUsername = 'Foo12' |
| 606 | + $adminPassword = $PLACEHOLDER |
| 607 | + $ipCfg = New-AzVmssIPConfig -Name 'test' -SubnetId $subnetId -Primary |
| 608 | + |
| 609 | + # Build a basic VMSS config WITHOUT hooks |
| 610 | + $vmss = New-AzVmssConfig -Location $loc -SkuCapacity 1 -SkuName 'Standard_DS1_v2' -UpgradePolicyMode 'Automatic' ` |
| 611 | + | Add-AzVmssNetworkInterfaceConfiguration -Name 'test' -Primary $true -IPConfiguration $ipCfg ` |
| 612 | + | Set-AzVmssOSProfile -ComputerNamePrefix 'test' -AdminUsername $adminUsername -AdminPassword $adminPassword ` |
| 613 | + | Set-AzVmssStorageProfile -OsDiskCreateOption 'FromImage' -OsDiskCaching 'None' ` |
| 614 | + -ImageReferenceOffer 'WindowsServer' -ImageReferenceSku '2022-Datacenter' ` |
| 615 | + -ImageReferenceVersion 'latest' -ImageReferencePublisher 'MicrosoftWindowsServer' |
| 616 | + |
| 617 | + # Attach hook via the builder cmdlet under test |
| 618 | + $hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT8H' -DefaultAction 'Approve' |
| 619 | + $vmss = Set-AzVmssLifecycleHooksProfile -VirtualMachineScaleSet $vmss -LifecycleHook $hook |
| 620 | + |
| 621 | + # Pre-deploy assertion: hook is on the in-memory object |
| 622 | + Assert-NotNull $vmss.LifecycleHooksProfile |
| 623 | + Assert-AreEqual 1 $vmss.LifecycleHooksProfile.LifecycleHooks.Count |
| 624 | + |
| 625 | + # Deploy |
| 626 | + $created = New-AzVmss -ResourceGroupName $rgname -Name $vmssName -VirtualMachineScaleSet $vmss |
| 627 | + Assert-NotNull $created |
| 628 | + |
| 629 | + # Read back from service and verify the hook round-tripped |
| 630 | + $read = Get-AzVmss -ResourceGroupName $rgname -VMScaleSetName $vmssName |
| 631 | + Assert-NotNull $read.LifecycleHooksProfile |
| 632 | + Assert-AreEqual 1 $read.LifecycleHooksProfile.LifecycleHooks.Count |
| 633 | + Assert-AreEqual 'UpgradeAutoOSScheduling' $read.LifecycleHooksProfile.LifecycleHooks[0].Type |
| 634 | + Assert-AreEqual ([System.TimeSpan]::FromHours(8)) $read.LifecycleHooksProfile.LifecycleHooks[0].WaitDuration |
| 635 | + Assert-AreEqual 'Approve' $read.LifecycleHooksProfile.LifecycleHooks[0].DefaultAction |
| 636 | + } |
| 637 | + finally |
| 638 | + { |
| 639 | + Clean-ResourceGroup $rgname |
| 640 | + } |
| 641 | +} |
| 642 | + |
| 643 | +<# |
| 644 | +.SYNOPSIS |
| 645 | +Test New-AzVmssConfig with -LifecycleHooksProfile parameter round-trips through the service. |
| 646 | +#> |
| 647 | +function Test-NewVmssConfigWithLifecycleHooksProfile |
| 648 | +{ |
| 649 | + # Tests the inline -LifecycleHooksProfile parameter on New-AzVmssConfig end-to-end: |
| 650 | + # Build profile -> New-AzVmssConfig -LifecycleHooksProfile -> deploy -> verify hook round-tripped from service. |
| 651 | + $rgname = Get-ComputeTestResourceName |
| 652 | + |
| 653 | + try |
| 654 | + { |
| 655 | + $loc = "eastus2euap" |
| 656 | + New-AzResourceGroup -Name $rgname -Location $loc -Force |
| 657 | + |
| 658 | + # NRP |
| 659 | + $subnet = New-AzVirtualNetworkSubnetConfig -Name ('subnet' + $rgname) -AddressPrefix "10.0.0.0/24" -DefaultOutboundAccess $false |
| 660 | + $vnet = New-AzVirtualNetwork -Force -Name ('vnet' + $rgname) -ResourceGroupName $rgname -Location $loc -AddressPrefix "10.0.0.0/16" -Subnet $subnet |
| 661 | + $subnetId = $vnet.Subnets[0].Id |
| 662 | + |
| 663 | + $vmssName = 'vmss' + $rgname |
| 664 | + $adminUsername = 'Foo12' |
| 665 | + $adminPassword = $PLACEHOLDER |
| 666 | + $ipCfg = New-AzVmssIPConfig -Name 'test' -SubnetId $subnetId -Primary |
| 667 | + |
| 668 | + # Build a LifecycleHooksProfile inline and pass it directly to New-AzVmssConfig |
| 669 | + $hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT8H' -DefaultAction 'Approve' |
| 670 | + $hooksProfile = [Microsoft.Azure.Management.Compute.Models.LifecycleHooksProfile]::new() |
| 671 | + $hooksProfile.LifecycleHooks = [System.Collections.Generic.List[Microsoft.Azure.Management.Compute.Models.LifecycleHook]]::new() |
| 672 | + $hooksProfile.LifecycleHooks.Add($hook) |
| 673 | + |
| 674 | + $vmss = New-AzVmssConfig -Location $loc -SkuCapacity 1 -SkuName 'Standard_DS1_v2' -UpgradePolicyMode 'Automatic' -LifecycleHooksProfile $hooksProfile ` |
| 675 | + | Add-AzVmssNetworkInterfaceConfiguration -Name 'test' -Primary $true -IPConfiguration $ipCfg ` |
| 676 | + | Set-AzVmssOSProfile -ComputerNamePrefix 'test' -AdminUsername $adminUsername -AdminPassword $adminPassword ` |
| 677 | + | Set-AzVmssStorageProfile -OsDiskCreateOption 'FromImage' -OsDiskCaching 'None' ` |
| 678 | + -ImageReferenceOffer 'WindowsServer' -ImageReferenceSku '2022-Datacenter' ` |
| 679 | + -ImageReferenceVersion 'latest' -ImageReferencePublisher 'MicrosoftWindowsServer' |
| 680 | + |
| 681 | + # Pre-deploy assertion: profile is on the in-memory object |
| 682 | + Assert-NotNull $vmss.LifecycleHooksProfile |
| 683 | + Assert-AreEqual 1 $vmss.LifecycleHooksProfile.LifecycleHooks.Count |
| 684 | + |
| 685 | + # Deploy |
| 686 | + $created = New-AzVmss -ResourceGroupName $rgname -Name $vmssName -VirtualMachineScaleSet $vmss |
| 687 | + Assert-NotNull $created |
| 688 | + |
| 689 | + # Read back from service and verify |
| 690 | + $read = Get-AzVmss -ResourceGroupName $rgname -VMScaleSetName $vmssName |
| 691 | + Assert-NotNull $read.LifecycleHooksProfile |
| 692 | + Assert-AreEqual 1 $read.LifecycleHooksProfile.LifecycleHooks.Count |
| 693 | + Assert-AreEqual 'UpgradeAutoOSScheduling' $read.LifecycleHooksProfile.LifecycleHooks[0].Type |
| 694 | + Assert-AreEqual ([System.TimeSpan]::FromHours(8)) $read.LifecycleHooksProfile.LifecycleHooks[0].WaitDuration |
| 695 | + Assert-AreEqual 'Approve' $read.LifecycleHooksProfile.LifecycleHooks[0].DefaultAction |
| 696 | + } |
| 697 | + finally |
| 698 | + { |
| 699 | + Clean-ResourceGroup $rgname |
| 700 | + } |
| 701 | +} |
| 702 | + |
| 703 | + |
| 704 | +<# |
| 705 | +.SYNOPSIS |
| 706 | +End-to-end test for Get-AzVmssLifecycleHookEvent (List + Get-single) and Update-AzVmssLifecycleHookEvent |
| 707 | +(WaitUntil delay + ActionState=Approved). |
| 708 | +
|
| 709 | +Uses the seekAutoOSUpgradeApproval admin REST endpoint (via Invoke-AzRestMethod) to synthesize a |
| 710 | +lifecycle hook event on demand, instead of waiting for the platform's AutoOSUpgrade scheduler. |
| 711 | +
|
| 712 | +Flow: |
| 713 | + 1. Deploy a 1-instance Uniform VMSS with AutomaticOSUpgrade enabled, Application Health (Linux) |
| 714 | + extension, and a UpgradeAutoOSScheduling lifecycle hook attached. |
| 715 | + 2. POST to /seekAutoOSUpgradeApproval with the same platformImageReference used for the VMSS. |
| 716 | + 3. LIST events -- should now contain exactly 1. |
| 717 | + 4. GET the event by name. |
| 718 | + 5. UPDATE -WaitUntil by +10 min. |
| 719 | + 6. UPDATE -ActionState 'Approved'. |
| 720 | + 7. GET the event again -- assert both updates persisted. |
| 721 | +#> |
| 722 | +function Test-VmssLifecycleHookEventEndToEnd |
| 723 | +{ |
| 724 | + $rgname = Get-ComputeTestResourceName |
| 725 | + |
| 726 | + try |
| 727 | + { |
| 728 | + $loc = "eastus2euap" |
| 729 | + New-AzResourceGroup -Name $rgname -Location $loc -Force |
| 730 | + |
| 731 | + # NRP (subnet must be defaultOutboundAccess=$false for the sub's policy) |
| 732 | + $subnet = New-AzVirtualNetworkSubnetConfig -Name ('subnet' + $rgname) -AddressPrefix "10.0.0.0/24" -DefaultOutboundAccess $false |
| 733 | + $vnet = New-AzVirtualNetwork -Force -Name ('vnet' + $rgname) -ResourceGroupName $rgname -Location $loc -AddressPrefix "10.0.0.0/16" -Subnet $subnet |
| 734 | + $subnetId = $vnet.Subnets[0].Id |
| 735 | + |
| 736 | + # Image reference (must match the body of seekAutoOSUpgradeApproval below) |
| 737 | + $publisher = 'canonical' |
| 738 | + $offer = '0001-com-ubuntu-server-focal' |
| 739 | + $sku = '20_04-lts-gen2' |
| 740 | + $version = 'latest' |
| 741 | + |
| 742 | + $vmssName = 'vmss' + $rgname |
| 743 | + $adminUsername = 'foo12' |
| 744 | + $adminPassword = $PLACEHOLDER |
| 745 | + $ipCfg = New-AzVmssIPConfig -Name 'test' -SubnetId $subnetId -Primary |
| 746 | + |
| 747 | + # 1-instance Uniform VMSS with AutoOSUpgrade + Application Health extension + lifecycle hook attached. |
| 748 | + $hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT1H' -DefaultAction 'Approve' |
| 749 | + |
| 750 | + $vmss = New-AzVmssConfig -Location $loc -SkuCapacity 1 -SkuName 'Standard_DS1_v2' ` |
| 751 | + -OrchestrationMode 'Uniform' -UpgradePolicyMode 'Automatic' -EnableAutomaticOSUpgrade ` |
| 752 | + | Add-AzVmssNetworkInterfaceConfiguration -Name 'test' -Primary $true -IPConfiguration $ipCfg ` |
| 753 | + | Set-AzVmssOSProfile -ComputerNamePrefix 'test' -AdminUsername $adminUsername -AdminPassword $adminPassword ` |
| 754 | + | Set-AzVmssStorageProfile -OsDiskCreateOption 'FromImage' -OsDiskCaching 'None' ` |
| 755 | + -ImageReferenceOffer $offer -ImageReferenceSku $sku ` |
| 756 | + -ImageReferenceVersion $version -ImageReferencePublisher $publisher |
| 757 | + |
| 758 | + # AutoOSUpgrade requires a health probe or health extension. |
| 759 | + Add-AzVmssExtension -VirtualMachineScaleSet $vmss ` |
| 760 | + -Name 'AppHealth' ` |
| 761 | + -Publisher 'Microsoft.ManagedServices' ` |
| 762 | + -Type 'ApplicationHealthLinux' ` |
| 763 | + -TypeHandlerVersion '1.0' ` |
| 764 | + -Setting @{ protocol = 'http'; port = 80; requestPath = '/' } ` |
| 765 | + -AutoUpgradeMinorVersion $true | Out-Null |
| 766 | + |
| 767 | + $vmss = Set-AzVmssLifecycleHooksProfile -VirtualMachineScaleSet $vmss -LifecycleHook $hook |
| 768 | + |
| 769 | + New-AzVmss -ResourceGroupName $rgname -Name $vmssName -VirtualMachineScaleSet $vmss |
| 770 | + |
| 771 | + # Synthesize a lifecycle hook event via the admin REST endpoint. |
| 772 | + # The platformImageReference must match the image used to deploy the VMSS. |
| 773 | + $subId = (Get-AzContext).Subscription.Id |
| 774 | + $apiPath = "/subscriptions/$subId/resourceGroups/$rgname/providers/Microsoft.Compute/virtualMachineScaleSets/$vmssName/seekAutoOSUpgradeApproval?api-version=2025-11-01" |
| 775 | + $body = @{ |
| 776 | + platformImageReference = @{ |
| 777 | + publisher = $publisher |
| 778 | + offer = $offer |
| 779 | + sku = $sku |
| 780 | + version = $version |
| 781 | + } |
| 782 | + } | ConvertTo-Json -Depth 5 |
| 783 | + $approval = Invoke-AzRestMethod -Method POST -Path $apiPath -Payload $body |
| 784 | + Assert-True { $approval.StatusCode -ge 200 -and $approval.StatusCode -lt 300 } "seekAutoOSUpgradeApproval returned HTTP $($approval.StatusCode): $($approval.Content)" |
| 785 | + |
| 786 | + # Bounded short poll for the synthesized event to materialize (event count must reach 1). |
| 787 | + $events = $null |
| 788 | + for ($i = 0; $i -lt 10; $i++) |
| 789 | + { |
| 790 | + $events = Get-AzVmssLifecycleHookEvent -ResourceGroupName $rgname -VMScaleSetName $vmssName |
| 791 | + if ($events -and @($events).Count -gt 0) { break } |
| 792 | + Start-TestSleep -Seconds 30 |
| 793 | + } |
| 794 | + Assert-NotNull $events "Expected Get-AzVmssLifecycleHookEvent to return at least one event after seekAutoOSUpgradeApproval" |
| 795 | + Assert-AreEqual 1 @($events).Count |
| 796 | + $event = @($events)[0] |
| 797 | + |
| 798 | + # GET single event by name and verify |
| 799 | + $fetched = Get-AzVmssLifecycleHookEvent -ResourceGroupName $rgname -VMScaleSetName $vmssName -Name $event.Name |
| 800 | + Assert-NotNull $fetched |
| 801 | + Assert-AreEqual $event.Name $fetched.Name |
| 802 | + |
| 803 | + # UPDATE WaitUntil: delay by 10 min |
| 804 | + $currentWaitUntil = [DateTime]::Parse($fetched.Properties.WaitUntil).ToUniversalTime() |
| 805 | + $newWaitUntilStr = $currentWaitUntil.AddMinutes(10).ToString("yyyy-MM-ddTHH:mm:ssZ") |
| 806 | + Update-AzVmssLifecycleHookEvent -ResourceGroupName $rgname -VMScaleSetName $vmssName -Name $event.Name -WaitUntil $newWaitUntilStr |
| 807 | + |
| 808 | + # UPDATE ActionState: mark the single target resource Approved |
| 809 | + Update-AzVmssLifecycleHookEvent -ResourceGroupName $rgname -VMScaleSetName $vmssName -Name $event.Name -ActionState 'Approved' |
| 810 | + |
| 811 | + # Re-GET and verify both updates persisted |
| 812 | + $fetched2 = Get-AzVmssLifecycleHookEvent -ResourceGroupName $rgname -VMScaleSetName $vmssName -Name $event.Name |
| 813 | + Assert-NotNull $fetched2 |
| 814 | + Assert-AreEqual 1 $fetched2.Properties.TargetResources.Count |
| 815 | + Assert-AreEqual 'Approved' $fetched2.Properties.TargetResources[0].ActionState |
| 816 | + $updatedWaitUntil = [DateTime]::Parse($fetched2.Properties.WaitUntil).ToUniversalTime() |
| 817 | + Assert-True { $updatedWaitUntil -gt $currentWaitUntil } "Expected WaitUntil to be updated to a later timestamp" |
| 818 | + } |
| 819 | + finally |
| 820 | + { |
| 821 | + Clean-ResourceGroup $rgname |
| 822 | + } |
| 823 | +} |
0 commit comments