Skip to content
Open
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
6 changes: 4 additions & 2 deletions parts/windows/windowscsehelper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ $global:WINDOWS_CSE_ERROR_ORAS_PULL_CREDENTIAL_PROVIDER=81 # exit code for error
$global:WINDOWS_CSE_ERROR_ORAS_PULL_POD_INFRA_CONTAINER=82 # exit code for error pulling pause image with oras from registry
$global:WINDOWS_CSE_ERROR_NETWORK_ISOLATED_CLUSTER_CSE_NOT_CACHED=83 # exit code for cse of network isolated cluster not cached
$global:WINDOWS_CSE_ERROR_ORAS_PULL_CONTAINERD=84 # exit code for error pulling containerd artifact with oras from registry
$global:WINDOWS_CSE_ERROR_ORAS_PULL_PACKAGE=85 # exit code for general error pulling package with oras from registry
# WINDOWS_CSE_ERROR_MAX_CODE is only used in unit tests to verify whether new error code name is added in $global:ErrorCodeNames
# Please use the current value of WINDOWS_CSE_ERROR_MAX_CODE as the value of the new error code and increment it by 1
$global:WINDOWS_CSE_ERROR_MAX_CODE=85
$global:WINDOWS_CSE_ERROR_MAX_CODE=86

Comment thread
fseldow marked this conversation as resolved.
# Please add new error code for downloading new packages in RP code too
$global:ErrorCodeNames = @(
Expand Down Expand Up @@ -178,7 +179,8 @@ $global:ErrorCodeNames = @(
"WINDOWS_CSE_ERROR_ORAS_PULL_CREDENTIAL_PROVIDER",
"WINDOWS_CSE_ERROR_ORAS_PULL_POD_INFRA_CONTAINER",
"WINDOWS_CSE_ERROR_NETWORK_ISOLATED_CLUSTER_CSE_NOT_CACHED",
"WINDOWS_CSE_ERROR_ORAS_PULL_CONTAINERD"
"WINDOWS_CSE_ERROR_ORAS_PULL_CONTAINERD",
"WINDOWS_CSE_ERROR_ORAS_PULL_PACKAGE"
)

# The package domain to be used
Expand Down
47 changes: 46 additions & 1 deletion staging/cse/windows/azurecnifunc.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,33 @@ function Install-VnetPlugins {
# Download Azure VNET CNI plugins.
# Mirror from https://github.com/Azure/azure-container-networking/releases
$zipfile = [Io.path]::Combine("$AzureCNIDir", "azure-vnet.zip")
DownloadFileOverHttp -Url $VNetCNIPluginsURL -DestinationPath $zipfile -ExitCode $global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE
if ([string]::IsNullOrEmpty($global:BootstrapProfileContainerRegistryServer)) {
DownloadFileOverHttp -Url $VNetCNIPluginsURL -DestinationPath $zipfile -ExitCode $global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE
}
else {
# ni path
# Extract package name and version from URL for ORAS reference.
# URL format: https://packages.aks.azure.com/azure-cni/v${version}/binaries/<package-name>-windows-amd64-v${version}.zip
# packageName examples include azure-vnet-cni, azure-vnet-cni-overlay, azure-vnet-cni-swift,
# and Windows single-tenancy variants such as azure-vnet-cni-singletenancy (including suffixed forms).
$packageInfo = Get-PackageNameAndVersionFromCniUrl -Url $VNetCNIPluginsURL
if (-not $packageInfo) {
Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE -ErrorMessage "Failed to extract Azure VNet CNI package version tag from URL: $VNetCNIPluginsURL"
}
Comment thread
fseldow marked this conversation as resolved.
$cniPackageVersionTag = $packageInfo.Version
$orasPackageName = $packageInfo.PackageName

Logs-To-Event -TaskName "AKS.WindowsCSE.DownloadAzureVnetCniWithOras" -TaskMessage "Start to download Azure VNet CNI with oras. CniPackageVersionTag: $cniPackageVersionTag, BootstrapProfileContainerRegistryServer: $global:BootstrapProfileContainerRegistryServer"
$orasReference = "$global:BootstrapProfileContainerRegistryServer/aks/packages/azure/${orasPackageName}:${cniPackageVersionTag}"
$cachedFileName = Get-FileNameFromUrl -Url $VNetCNIPluginsURL
Comment thread
fseldow marked this conversation as resolved.
try {
Retry-Command -Command "DownloadFileWithOras" -Args @{Reference = $orasReference; DestinationPath = $zipfile; CachedFile = $cachedFileName } -Retries 5 -RetryDelaySeconds 10
}
Comment thread
fseldow marked this conversation as resolved.
Comment thread
fseldow marked this conversation as resolved.
catch {
# TODO: modify WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE to WINDOWS_CSE_ERROR_ORAS_PULL_PACKAGE after new VHD release
Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE -ErrorMessage "Exhausted retries for oras pull $orasReference. Error: $_"
}
}
Comment thread
fseldow marked this conversation as resolved.
AKS-Expand-Archive -path $zipfile -DestinationPath $AzureCNIBinDir
del $zipfile

Expand All @@ -26,6 +52,25 @@ function Install-VnetPlugins {
move $AzureCNIBinDir/*.conflist $AzureCNIConfDir
}

function Get-PackageNameAndVersionFromCniUrl {
Param(
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]
$Url
)

# Expected format:
# https://packages.aks.azure.com/azure-cni/vX.Y.Z/binaries/<package-name>-windows-amd64-vX.Y.Z[-suffix].zip
$pattern = '/binaries/(?<PackageName>.+)-windows-amd64-(?<Version>v[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9._-]+)?)\.zip$'
if ($Url -match $pattern) {
Comment thread
fseldow marked this conversation as resolved.
return [PSCustomObject]@{
PackageName = $matches['PackageName']
Version = $matches['Version']
Comment thread
fseldow marked this conversation as resolved.
}
}

return $null
}

function Set-AzureCNIConfig {
Param(
[Parameter(Mandatory = $true)][string]
Expand Down
171 changes: 171 additions & 0 deletions staging/cse/windows/azurecnifunc.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,177 @@ Describe 'GetBroadestRangesForEachAddress' {
}
}

Describe 'Get-PackageNameAndVersionFromCniUrl' {
It 'Should parse package name and version for azure-vnet-cni URL' {
$url = 'https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-windows-amd64-v1.6.20.zip'

$result = Get-PackageNameAndVersionFromCniUrl -Url $url

$result | Should -Not -BeNullOrEmpty
$result.PackageName | Should -Be 'azure-vnet-cni'
$result.Version | Should -Be 'v1.6.20'
}

It 'Should parse package name and version for azure-vnet-cni-swift URL' {
$url = 'https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-swift-windows-amd64-v1.6.20.zip'

$result = Get-PackageNameAndVersionFromCniUrl -Url $url

$result | Should -Not -BeNullOrEmpty
$result.PackageName | Should -Be 'azure-vnet-cni-swift'
$result.Version | Should -Be 'v1.6.20'
}

It 'Should parse package name and hotfix version suffix from URL' {
$url = 'https://packages.aks.azure.com/azure-cni/v1.6.1-hotfix20241024ApipaGW/binaries/azure-vnet-cni-windows-amd64-v1.6.1-hotfix20241024ApipaGW.zip'

$result = Get-PackageNameAndVersionFromCniUrl -Url $url

$result | Should -Not -BeNullOrEmpty
$result.PackageName | Should -Be 'azure-vnet-cni'
$result.Version | Should -Be 'v1.6.1-hotfix20241024ApipaGW'
}

It 'Should return null for invalid URL format' {
$url = 'https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-linux-amd64-v1.6.20.tar.gz'

$result = Get-PackageNameAndVersionFromCniUrl -Url $url

$result | Should -Be $null
}
}
Comment thread
fseldow marked this conversation as resolved.

Describe 'Install-VnetPlugins ORAS path' {
BeforeAll {
# Stub functions from networkisolatedclusterfunc.ps1 that are not sourced in tests
if (-not (Get-Command 'Get-FileNameFromUrl' -ErrorAction SilentlyContinue)) {
function global:Get-FileNameFromUrl { param($Url) return "azure-vnet-cni-windows-amd64-v1.6.20.zip" }
}
if (-not (Get-Command 'DownloadFileWithOras' -ErrorAction SilentlyContinue)) {
function global:DownloadFileWithOras {
param(
[string]$Reference,
[string]$DestinationPath,
[string]$Platform = "windows/amd64",
[string]$CachedFile = ""
)
}
}
}

BeforeEach {
Mock Set-ExitCode -MockWith {
param($ExitCode, $ErrorMessage)
throw $ErrorMessage
} -Verifiable
Mock Logs-To-Event -MockWith { } -Verifiable
Mock Create-Directory -MockWith { } -Verifiable
Mock AKS-Expand-Archive -MockWith { } -Verifiable
Mock Get-FileNameFromUrl -MockWith { return "azure-vnet-cni-windows-amd64-v1.6.20.zip" } -Verifiable
Mock DownloadFileWithOras -MockWith { } -Verifiable
Mock Start-Sleep -MockWith { } -Verifiable

# Suppress move and del which operate on files that don't exist in test
Mock Move-Item -MockWith { } -Verifiable
Mock Remove-Item -MockWith { } -Verifiable

$global:AzureCNIDir = $TestDrive
}

AfterEach {
$global:BootstrapProfileContainerRegistryServer = $null
}

Context 'ORAS reference construction' {
It 'Should call DownloadFileWithOras with correct reference for azure-vnet-cni' {
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"

Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-windows-amd64-v1.6.20.zip"

Assert-MockCalled -CommandName "DownloadFileWithOras" -Exactly -Times 1 -ParameterFilter {
$Reference -eq "myregistry.azurecr.io/aks/packages/azure/azure-vnet-cni:v1.6.20"
}
}

Comment thread
fseldow marked this conversation as resolved.
It 'Should call DownloadFileWithOras with correct reference for azure-vnet-cni-overlay' {
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"

Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-overlay-windows-amd64-v1.6.20.zip"

Assert-MockCalled -CommandName "DownloadFileWithOras" -Exactly -Times 1 -ParameterFilter {
$Reference -eq "myregistry.azurecr.io/aks/packages/azure/azure-vnet-cni-overlay:v1.6.20"
}
}

It 'Should call DownloadFileWithOras with correct reference for azure-vnet-cni-swift' {
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"

Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-swift-windows-amd64-v1.6.20.zip"

Assert-MockCalled -CommandName "DownloadFileWithOras" -Exactly -Times 1 -ParameterFilter {
$Reference -eq "myregistry.azurecr.io/aks/packages/azure/azure-vnet-cni-swift:v1.6.20"
Comment thread
fseldow marked this conversation as resolved.
}
}

It 'Should pass correct destination path to DownloadFileWithOras' {
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"

Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-windows-amd64-v1.6.20.zip"

$expectedZipPath = [Io.path]::Combine("$global:AzureCNIDir", "azure-vnet.zip")
Assert-MockCalled -CommandName "DownloadFileWithOras" -Exactly -Times 1 -ParameterFilter {
$DestinationPath -eq $expectedZipPath
}
}
}

Context 'URL parse failure' {
It 'Should set exit code when URL format is invalid' {
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"

{ Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/invalid-linux-amd64-v1.6.20.tar.gz" } | Should -Throw "*Failed to extract*"

Assert-MockCalled -CommandName "Set-ExitCode" -Exactly -Times 1 -ParameterFilter {
$ExitCode -eq $global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE
}
}
}

Context 'ORAS pull failure' {
It 'Should set exit code on pull failure' {
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"

Mock DownloadFileWithOras -MockWith { throw "oras pull failed" }
Mock Start-Sleep -MockWith { }

{ Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-windows-amd64-v1.6.20.zip" } | Should -Throw "*Exhausted retries*"

Assert-MockCalled -CommandName "Set-ExitCode" -Exactly -Times 1 -ParameterFilter {
$ExitCode -eq $global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE
}
}
}

Context 'HTTP download path' {
It 'Should use DownloadFileOverHttp when BootstrapProfileContainerRegistryServer is not set' {
$global:BootstrapProfileContainerRegistryServer = $null

Mock DownloadFileOverHttp -MockWith { } -Verifiable

Install-VnetPlugins -AzureCNIConfDir "$TestDrive\cniconf" -AzureCNIBinDir "$TestDrive\cnibin" `
-VNetCNIPluginsURL "https://packages.aks.azure.com/azure-cni/v1.6.20/binaries/azure-vnet-cni-windows-amd64-v1.6.20.zip"

Assert-MockCalled -CommandName "DownloadFileOverHttp" -Exactly -Times 1
}
}
}

Describe 'Set-AzureCNIConfig' {
BeforeEach {
$azureCNIConfDir = "$PSScriptRoot\azurecnifunc.tests.suites"
Expand Down
Loading