ArcHci.psm1
######################################################################################### # # Copyright (c) Microsoft Corporation. All rights reserved. # # ARC appliance self-service VM Day 0/2 Operations # ######################################################################################### # requires -runasadministrator using module moc # Imports Common.psm1 # this also imports $global:defaultworkingDir Import-Module ((Get-Module "MOC" -ListAvailable | Sort-Object Version -Descending)[0].ModuleBase + "\common.psm1") $moduleName = "ArcHci" $moduleVersion = "1.1.87" $global:ArcHciModule = $moduleName $cloudNames = "AzureCloud", "AzureUSGovernment", "AzureChinaCloud", "AzureGermanCloud", "Azure.Local", "AzurePPE" $defaultTokenExpiryDays = 365 $global:kvaCtlFullPath = "C:\Program Files\AksHci\kvactl.exe" $global:sshPrivateKeyFile = "C:\ProgramData\kva\.ssh\logkey" $azureLocalCloudArmApiVersion = "2022-09-01" $regexPatternAzureResourceGroup = "^[-\w\._\(\)]+" $regexPatternRFC1123 = "^[-a-zA-Z0-9\.]+" $regexPatternVersionNumber = "^[0-9]+(?:\.[0-9]+)+" $regexPatternHostName = "^(?!-)[a-zA-Z0-9-]{1,}[^-]$" $regexPatternDomainName = "^[A-Za-z0-9_@\!#\$\^%&*()+=\-[\]\\\';,\.\/\{\}\|\:<>\?]+([\\-\\.]{1}[A-Za-z0-9_@\!#\$\^%&*()+=\-[\]\\\';,\.\/\{\}\|\:<>\?]+)*\.[A-Za-z0-9_@\!#\$\^%&*()+=\-[\]\\\';,\.\/\{\}\|\:<>\?]{2,10}$" $regexPatternCIDRFormat = "^([0-9]{1,3}\.){3}[0-9]{1,3}\/\b(([0-9]|[1-2][0-9]|3[0-2]))?$" $regexPatternProxyUrl = "^(?:https?:\/\/)(?:(.*)(?::(.*))@)?[a-zA-Z0-9][a-zA-Z0-9-_]{0,61}\.([a-zA-Z0-9][a-zA-Z0-9-_]{0,30}\.){0,8}([a-zA-Z]){1,62}(:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4})))?$" $regexPatternProxyIP = "^(:?https?:\/\/)(?:(.*)(?::(.*))@)?((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4})))(\/){0,1}$" $regexPatternHttpsUrl = "^https?:\/\/" $space = "^\s+$" $spaceWithChar = "\s" $ipv4ValidationError = "{0} {1} is an invalid ip address" $regexPatternAzureResourceGroupError = "Invalid string {0} detected. {1} can contain only letters, numbers and the following special characters: -,.,_,(,)" $regexPatternRFC1123Error = "Invalid string {0} detected. {1} can contain only letters, numbers and the following special characters: -,." $regexPatternVersionNumberError = "Invalid string {0} detected. {1} can contain only number(s) followed by a special character '.' followed again by number(s). E.g 1.2, 1.2.6 " $regexPatternDomainNameError = ("Invalid string {0} detected. Please makes sure {1} matches the following criteria:" + "`n1. The domain name should be a-z | A-Z | 0-9 and special characters" + "`n2. Last Tld must be at least two characters, and a maximum of 10 characters" + "`n3. The domain name can be a subdomain (e.g. sharepoint.microsoft.com)" + "`n4. Or it can be an ip address e.g. 192.168.0.1, 1.1.1.1" + "`n5. Or it can be a hostname and must be at least two characters e.g. localhost") $regexPatternCIDRFormatError = "Invalid IPV4 address {0}. {1} address does not conform to cidr format eg. 192.0.1.1/16, 255.255.255.0/24" $regexPatternProxyUrlError = "Invalid {0} {1} url" $regexPatternHttpsUrlError = "Invalid {0} {1} https url" $spaceError = "Value must not contain whitespace(s)" $spaceErrorForFolderFilePath = "Value should not only contain whitespace(s)" $fileFolderPathError = "Path is incorrect or file\folder does not exist" $sleepDuration = 5 $timeout = 1800 #seconds for 30min $timeoutDeployARB = 7200 # seconds for 2 hours $timeoutForExtensionCreate = 3600 #seconds for 60min $timeoutForExtensionCreateInMinutes = 60 $fileError = "Given path is not a valid file path" $invalidaK8snodeendIP = "k8snodeippoolend ipaddress must be greater than k8snodeippoolstart ip address" $installDirName = "ArcHci" $installDir = "$env:ProgramData" $numberOfArcMgmtPrechecks = 6 $numberOfArcHciMocPrechecks = 4 $logPath = "" $logFile = "" $arcVMEnabledMsg = "Arc vm management successfully enabled!" $defaultNamespace = "default" $nothingToDelete = "so nothing to delete" New-ModuleEventLog -moduleName $moduleName function Reset-ArcHciConfigurationKey { <# .DESCRIPTION Resets the ARC HCI module configuration key #> $global:configurationKeys[$global:ArcHciModule] = "HKLM:SOFTWARE\Microsoft\${global:ArcHciModule}PS"; } if (!$global:config) { $global:config = @{} } function Initialize-ArcHciConfiguration { <# .DESCRIPTION Initialize ARC HCI Configuration #> Reset-ArcHciConfigurationKey if (-Not $global:config.ContainsKey($global:ArcHciModule)) { $global:config += @{ $global:ArcHciModule = @{ "workingDir" = $global:defaultworkingDir }; } } if (-Not (Test-Configuration -moduleName $global:ArcHciModule)) { Set-ConfigurationValue -name "workingDir" -value $global:defaultworkingDir -module $global:ArcHciModule Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $global:defaultworkingDir Save-Configuration -moduleName $global:ArcHciModule } } function Initialize-Telemetry { # Import helper functions Import-Module "$PSScriptRoot\TelemetryCommon.psm1" -Force # Import event writers Add-Type -Path "$PSScriptRoot\Microsoft.AzureStack.ArcHci.Ubercrud.Observability.Events.dll" } Initialize-Telemetry Initialize-ArcHciConfiguration function Uninitialize-ArcHciConfiguration { <# .DESCRIPTION Uninitializes ARC HCI Configuration Wipes off any existing cached configuration #> if ($global:config.ContainsKey($global:ArcHciModule)) { $global:config.Remove($global:ArcHciModule) } } function Get-ArcHciConfig { <# .DESCRIPTION Loads and returns the current ARC HCI configuration. #> Import-ArcHciConfig return $global:config[$global:ArcHciModule] } function Get-ArcHciConfigValue { <# .DESCRIPTION Reads a configuration value from the registry .PARAMETER name Name of the configuration value #> param ( [String] $name ) return Get-ConfigurationValue -name $name -module $global:ArcHciModule } function Import-ArcHciConfig { <# .DESCRIPTION Loads a configuration from persisted storage. If no configuration is present then a default configuration can be optionally generated and persisted. #> Reset-ArcHciConfigurationKey if (Test-Configuration -moduleName $global:ArcHciModule) { Import-Configuration -moduleName $global:ArcHciModule } else { throw "ARCHCI doesn't currently have a config file. Please make sure to reload all powershell windows before continuing" } } function Set-ArcHciConfigValue { <# .DESCRIPTION Persists a configuration value to the registry .PARAMETER name Name of the configuration value .PARAMETER value Value to be persisted #> param ( [String] $name, [Object] $value ) Reset-ArcHciConfigurationKey Set-ConfigurationValue -name $name -value $value -module $global:ArcHciModule } function Set-ArcHciConfig { <# .DESCRIPTION Configures ARCHCI by persisting the specified parameters to the registry. Any parameter which is not explicitly provided by the user will be defaulted. .PARAMETER workingDir Path to the working directory #> [CmdletBinding()] param ( [String] $workingDir = $global:defaultworkingDir ) Reset-ArcHciConfigurationKey $workingDir = $workingDir.TrimEnd('\\').Replace('\\','\') Set-ConfigurationValue -name "workingDir" -value $workingDir -module $global:ArcHciModule Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $workingDir Save-Configuration -moduleName $global:ArcHciModule } function New-MocNetworkSetting { <# .DESCRIPTION Create network settings to be used for the Arc Hci deployment. .PARAMETER name The name of the vnet .PARAMETER vswitchName The name of the vswitch .PARAMETER MacPoolName The name of the mac pool .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER vipPoolStart Beginning of the IP address pool .PARAMETER vipPoolEnd End of the IP address pool .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .OUTPUTS VirtualNetwork object .EXAMPLE New-MocNetworkSetting -name "External" -vipPoolStart "172.16.0.240" -vipPoolEnd "172.16.0.250" #> param ( [Parameter(Mandatory=$true)] [string] $name, [Parameter(Mandatory=$true)] [string] $vswitchName, [Parameter(Mandatory=$false)] [String] $MacPoolName = $global:cloudMacPool, [Parameter(Mandatory=$false)] [int] $vlanID = $global:defaultVlanID, [Parameter(Mandatory=$false)] [String] $ipaddressprefix, [Parameter(Mandatory=$false)] [String] $gateway, [Parameter(Mandatory=$false)] [String[]] $dnsservers, [Parameter(Mandatory=$true)] [String] $vipPoolStart, [Parameter(Mandatory=$true)] [String] $vipPoolEnd, [Parameter(Mandatory=$false)] [String] $k8snodeippoolstart, [Parameter(Mandatory=$false)] [String] $k8snodeippoolend ) return New-VirtualNetwork -name $name -vswitchName $vswitchName -MacPoolName $MacPoolName -vlanID $vlanID -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend } function New-ArcHciMocTokenFile { <# .DESCRIPTION Creates a new MOC Admin Identity for the Appliance and writes it to the given file path. .PARAMETER arcTokenFilePath Optional parameter. Path to the file where the arc token would be output. #> Param ( [Parameter(Mandatory=$false)] [String] $arcTokenFilePath = ($(Get-Location).Path + "\kvatoken.tok") ) $clusterName = "Appliance" try { Remove-MocIdentity -name $clusterName } catch { } Remove-CertFiles $base64Identity = New-MocAdminIdentity -name $clusterName -validityDays $defaultTokenExpiryDays $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64Identity)) $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding) Write-Output "HCI login file successfully generated in '$arcTokenFilePath'" } function New-ArcHciIdentityFiles { <# .DESCRIPTION Creates the Arc HCI token files .PARAMETER workDirectory Optional parameter. Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER mocConfigFilePath Optional parameter. Path to the hci-config.json file. .OUTPUTS N/A .EXAMPLE New-ArcHciIdentityFiles #> Param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"), [Parameter(Mandatory=$false)] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [String] $mocImage, [Parameter(Mandatory=$false)] [String] $mocVersion, [Parameter(Mandatory=$false)] [String] $mocConfigFilePath = ($workDirectory + "\hci-config.json") ) $mocConfig = Get-MocConfig $mocoperator = "moc-operator" try { Remove-MocIdentity -name $mocoperator } catch { } $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null $configData = @{} $configData["secret.loginString"] = $mocOperatorIdentity $configData["secret.cloudFQDN"] = $mocConfig.cloudFqdn if ($azstackhciImage) { $configData["azstackhciOperator.image"] = $azstackhciImage } if ($azstackhciVersion) { $configData["azstackhciOperator.version"] = $azstackhciVersion } if ($mocImage) { $configData["mocOperator.image"] = $mocImage } if ($mocVersion) { $configData["mocOperator.version"] = $mocVersion } $jsonData = convertto-json $configData Remove-Item -Path $mocConfigFilePath -Force -ErrorAction Ignore Set-Content -Path $mocConfigFilePath -Value $jsonData -ErrorVariable err Write-Output "MOC config file successfully generated in '$mocConfigFilePath'" Write-Output "Cloud agent service FQDN/IP: '$($mocConfig.cloudFqdn)'" } function New-ArcHciConfigFiles { <# .DESCRIPTION Creates the Arc HCI config files .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location (optional parameter. defaults to "eastus") .PARAMETER resourceGroup Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Name of the Azure resource .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER cloudName The name of the cloud. .PARAMETER armEndpoint The ARM endpoint of the cloud. .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER cloudFqdn Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service. .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER proxyServerHTTP http urls for proxy .PARAMETER proxyServerHTTPS https urls for proxy .PARAMETER proxyServerNoProxy Comma separate list of URLs and IP ranges that should not be proxied. When proxy is enabled, the no proxy list must include at the minimum: localhost,127.0.0.1,.svc,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 .PARAMETER certificateFilePath Name of the cert File Path for proxy .PARAMETER aksExtensionConfigFileName Name of the AKS extension config file (optional parameter. Defaults to "aks-extension-config.json") .PARAMETER aksExtProxyConfig Optional parameter. Proxy Config for AKS Extension .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" -controlPlaneIP "192.168.200.109" -cloudName "AzureCloud" -armEndpoint "https://management.azure.com" #Example if the archci proxy params are provided New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" -controlPlaneIP "192.168.200.109" -cloudName "AzureCloud" -armEndpoint "https://management.azure.com" -proxyServerHTTP "http://myproxy:8080" -proxyServerHTTPS "https://myproxy:8080" -proxyServerNoProxy "localhost,127.0.0.1" -certificateFilePath "C:\Proxy.cert" #Example if the aks extension proxy params are provided New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" -controlPlaneIP "192.168.200.109" -cloudName "AzureCloud" -armEndpoint "https://management.azure.com" -aksExtProxyConfig @{ProxyServerHTTPS = "https://myproxy:8080"; ProxyServerHTTP = "http://myproxy:8080"; ProxyServerNoProxy = "localhost,127.0.0.1" } -certificateFilePath "C:\Proxy.cert" #> Param ( [Parameter(Mandatory=$true)] [GUID] $subscriptionID, [Parameter(Mandatory=$false)] [String] $location = "eastus", [Parameter(Mandatory=$true)] [ValidateScript({ if($_ -notmatch $regexPatternAzureResourceGroup){ $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_,$parameter } return $true })] [String] $resourceGroup, [Parameter(Mandatory=$true)] [ValidateScript({ if($_ -notmatch $regexPatternRFC1123){ $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $resourceName, [Parameter(Mandatory=$true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "ControlPlaneIP" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -in $cloudNames) { return $true } throw "Cloud name '$_' is invalid. Please try one of the following: {1} $($cloudNames -join ', ')" })] [string] $cloudName, [parameter(Mandatory = $true)] [ValidateScript({ if($_ -notmatch $regexPatternHttpsUrl){ $parameter = "armEndpoint" throw $regexPatternHttpsUrlError -f $parameter,$_ } return $true })] [string] $armEndpoint, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "azstackhciImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "azstackhciVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "mocImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $mocImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "mocVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $mocVersion, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if($response){ return $true } if($_ -match $regexPatternHostName){ return $true } if($_ -notmatch $regexPatternDomainName){ $parameter = "cloudFqdn" throw $regexPatternDomainNameError -f $_,$parameter } return $true })] [String] $cloudFqdn, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "vnetName" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $vnetName, # vswitchName can accept any characters [Parameter(Mandatory=$false)] [String] $vswitchName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolstart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolend, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternCIDRFormat){ $parameter = "ipaddressprefix" throw $regexPatternCIDRFormatError -f $_,$parameter } return $true })] [String] $ipAddressPrefix, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $gateway, [Parameter(Mandatory=$false)] [ValidateScript({ if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) { return $true } foreach($i in $_){ $response = Test-IPV4Address -ip $i if(!$response){ $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter,$i } } return $true })] [String[]] $dnsServers = @(), [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "K8sNodeIpPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $k8sNodeIpPoolStart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "K8sNodeIpPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $k8sNodeIpPoolEnd, [Parameter(Mandatory=$false)] [ValidateRange(0, 4094)] [Int] $vlanID, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTP" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTP, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTPS" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTPS, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $spaceWithChar){ throw $spaceError } return $true })] [String] $proxyServerNoProxy, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $certificateFilePath, [Parameter(Mandatory=$false)] [String] $aksExtensionConfigFileName = "aks-extension-config.json", [Parameter(Mandatory = $false)] $aksExtProxyConfig, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $workDirectory = $global:defaultworkingDir ) $workDirectory = $workDirectory.TrimEnd('\\').Replace('\\','\') if (Test-IsHCIMachine) { Test-HCIMachineRequirements } New-ArcHciIdentityFiles -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workDirectory $workDirectory New-ArcHciAksConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -workDirectory $workDirectory -controlPlaneIP $controlPlaneIP ` -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsServers -ipaddressprefix $ipAddressPrefix -k8snodeippoolstart $k8sNodeIppoolStart -k8snodeippoolend $k8sNodeIpPoolEnd -vlanID $vlanID ` -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath $aksExtProxyServerHTTP = $null $aksExtProxyServerHTTPS = $null $aksExtProxyServerNoProxy = $null if ($null -ne $aksExtProxyConfig -and $aksExtProxyConfig.count -gt 0) { $aksExtProxyServerHTTP = $aksExtProxyConfig.ProxyServerHTTP $aksExtProxyServerHTTPS = $aksExtProxyConfig.ProxyServerHTTPS $aksExtProxyServerNoProxy = $aksExtProxyConfig.ProxyServerNoProxy } New-ArcHciAksExtensionConfigFile -cloudName $cloudName -armEndpoint $armEndpoint -location $location -aksExtensionConfigFileName $aksExtensionConfigFileName -workDirectory $workDirectory -proxyServerHTTP $aksExtProxyServerHTTP -proxyServerHTTPS $aksExtProxyServerHTTPS -proxyServerNoProxy $aksExtProxyServerNoProxy } function New-ArcHciAksConfigFiles { <# .DESCRIPTION Creates the Arc Appliance config files .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location (optional parameter. defaults to "eastus") .PARAMETER resourceGroup Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Name of the Azure resource .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER cloudFqdn Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service. .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .PARAMETER proxyServerHTTP http urls for proxy server .PARAMETER proxyServerHTTPS https urls for proxy server .PARAMETER proxyServerNoProxy Comma separate list of URLs and IP ranges that should not be proxied. When proxy is enabled, the no proxy list must include at the minimum: localhost,127.0.0.1,.svc,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 .PARAMETER certificateFilePath Name of the cert File Path for proxy .OUTPUTS N/A .EXAMPLE New-ArcHciAksConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" #> Param ( [Parameter(Mandatory=$true)] [GUID] $subscriptionID, [Parameter(Mandatory=$false)] [String] $location, [Parameter(Mandatory=$true)] [ValidateScript({ if($_ -notmatch $regexPatternAzureResourceGroup){ $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_,$parameter } return $true })] [String] $resourceGroup, [Parameter(Mandatory=$true)] [ValidateScript({ if($_ -notmatch $regexPatternRFC1123){ $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $resourceName, [Parameter(Mandatory=$true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "ControlPlaneIp" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "azstackhciImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "azstackhciVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "mocImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $mocImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "mocVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $mocVersion, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if($response){ return $true } if($_ -match $regexPatternHostName){ return $true } if($_ -notmatch $regexPatternDomainName){ $parameter = "cloudFqdn" throw $regexPatternDomainNameError -f $_,$parameter } return $true })] [String] $cloudFqdn, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "vnetName" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $vnetName, # vswitchName can accept any characters [Parameter(Mandatory=$false)] [String] $vswitchName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolstart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolend, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternCIDRFormat){ $parameter = "ipaddressprefix" throw $regexPatternCIDRFormatError -f $_,$parameter } return $true })] [String] $ipaddressprefix, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $gateway, [Parameter(Mandatory=$false)] [ValidateScript({ if ($_ -eq $null -or $_ -eq 0) { return $true } foreach($i in $_){ $response = Test-IPV4Address -ip $i if(!$response){ $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter,$i } } return $true })] [String[]] $dnsservers = @(), [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "K8sNodeIpPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $k8snodeippoolstart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "K8sNodeIpPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $k8snodeippoolend, [Parameter(Mandatory=$false)] [ValidateRange(0, 4094)] [String] $vlanID, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTP" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTP, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTPS" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTPS, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $spaceWithChar){ throw $spaceError } return $true })] [String] $proxyServerNoProxy, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $certificateFilePath, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $workDirectory ) if (-Not [string]::IsNullOrEmpty($k8snodeippoolstart) -and -Not [string]::IsNullOrEmpty($k8snodeippoolend)){ $verifyendIP = Test-K8snodeIPPoolRange -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend if(!$verifyendIP) { throw "$k8snodeippoolend $invalidaK8snodeendIP" } } if ([String]::IsNullOrEmpty($workDirectory)) { $mocConfig = Get-ArcHciMoc $workDirectory = $($mocConfig.workingDir) Write-Log "Using working directory '$workDirectory' for generating AKS configuration files." } Set-ArcHciConfig -workingDir $workDirectory Set-ArcHciConfigValue -name "configFileLocation" -Value $workDirectory $arcTokenFilePath = Join-Path -Path $workDirectory -ChildPath "kvatoken.tok" Set-ArcHciConfigValue -name "kvaTokenLocation" -Value $arcTokenFilePath New-ArcHciMocTokenFile -arcTokenFilePath $arcTokenFilePath Write-Output "Generating ARC HCI configuration files..." $mocConfig = Get-MocConfig $kubectlLoc = $mocConfig.installationPackageDir $oldPath = $($Env:PATH) if (-Not $oldPath.Split(';').Contains($kubectlLoc)) { $Env:PATH="$($Env:PATH);$kubectlLoc" } if([string]::IsNullOrEmpty($cloudFqdn)) { $cloudFqdn = $mocConfig.cloudFqdn } Set-ArcHciConfigValue -name "controlPlaneIP" -Value $controlPlaneIP Set-ArcHciConfigValue -name "cloudFqdn" -Value $cloudFqdn Set-ArcHciConfigValue -name "vswitchName" -Value $vswitchName Set-ArcHciConfigValue -name "vippoolstart" -Value $vippoolstart Set-ArcHciConfigValue -name "vippoolend" -Value $vippoolend Set-ArcHciConfigValue -name "ipaddressprefix" -Value $ipaddressprefix Set-ArcHciConfigValue -name "dnsservers" -Value $dnsservers Set-ArcHciConfigValue -name "gateway" -Value $gateway Set-ArcHciConfigValue -name "k8snodeippoolstart" -Value $k8snodeippoolstart Set-ArcHciConfigValue -name "k8snodeippoolend" -Value $k8snodeippoolend Set-ArcHciConfigValue -name "vlanID" -Value $vlanID $dnsserver = "" if(-Not [string]::IsNullOrEmpty($ipaddressprefix)) { foreach ($dns in $dnsservers) { $dns = $dns.Trim(" ") if(-Not [string]::IsNullOrEmpty($dns)) { $dnsserver += "`n - " + $dns } } } $yaml = @" # hci-appliance.yaml [config file] # Absolute path to vCenter/HCI/other Fabric/infra specific credentials and configuration details infrastructureConfigPath: "$($workDirectory.Replace("\","\\"))\\hci-infra.yaml" # Specify admin cluster settings applianceClusterConfig: # Used to connect to the Kubernetes API controlPlaneEndpoint: "$controlPlaneIP" "@ # if proxy params are provided publish networking block in appliance yaml if ((-Not [string]::IsNullOrEmpty($proxyServerHTTP)) -or (-Not [string]::IsNullOrEmpty($proxyServerHTTPS))) { $yaml += @" `n networking: proxy: certificateFilePath: "$($certificateFilePath.Replace("\","\\"))" "@ Set-ArcHciConfigValue -name "certificateFilePath" -Value $certificateFilePath #Appliance require authentication present within the url itself. #For example: http: "http://username:password@contoso.com:3128" $yaml += @" `n http: "$proxyServerHTTP" https: "$proxyServerHTTPS" "@ Set-ArcHciConfigValue -name "proxyServerHTTP" -Value $proxyServerHTTP Set-ArcHciConfigValue -name "proxyServerHTTPS" -Value $proxyServerHTTPS $yaml += @" `n noproxy: "$proxyServerNoProxy" "@ Set-ArcHciConfigValue -name "proxyServerNoProxy" -Value $proxyServerNoProxy } $yaml += @" `n # Relative or absolute path to Arc Appliance ARM resource definition to be created in Azure applianceResourceFilePath: "$($workDirectory.Replace("\","\\"))\\hci-resource.yaml" "@ $yamlFile = "$($workDirectory)\hci-appliance.yaml" Remove-Item -Path $yamlFile -Force -ErrorAction Ignore Set-Content -NoNewline -Path $yamlFile -Value $yaml -Encoding UTF8 -ErrorVariable err $yaml = @" # hci-infra.yaml [as referenced in file above] # infra file azurestackhciprovider: # Cloud agent configuration cloudagent: # Address of machine on which HCI Environment is deployed address: "$cloudFqdn" # port and authenticationport port: 55000 authenticationport: 65000 # Relative path to the cloudconfig file to access HCI Environment loginconfigfile: "$($arcTokenFilePath.Replace("\","\\"))" location: MocLocation group: management storagecontainer: MocStorageContainer virtualnetwork: name: "$vnetname" vswitchname: "$vswitchName" "@ if (-Not [string]::IsNullOrEmpty($vlanID)) { $yaml += @" `n vlanid: $($vlanID) "@ } if (-Not [string]::IsNullOrEmpty($vippoolstart)) { $yaml += @" `n vippoolstart: $vippoolstart vippoolend: $vippoolend "@ } if (-Not [string]::IsNullOrEmpty($ipaddressprefix)) { $yaml += @" `n ipaddressprefix: $ipaddressprefix gateway: $gateway dnsservers: $dnsserver k8snodeippoolstart: $k8snodeippoolstart k8snodeippoolend: $k8snodeippoolend "@ } $yaml += @" `n appliancevm: vmsize: Standard_A4_v2 download: workingdirectory: "$($workDirectory.Replace("\","\\"))" "@ $yamlFile = "$($workDirectory)\hci-infra.yaml" Remove-Item -Path $yamlFile -Force -ErrorAction Ignore Set-Content -Path $yamlFile -Value $yaml -Encoding UTF8 -ErrorVariable err $yaml = @" # hci-resource.yaml [as reference in hci-appliance.yaml above] # resource file resource: # Resource group must exist resource_group: $resourceGroup # Resource name name: $resourceName # Location location: $location # Subscription should match CLI context subscription: $subscriptionID "@ $yamlFile = "$($workDirectory)\hci-resource.yaml" Remove-Item -Path $yamlFile -Force -ErrorAction Ignore Set-Content -Path $yamlFile -Value $yaml -Encoding UTF8 -ErrorVariable err Write-Output "Config file successfully generated in '$workDirectory'" } function Remove-ArcHciIdentityFiles { <# .DESCRIPTION Removes the Arc HCI token files .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciIdentityFiles #> Param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) # Remove Identities Remove-Item -Path "$($workDirectory)\hci-config.json" -Force -ErrorAction Ignore $mocoperator = "moc-operator" try { Remove-MocIdentity -name $mocoperator } catch { } } function Remove-ArcHciAksConfigFiles { <# .DESCRIPTION Removes the Arc Appliance config files .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciAksConfigFiles #> Param ( [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) # Remove Identities Remove-Item -Path "$($workDirectory)\kvatoken.tok" -Force -ErrorAction Ignore $clusterName = "Appliance" try { Remove-MocIdentity -name $clusterName } catch { } Remove-CertFiles # Remove the appliance config files Remove-Item -Path "$($workDirectory)\hci-appliance.yaml" -Force -ErrorAction Ignore Remove-Item -Path "$($workDirectory)\hci-infra.yaml" -Force -ErrorAction Ignore Remove-Item -Path "$($workDirectory)\hci-resource.yaml" -Force -ErrorAction Ignore Remove-Item -Path "$($workDirectory)\kubeconfig" -Force -ErrorAction Ignore } function Remove-ArcHciAksExtensionConfigFile { <# .DESCRIPTION Removes the AKS extension config file .PARAMETER aksExtensionConfigFileName Name of the AKS extension config file (optional parameter. Defaults to "aks-extension-config.json") .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciAksExtensionConfigFile #> Param ( [Parameter(Mandatory=$false)] [String] $aksExtensionConfigFileName = "aks-extension-config.json", [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) if (Test-Path "$workDirectory\$aksExtensionConfigFileName") { Remove-Item -Path "$workDirectory\$aksExtensionConfigFileName" -Force -ErrorAction Ignore } } function Remove-ArcHciConfigFiles { <# .DESCRIPTION Removes the Arc HCI config files .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciConfigFiles #> Param ( [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) Remove-ArcHciIdentityFiles -workDirectory $workDirectory Remove-ArcHciAksConfigFiles -workDirectory $workDirectory Remove-ArcHciAksExtensionConfigFile -workDirectory $workDirectory } function New-ArcHciCommonExtensionConfig { <# .DESCRIPTION Generate the common extension configurations. Azure.Local: The configurations will contain the ARM metadata and the Azure.Local public root certificate. Other clouds: Empty configuration, more config data will be added to the configuration file by the steps of installing VM and AKS extensions. .PARAMETER cloudName The name of the cloud. .PARAMETER armEndpoint The ARM endpoint of the cloud. .PARAMETER location Azure location .OUTPUTS The file path of the generated configuration file of the cloud. .EXAMPLE #Azure.Local cloud New-ArcHciCommonExtensionConfig -cloudName "Azure.Local" -armEndpoint "https://armmanagement.autonomous.cloud.private" -location "autonomous" #Azure cloud New-ArcHciCommonExtensionConfig -cloudName "AzureCloud" -armEndpoint "https://management.azure.com" -location "eastus" #> param ( [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -in $cloudNames) { return $true } throw "Cloud name '$_' is invalid. Please try one of the following: {1} $($cloudNames -join ', ')" })] [string] $cloudName, [parameter(Mandatory = $true)] [ValidateScript({ if($_ -notmatch $regexPatternHttpsUrl){ $parameter = "armEndpoint" throw $regexPatternHttpsUrlError -f $parameter,$_ } return $true })] [string] $armEndpoint, [Parameter(Mandatory=$true)] [String] $location ) Write-Log "Entered New-ArcHciCommonExtensionConfig, cloudName: $cloudName, armEndpoint: $armEndpoint" $configFileContent = @{} #region Add endpoint metadata to the config file Write-Log "Adding endpoint metadata for cloud $cloudName" $armMetadata = @{} switch ($cloudName) { "AzureCloud" { $armMetadata["resourceManager"] = "https://management.azure.com" $armMetadata["dataplaneEndpoints.arcConfigEndpoint"] = "https://$($location).dp.kubernetesconfiguration.azure.com" # GNS: clusterconnected-agent expects the endpoint ends with a slash $armMetadata["dataplaneEndpoints.arcGlobalNotificationServiceEndpoint"] = "https://df.guestnotificationservice.azure.com/" $armMetadata["dataplaneEndpoints.arcHybridIdentityServiceEndpoint"] = "https://gbl.his.arc.azure.com/discovery?location=$($location)&api-version=1.0-preview" $armMetadata["suffixes.relayEndpointSuffix"] = ".servicebus.windows.net" # kube-aad-proxy expects the endpoint ends with a slash $armMetadata["authentication.loginEndpoint"] = "https://login.microsoftonline.com/" $armMetadata["suffixes.AcrLoginServer"] = ".azurecr.io" } "AzureUSGovernment" { $armMetadata["resourceManager"] = "https://management.usgovcloudapi.net" $armMetadata["dataplaneEndpoints.arcConfigEndpoint"] = "https://$($location).dp.kubernetesconfiguration.azure.us" # GNS: clusterconnected-agent expects the endpoint ends with a slash $armMetadata["dataplaneEndpoints.arcGlobalNotificationServiceEndpoint"] = "https://guestnotificationservice.azure.us/" $armMetadata["dataplaneEndpoints.arcHybridIdentityServiceEndpoint"] = "https://gbl.his.arc.azure.us/discovery?location=$($location)&api-version=1.0-preview" $armMetadata["suffixes.relayEndpointSuffix"] = ".servicebus.usgovcloudapi.net" # kube-aad-proxy expects the endpoint ends with a slash $armMetadata["authentication.loginEndpoint"] = "https://login.microsoftonline.us/" $armMetadata["suffixes.AcrLoginServer"] = ".azurecr.us" } # Azure.Local {Test-AzureLocalCloud -cloudname $cloudName} { Write-Log "Retrieving ARM metadata from endpoint $armEndpoint for cloud $cloudName" $ArmMetadataEndpoint = "$($armEndpoint)/metadata/endpoints?api-version=$($azureLocalCloudArmApiVersion)" # Retrieve ARM metadata $response = Retry {Invoke-RestMethod -Uri $ArmMetadataEndpoint -Method Get} Write-Log "ARM metadata retrieved successfully." $armMetadata["resourceManager"] = $response.resourceManager.TrimEnd('/') $armMetadata["dataplaneEndpoints.arcConfigEndpoint"] = $response.dataplaneEndpoints.arcConfigEndpoint.TrimEnd('/') # GNS: clusterconnected-agent expects the endpoint ends with a slash $armMetadata["dataplaneEndpoints.arcGlobalNotificationServiceEndpoint"] = $response.dataplaneEndpoints.arcGlobalNotificationServiceEndpoint $armMetadata["dataplaneEndpoints.arcHybridIdentityServiceEndpoint"] = $response.dataplaneEndpoints.arcHybridIdentityServiceEndpoint.TrimEnd('/') $armMetadata["suffixes.relayEndpointSuffix"] = $response.suffixes.relayEndpointSuffix.TrimEnd('/') # kube-aad-proxy expects the endpoint ends with a slash $armMetadata["authentication.loginEndpoint"] = $response.authentication.loginEndpoint # Override the default values in https://github.com/azure-core/ClusterConfigurationAgent/blob/master/charts/azure-arc-k8sagents/values.yaml $armMetadata["suffixes.AcrLoginServer"] = $response.suffixes.AcrLoginServer.TrimEnd('/') } # Add endpoint metadata for other clouds here default { throw "Unsupported cloud: $cloudName" } } # Remove null or empty values from the ARM metadata hashtable foreach ($key in $armMetadata.Keys.Clone()) { if ([string]::IsNullOrEmpty($armMetadata[$key])) { $armMetadata.Remove($key) Write-Log "Removing $key from ARM metadata hashtable because its value is null" } } $configFileContent = $armMetadata #endregion #region Azure.Local cloud only: Get Azure.Local certificate if (Test-AzureLocalCloud -cloudname $cloudName) { $certfilePath = Get-AzureLocalCertificateFilePath Write-Log "Azure.Local public root cert file path: $certfilePath" try { # Read the certificate file content $certContent = Get-Content -Path $certFilePath -Raw # Encode the certificate content to base64 $encodedCert = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($certContent)) } catch { $errorMessage = "Failed to read Azure.Local public root certificate at $certfilePath, error: $_" Write-Log $errorMessage throw $errorMessage } $configFileContent["AzureLocalRootCert"] = $encodedCert } #endregion Write-Log "Exiting New-ArcHciCommonExtensionConfig" return $configFileContent } function New-ArcHciAksExtensionConfigFile { <# .DESCRIPTION Generate a configuration file for the AKS extension and return the file path. .PARAMETER cloudName The name of the cloud. .PARAMETER armEndpoint The ARM endpoint of the cloud. .PARAMETER location Azure location .PARAMETER aksExtensionConfigFileName Name of the AKS extension config file (optional parameter. Defaults to "aks-extension-config.json") .PARAMETER proxyServerHTTP Optional parameter. Proxy server for HTTP .PARAMETER proxyServerHTTPS Optional parameter. Proxy server for HTTPS .PARAMETER proxyServerNoProxy Optional parameter. Proxy server for No Proxy .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS The file path of the generated configuration file of the cloud. .EXAMPLE # Azure.Local cloud New-ExtensionConfigFile -cloudName "Azure.Local" -armEndpoint "https://armmanagement.autonomous.cloud.private" -workDirectory $workDirectory # Azure cloud New-ExtensionConfigFile -cloudName "AzureCloud" -armEndpoint "https://management.azure.com" -workDirectory $workDirectory #> param ( [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -in $cloudNames) { return $true } throw "Cloud name '$_' is invalid. Please try one of the following: {1} $($cloudNames -join ', ')" })] [string] $cloudName, [parameter(Mandatory = $true)] [ValidateScript({ if($_ -notmatch $regexPatternHttpsUrl){ $parameter = "armEndpoint" throw $regexPatternHttpsUrlError -f $parameter,$_ } return $true })] [string] $armEndpoint, [Parameter(Mandatory=$true)] [String] $location, [Parameter(Mandatory=$false)] [String] $aksExtensionConfigFileName = "aks-extension-config.json", [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTP" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTP, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTPS" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTPS, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $spaceWithChar){ throw $spaceError } return $true })] [String] $proxyServerNoProxy, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $workDirectory ) if ([String]::IsNullOrEmpty($workDirectory)) { $mocConfig = Get-ArcHciMoc $workDirectory = $($mocConfig.workingDir) Write-Log "Using working directory '$workDirectory' for generating AKS extension configuration file." } Write-Log "Entered New-ArcHciAksExtensionConfigFile, cloudName: $cloudName, armEndpoint: $armEndpoint, workDirectory: $workDirectory" $configFileContent = New-ArcHciCommonExtensionConfig -cloudName $cloudName -armEndpoint $armEndpoint -location $location if ((-not [string]::IsNullOrEmpty($proxyServerHTTP)) -or (-not [string]::IsNullOrEmpty($proxyServerHTTPS))) { Write-Log "Add proxy configs, proxyServerHTTP: $proxyServerHTTP, proxyServerHTTPS: $proxyServerHTTPS, proxyServerNoProxy: $proxyServerNoProxy" $configFileContent["http_proxy"] = $proxyServerHTTP $configFileContent["https_proxy"] = $proxyServerHTTPS $configFileContent["no_proxy"] = $proxyServerNoProxy } #region Save to extension config file $configFilepath = "$workDirectory\$aksExtensionConfigFileName" $configFileContent | ConvertTo-Json -Depth 10 | Set-Content -Path $configFilePath Write-Log "AKS extension config file is saved to $configFilepath" #endregion Write-Log "Exiting New-ArcHciAksExtensionConfigFile" return $configFilepath } function Get-ArcHciFirstControlPlaneNodeIp { <# .DESCRIPTION Finds the ARC resource bridge control plane VM and retrieves its non-control plane IPv4. .PARAMETER logDir Path to the directory to store the logs #> param ( [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String]$arcHciLogDir = [System.Io.Path]::GetTempPath() ) # Assumption: we assume that the ARC control plane VM is the VM that contains "control-plane" in the name and is located in the **management** group Get-MocConfig > $null # Workaround for Get-MocVirtualMachine initialization. Call into Get-MocConfig first to initialize MOC context. try { $mocVm = Get-MocVirtualMachine -group management -ErrorAction SilentlyContinue $mocVm = $mocVm | Where-Object { $_.name -match ".*control-plane.*" } } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } if ($mocVm -ne $null) { $controlPlaneIp = ([ipaddress](Get-ArcHciConfig).controlPlaneIP) $hypervVm = Get-VM -Name "$($mocVm.name)*" -ComputerName $mocVm.virtualmachineproperties.host.id $vmIpAddresses = ([ipaddress[]]$hypervVm.NetworkAdapters.IPAddresses) | where { $_.AddressFamily -eq "InterNetwork" -And $_ -ne $controlPlaneIp} if ($vmIpAddresses -ne $null) { return $vmIpAddresses[0] } } return $null } function Test-IfCluster { try { $cluster = Get-Cluster -ErrorAction Stop } catch { return $false } return ($cluster -ne $null) } function Get-ArcHciDefaultPath{ <# .DESCRIPTION Return a default path for installation. .OUTPUTS N/A .EXAMPLE Get-ArcHciDefaultPath #> if (Test-IfCluster) { $volumes = (Get-ClusterSharedVolume -ErrorAction Ignore) if ($volumes.Count -gt 0) { $volume = $volumes | Where-Object { $_.Name -like "*Infrastructure*" } if ($volume.Count -gt 0) { $volumePath = $volume[0].SharedVolumeInfo.FriendlyVolumeName } } } if ([String]::IsNullOrEmpty($volumePath)){ $volumePath = $installDir } $volumePath = New-Item -Path $volumePath -Name $installDirName -ItemType Directory -Force return $volumePath } function Get-ArcHciLogs { <# .DESCRIPTION Collects all the logs from the deployment and compresses them. .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .PARAMETER activity Activity name to use when updating progress .PARAMETER ip IP address of the ARC appliance VM or kubernetes api server .PARAMETER logDir Path to the directory to store the logs .PARAMETER kvaTokenPath Path to the KVA token (which was generated during the installation of the ARC resource bridge) .PARAMETER clusterLogTimeSpan The time span in minutes for which to generate the cluster log .PARAMETER aksExtensionConfigFileName Name of the AKS extension config file (optional parameter. Defaults to "aks-extension-config.json") #> param ( [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"), [Parameter()] [String]$activity = $MyInvocation.MyCommand.Name, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String]$logDir, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "Ip" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String]$ip, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -match $space){ throw $spaceErrorForFolderFilePath } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $kvaTokenPath = (Get-ArcHciConfigValue -name "kvaTokenLocation"), [Parameter(Mandatory=$false)] [UInt32] $clusterLogTimeSpan = 10080, [Parameter(Mandatory=$false)] [String] $aksExtensionConfigFileName = "aks-extension-config.json" ) if ([String]::IsNullOrEmpty($logPath)) { $logPath = Get-ArcHciDefaultPath $logFile = Join-Path $logPath "ubercrud.log" } if ([String]::IsNullOrEmpty($logDir)) { $logDir = $logPath } $timestamp = Get-Date -Format "yyyyMMddHHmmss" $arcHciLogDir = $(Join-Path $logDir "archcilogs_$timestamp") if (-not ($arcHciLogDir | Test-Path)) { New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null } New-Item -ItemType Directory -Path "$($arcHciLogDir)\config\" -Force > $null try { $mocConfig = Get-MocConfig } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } Get-ArcHciConfig -ErrorAction SilentlyContinue | Format-Table -AutoSize | Out-File "$($arcHciLogDir)\config\archciconfig.txt" $mocConfig | Format-Table -AutoSize | Out-File "$($arcHciLogDir)\config\mocconfig.txt" Copy-Item -Path "$($workDirectory)\hci-appliance.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore Copy-Item -Path "$($workDirectory)\hci-infra.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore Copy-Item -Path "$($workDirectory)\hci-resource.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore Copy-Item -Path "$($workDirectory)\$aksExtensionConfigFileName" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore # Collecting uber crud logs Copy-Item -Path $logFile -Destination "$($arcHciLogDir)\ubercrud-logs.txt" -ErrorAction Ignore try { Get-MocLogs -path $arcHciLogDir -activity $activity -AgentLogs -NodeVirtualizationLogs } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } Get-DownloadSdkLogs -Path $arcHciLogDir Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Collecting ARC appliance Logs...") try { Get-ArcHciApplianceLogs -arcHciLogDir $arcHciLogDir -ip $ip -kvaTokenPath $kvaTokenPath } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } New-Item -ItemType Directory -Path "$($arcHciLogDir)\clusterlog\" -Force > $null try { Get-ClusterLog -Destination "$($arcHciLogDir)\clusterlog\" -TimeSpan $clusterLogTimeSpan } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } az.cmd version > "$($arcHciLogDir)\cliversions.txt" Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Compressing Logs...") $zipName = "$arcHciLogDir.zip" Remove-Item $zipName -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue Compress-Directory -ZipFilename $zipName -SourceDir $arcHciLogDir Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue Write-Output "Logs were successfully saved to ""$zipName""" return $zipName } function New-KvaVirtualNetwork { <# .DESCRIPTION Creates a new virtual network usig kvactl .PARAMETER name The name of the vnet .PARAMETER vswitchName The name of the vswitch .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER kubeconfig Path to the appliance kubeconfig #> param ( [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $name, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vswitchName, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vippoolstart, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vippoolend, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $ipaddressprefix, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $gateway, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $dnsservers, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $k8snodeippoolstart, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $k8snodeippoolend, [Parameter(Mandatory=$false, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vlanID, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $kubeconfig ) Write-Warning "The New-KvaVirtualNetwork command will be deprecated in the next release, please shift to New-ArcHciVirtualNetwork command instead" if ($PSCmdlet.ParameterSetName -eq "DHCP") { New-ArcHciVirtualNetwork -name $name -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend } else { New-ArcHciVirtualNetwork -name $name -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend ` -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -k8snodeippoolstart $k8snodeippoolstart ` -k8snodeippoolend $k8snodeippoolend -vlanID $([int]$vlanID) } } function New-ArcHciVirtualNetwork { <# .DESCRIPTION Creates a new virtual network using mocctl .PARAMETER name The name of the vnet .PARAMETER vswitchName The name of the vswitch .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER mocGroup The MOC Group in which the virtual network will be created .PARAMETER mocLocation The MOC Location in which the virtual network will be created #> param ( [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $name, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vswitchName, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vippoolstart, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vippoolend, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $ipaddressprefix, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $gateway, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $dnsservers, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $k8snodeippoolstart, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $k8snodeippoolend, [Parameter(Mandatory=$false, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [Int] $vlanID = 0, [Parameter(Mandatory=$false, ParameterSetName="DHCP")] [Parameter(Mandatory=$false, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $mocGroup = "target-group", [Parameter(Mandatory=$false, ParameterSetName="DHCP")] [Parameter(Mandatory=$false, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $mocLocation = "MocLocation" ) # Get or create the moc location $mocLoc = Get-MocLocation -name $mocLocation -ErrorAction SilentlyContinue if ($null -eq $mocLoc) { $mocLoc = New-MocLocation -name $mocLocation -ErrorAction Stop } # Get or create the moc group $mocGrp = Get-MocGroup -name $mocGroup -location $mocLocation -ErrorAction SilentlyContinue if ($null -eq $mocGrp) { $mocGrp = New-MocGroup -name $mocGroup -location $mocLocation -ErrorAction Stop } try { # Create the moc vnet $dnsserversYaml = "" if(-Not [string]::IsNullOrEmpty($ipaddressprefix)) { foreach ($dns in $dnsservers.Split(",")) { $dns = $dns.Trim(" ") if(-Not [string]::IsNullOrEmpty($dns)) { $dnsserversYaml += "`n - " + $dns } } } # Create the network config file for mocctl $mocvnetyaml = @" name: $name tags: VSwitch-Name: $vswitchName type: Transparent virtualnetworkpropertiesformat: "@ if (-Not [string]::IsNullOrWhiteSpace($dnsserversYaml)) { $mocvnetyaml += @" dhcpoptions: dnsservers: $dnsserversYaml "@ } if (-Not [string]::IsNullOrEmpty($ipaddressprefix)) { $mocvnetyaml += @" subnets: - name: $name-subnet subnetpropertiesformat: addressprefix: $ipaddressprefix ipallocationmethod: Static ippools: - end: $k8snodeippoolend type: vm name: vmpool0 start: $k8snodeippoolstart - end: $vippoolend type: vippool name: vippool0 start: $vippoolstart routetable: routetablepropertiesformat: routes: - routepropertiesformat: addressprefix: 0.0.0.0 nexthopipaddress: $gateway vlan: $vlanID "@ } else { $ipaddressprefix = Get-AddressPrefix -startIP $vippoolstart -endIP $vippoolend $mocvnetyaml += @" subnets: - name: $name-subnet subnetpropertiesformat: addressprefix: $ipaddressprefix ipallocationmethod: Dynamic ippools: - end: $vippoolend type: vippool name: vippool start: $vippoolstart "@ } $yamlFile = Join-Path $(Get-Location).Path "vnet-$name.yaml" Set-Content -Path $yamlFile -Value $mocvnetyaml -Encoding UTF8 -ErrorVariable err # Create the moc virtual network $config = Get-MocConfig $mocctlPath = Join-Path $config.installationPackageDir "mocctl.exe" # Create the moc virtual network Invoke-Expression "$mocctlPath network vnet create --config '$yamlFile' --group $mocGroup --location $mocLocation --cloudFqdn $($config.cloudFqdn)" # Show the moc virtual network Invoke-Expression "$mocctlPath network vnet show --name $name --group $mocGroup --location $mocLocation --cloudFqdn $($config.cloudFqdn) -o yaml" } catch { $errMsg = "Error while creating MOC virtual network: " + $($_.Exception.Message) Write-Error $errMsg } } function Get-AddressPrefix { param ( [string] $startIP, [string] $endIP ) $start = [IPAddress]($startIP) $end = [IPAddress]($endIP) $startbytes = $start.GetAddressBytes() $endbytes = $end.GetAddressBytes() # Check and fix the order of the IP address bytes if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($startbytes) [Array]::Reverse($endbytes) } $startAddress = [BitConverter]::ToUInt32($startbytes, 0) $endAddress = [BitConverter]::ToUInt32($endbytes, 0) $hostPrefixLength = 0 # Bitwise XOR will convert all the bits that match to 0s $ipxor = $startAddress -bxor $endAddress # Keep shifting 1 bit at time till we reach only 0s (That will be the longest length of the host prefix) while (-not ($ipxor -eq 0)) { $ipxor = $ipxor -shr 1 $hostPrefixLength += 1 } # Get the prefix IP by shifting the start IP (or the end IP) by the host prefix length bitwise to right and then left by the same amount $prefixIP = [ipaddress](($startAddress -shr $hostPrefixLength) -shl $hostPrefixLength) $ipBytes = $prefixIP.GetAddressBytes() if ([BitConverter]::IsLittleEndian) { [Array]::Reverse($ipBytes) } $prefixIPString = ([ipaddress][BitConverter]::ToUInt32($ipBytes, 0)).IPAddressToString # IP Prefix length is the the number of 1s followed by 0s $prefixLength = 32 - $hostPrefixLength $addressPrefix = $prefixIPString + "/" + $prefixLength return $addressPrefix } function Get-KubectlExeInternal { <# .DESCRIPTION Downloads kubectl.exe and copies it to the MOC installationPackageDir #> try { $config = Get-MocConfig $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe" # If kubectl.exe is not present, download and copy it to the moc installation directory if (-not (Test-Path $kubectlPath)) { Write-Output "Downloading kubectl.exe to $($config.installationPackageDir)" curl.exe -LO "https://dl.k8s.io/release/stable.txt" $latest_version = Get-Content "stable.txt" $url = "https://dl.k8s.io/release/" + $latest_version + "/bin/windows/amd64/kubectl.exe" curl.exe -LO $url mv .\kubectl.exe $($config.installationPackageDir) } } catch { Write-Error "Error getting kubectl: " + $_.Exception.Message } } function Get-TargetClusterAdminCredentials { <# .DESCRIPTION Gets the kubeconfig of target cluster .PARAMETER outfile Path to the file in which target cluster kubeconfig will be stored .PARAMETER clusterName The name of the target cluster .PARAMETER controlPlaneIP IP Address of the control plane VM of target cluster. Defaults to the IP Address of the VM matching the name .*<clusterName>-control-plane.* .PARAMETER sshPrivateKey Path to the SSH private key file used for the target cluster. Defaults to "$env:USERPROFILE\.ssh\id_rsa" #> param ( [Parameter(Mandatory=$true)] [ValidateScript({ # Check if path exists if($_ | Test-Path){ # Check if it is a file path if($_ | Test-Path -PathType Leaf){ return $true } throw $fileError } # Check if the parent directory path exists if([System.IO.Path]::GetDirectoryName($_) | Test-Path){ return $true } throw $fileFolderPathError })] [String] $outfile, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [String] $clusterName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "IP" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(-not ($_ | Test-Path)){ throw $fileFolderPathError } return $true })] [String] $sshPrivateKey = "$env:USERPROFILE\.ssh\id_rsa" ) try { # Try getting the control plane IP for the target cluster if ([string]::IsNullOrWhiteSpace($controlPlaneIP)) { $vms = @() # Check all nodes in a multi node host cluster $hostClusterNodes = Get-ClusterNode -ErrorAction Ignore if ($hostClusterNodes.Length -gt 1) { ForEach ($node in $hostClusterNodes) { $vms = Get-VM -ComputerName $node.Name | Where-Object { $_.Name -Match ".*$clusterName-control-plane.*" } if ($vms.Length -gt 0) { break } } } else { $vms = Get-VM | Where-Object {$_.Name -Match ".*$clusterName-control-plane.*"} } if ($vms.Length -eq 0) { throw "Unable to find a VM with name matching .*$clusterName-control-plane.*" } $ips = [ipaddress[]]$vms[0].NetworkAdapters.IPAddresses | Where-Object {$_.AddressFamily -eq "InterNetwork"} if ($null -eq $ips) { throw "Unable to find the IP Address for the VM with name: $($vms[0].Name)" } $controlPlaneIP = $ips[0] } Write-Output "Fetching the target cluster kubeconfig from the control plane VM with IPv4 Address: $controlPlaneIP" Invoke-Expression "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $sshPrivateKey clouduser@${controlPlaneIP} 'sudo cat /etc/kubernetes/admin.conf > /home/clouduser/admin.conf'" Invoke-Expression "scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $sshPrivateKey clouduser@${controlPlaneIP}:/home/clouduser/admin.conf $outfile" Invoke-Expression "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $sshPrivateKey clouduser@${controlPlaneIP} 'rm /home/clouduser/admin.conf'" Write-Output "Successfully saved the target cluster kubeconfig to $outfile" } catch { $errMsg = "Error while getting kubeconfig for cluster $clusterName. " + $_.Exception.Message Write-Output $errMsg } } function Add-ArcHciK8sGalleryImage { <# .DESCRIPTION Downaload and adds a kubernetes image to the gallery .PARAMETER k8sVersion Version of kubernetes that the image will use .PARAMETER imageType Type of the image: Linux or Windows .PARAMETER version AksHci/Moc version to get the image download information from the release manifest. Defaults to the installed Moc version #> param ( [Parameter(Mandatory=$true)] [String] $k8sVersion, [Parameter(Mandatory=$false)] [ValidateSet("Windows", "Linux")] [String] $imageType = "Linux", [Parameter(Mandatory=$false)] [String] $version, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) $mocConfig = Get-MocConfig $moduleName = "Moc" Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_provisioning_galleryimage) if ([string]::IsNullOrEmpty($version)) { $version = $mocConfig.version } $imageName = Get-LegacyKubernetesGalleryImageName -imagetype $imageType -k8sVersion $k8sVersion $galleryImage = $null $mocLocation = $mocConfig.cloudLocation # Check if requested k8s gallery image is already present try { $galleryImage = Get-MocGalleryImage -name $imageName -location $mocLocation } catch {} if ($null -ine $galleryImage) { Write-SubStatus -moduleName $moduleName $($GenericLocMessage.comm_image_already_present_in_gallery) Write-Output "$imageType $k8sVersion k8s gallery image already present" return } # Try downloading and adding the requested k8s gallery image try { # Get image download information from the release manifest $imageRelease = Get-ImageReleaseManifest -imageVersion $version -operatingSystem $imageType -k8sVersion $k8sVersion -moduleName $moduleName # Download the k8s gallery image Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_downloading_image, $imageName)) $result = Get-ImageRelease -imageRelease $imageRelease -imageDir $mocConfig.imageDir -moduleName $moduleName -releaseVersion $version # Add the downloaded k8s gallery image to Moc Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_adding_image_to_cloud_gallery, $imageName)) # We need to find a MocStorageContainer that is not isolated $mocContainers = Get-MocContainer -location $mocLocation $storageContainer = $null foreach($container in $mocContainers) { if ($container.properties.isolated -ne $true) { $storageContainer = $container.name break } } # Error out if we can't find a MocContainer that is not isolated if ($null -eq $storageContainer) { errorMsg = ("Unable to find a suitable MocContainer. Please ensure a MocContainer exists and is not isolated.\n" + "To create a new MocContainer, please run 'New-MocContainer -location MocLocation -Name <moc container name> -Path <path to a storage location where to store the images>'.") throw errorMsg } else { New-MocGalleryImage -name $imageName -location $mocLocation -imagePath $result -container $storageContainer } # Remove the downloaded bits Remove-Item -Path $result -Force -ErrorAction Ignore Write-Output "Successfully added $imageType $k8sVersion k8s gallery image" } catch { Write-Output "Error while adding $imageType $k8sVersion k8s gallery image" Write-Output $_ } Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } function Invoke-CommandLine { <# .DESCRIPTION Executes a command and optionally ignores errors. .PARAMETER command Comamnd to execute. .PARAMETER arguments Arguments to pass to the command. .PARAMETER showOutput Optionally, show live output from the executing command. #> param ( [String]$command, [String]$arguments, [Switch]$showOutput ) if ($showOutput.IsPresent) { $result = (& $command $arguments.Split(" ") | Out-Default) 2>&1 } else { $result = (& $command $arguments.Split(" ") 2>&1) } $out = $result | Where-Object {$_.gettype().Name -ine "ErrorRecord"} # On a non-zero exit code, this may contain the error #$outString = ($out | Out-String).ToLowerInvariant() if ($LASTEXITCODE) { $err = $result | Where-Object {$_.gettype().Name -eq "ErrorRecord"} $errMessage = "$command $arguments (Error: $LASTEXITCODE [$err])" throw $errMessage } return $out } function Repair-ArcHciApplianceCerts { <# .DESCRIPTION Attempts to repair failed TLS to cloudagent .PARAMETER sshPrivateKeyFile SSH private key file .PARAMETER force Force repair(without checks) .PARAMETER kubeconfig Path to the kubeconfig file .PARAMETER group Appliance group .PARAMETER clusterName Name of the appliance cluster .PARAMETER arcTokenFilePath File path to the ARC indentity token .PARAMETER kvactlPath Location of the kvactl.exe tool (which may need to be installed separately) #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory=$false)] [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile, [Parameter(Mandatory=$false)] [Switch] $force, [Parameter(Mandatory=$false)] [string] $kubeconfig = $(Join-Path $(Get-ArcHciConfigValue -name "workingDir") "kubeconfig"), [Parameter(Mandatory=$false)] [string] $group = "management", [Parameter(Mandatory=$false)] [string] $clusterName = "Appliance", [Parameter(Mandatory=$false)] [String] $arcTokenFilePath = $(Join-Path $(Get-ArcHciConfigValue -name "workingDir") "kvatoken.tok"), [Parameter(Mandatory=$false)] [String] $kvactlpath = $global:kvaCtlFullPath ) if (-Not (Test-Path $kvactlpath)) { throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair" } $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($kvaIdentity)) $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding) Invoke-CommandLine -command $kvactlpath -arguments $("repair --kubeconfig ""$kubeconfig"" --sshprivatekey ""$sshPrivateKeyFile"" --tags ""Group=$group"" --force=$force --verbose") -showOutput } function Repair-MocOperatorToken { <# .DESCRIPTION Refresh the moc-operator token. This command depends on kubernetes config file with admin permission to be saved as $env:USERPROFILE\.kube\config. #> Repair-Moc $mocConfig = Get-MocConfig $mocoperator = "moc-operator" try { Remove-MocIdentity -name $mocOperator } catch { } $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null $loginString = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($mocOperatorIdentity)) Get-KubectlExeInternal $kubectlPath = Join-Path $mocConfig.installationPackageDir "kubectl.exe" $configStr = "" try { $configStr = Invoke-Expression "$kubectlPath get secret controllerconfig -n moc-operator-system" } catch { Write-Output "Failed to get controllerconfig" } if ($configStr.Length -gt 1 -and $configStr[1].Contains("controllerconfig")) { Invoke-Expression "$kubectlPath patch secret controllerconfig -p '{\""data\"":{\""LoginString\"":\""$loginString\""}}' -n moc-operator-system" } else { $filePath = ".\controllerconfig.yaml" $secretContent = "apiVersion: v1`n" $secretContent += "kind: Secret`n" $secretContent += "metadata:`n" $secretContent += " name: controllerconfig`n" $secretContent += " namespace: moc-operator-system`n" $secretContent += "type: Opaque`n" $secretContent += "data:`n" $secretContent += " LoginString: $loginString`n" $secretContent += "stringData:`n" $secretContent += " CloudFQDN: " + $mocConfig.cloudFqdn + "`n" $secretContent += " Location: " + $mocConfig.cloudLocation + "`n" $secretContent += " ContainerName: MocStorageContainer`n" $secretContent | Out-File -FilePath $filePath if (Test-Path $filePath) { Invoke-Expression "$kubectlPath apply -f $filePath" Remove-Item -Path $filePath } } Invoke-Expression "$kubectlPath delete pods -n moc-operator-system -l control-plane=controller-manager" } function Repair-ArcHciVmInfra { <# .DESCRIPTION Rotates the security certificates and tokens in MOC and ARC resource bridge .PARAMETER workDirectory TBD #> param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"), [Parameter()] [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile, [Parameter()] [Switch] $force, [Parameter()] [string] $kubeconfig, [Parameter()] [string] $group, [Parameter()] [string] $clusterName, [Parameter(Mandatory=$false)] [String] $arcTokenFilePath, [Parameter(Mandatory=$false)] [String] $kvactlpath = $global:kvaCtlFullPath ) if (-Not (Test-Path $kvactlpath)) { throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair" } Repair-MocOperatorToken Repair-ArcHciApplianceCerts -sshprivatekey $sshPrivateKeyFile -force:$force.IsPresent -kubeconfig $kubeconfig -group "management" -clusterName "Appliance" -arcTokenFilePath $($workDirectory + "\kvatoken.tok") -kvactlpath $kvactlpath } function Test-HCIMachineRequirements { <# .DESCRIPTION Checks 1. The substrate is Azure Stack HCI 2. HCI node is registered #> Import-Module -Name "AzureStackHCI" $hciStatus = Get-AzureStackHCI # Check if the HCI machine is registered; if not, throw error if ($hciStatus.RegistrationStatus -ine "Registered") { throw "The HCI machine ($env:computername) is not registered. Please register your HCI cluster." } } function Test-IPV4Address { <# .DESCRIPTION Checks 1. Whether it is valid IPV4 ip .PARAMETER ip Ip address .OUTPUTS Boolean value true/false .EXAMPLE Test-IPV4Address -ip $ip #> param( [parameter(Mandatory = $true)] [string] $ip ) $ipv4 = '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' if($ip -notmatch $ipv4){ return $false } return $true } function Get-Nodes { <# .DESCRIPTION Returns the nodes of the cluster or the local host in case of standalone. #> # Check if failover cluster powershell module was installed and the cluster was deployed # and only run Get-ClusterNode in that case if ((Get-Command "Get-ClusterNode" -errorAction SilentlyContinue) -and $null -ne (Get-Cluster -errorAction SilentlyContinue)) { return (Get-ClusterNode -ErrorAction SilentlyContinue).Name } return $env:computername } function Remove-CertFiles { <# .DESCRIPTION Removes the certificate file on all the nodes #> $mocConfig = Get-MocConfig $workingDir = $mocConfig.workingDir rmdir $workingDir\cloudCfg\python -Recurse -Force -ErrorAction SilentlyContinue Get-Nodes | ForEach-Object { Invoke-Command -ComputerName $_ -ScriptBlock { rmdir $env:USERPROFILE\.wssd\python -Recurse -Force -ErrorAction SilentlyContinue } } } function Test-K8snodeIPPoolRange { <# .DESCRIPTION Validates k8sIPPool range for archci .PARAMETER $k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER $k8snodeippoolend The ending ip address to use for VM's in the cluster. #> param ( [parameter(Mandatory = $true)] [string] $k8snodeippoolstart, [parameter(Mandatory = $true)] [string] $k8snodeippoolend ) $to_bytes = [IPAddress]::Parse($k8snodeippoolstart).GetAddressBytes() [Array]::Reverse($to_bytes) $start_ip = [BitConverter]::ToUInt32($to_bytes, 0) $to_bytes = [IPAddress]::Parse($k8snodeippoolend).GetAddressBytes() [Array]::Reverse($to_bytes) $end_ip = [BitConverter]::ToUInt32($to_bytes, 0) if($end_ip -le $start_ip) { return $false } return $true } function Test-rbIpRange { <# .DESCRIPTION Validates k8sIPPool range for archci .PARAMETER $rbIpStart The starting ip address to use for VM's in the cluster. .PARAMETER $rbIpEnd The ending ip address to use for VM's in the cluster. #> param ( [parameter(Mandatory = $true)] [string] $rbIpStart, [parameter(Mandatory = $true)] [string] $rbIpEnd ) $to_bytes = [IPAddress]::Parse($rbIpStart).GetAddressBytes() [Array]::Reverse($to_bytes) $start_ip = [BitConverter]::ToUInt32($to_bytes, 0) $to_bytes = [IPAddress]::Parse($rbIpEnd).GetAddressBytes() [Array]::Reverse($to_bytes) $end_ip = [BitConverter]::ToUInt32($to_bytes, 0) if($start_ip -ge $end_ip ) { return $false } return $true } function Write-Log { <# .DESCRIPTION Write logs to the log file .PARAMETER LogString Log string .OUTPUTS N/A .EXAMPLE Write-Log -LogString $LogString #> Param ( [parameter(Mandatory = $true)] [object] $LogString ) if([String]::IsNullOrEmpty($logPath)){ $logPath = Get-ArcHciDefaultPath $logFile = Join-Path $logPath "ubercrud.log" } $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss") $Hostname = $($env:computername) $LogMessage = "$Stamp [$Hostname] $LogString" $LogMessage >> $logFile } function Retry() { <# .DESCRIPTION Reruns a function maxAttempts times with a delay of retryDelaySeconds. .PARAMETER $operation Operation to run .PARAMETER $maxAttempts Max number of retries .PARAMETER $retryDelaySeconds Time in seconds to wait after failed attempt .PARAMETER $forceRetry Force retry even if function doesn't fail .PARAMETER $ignoreFailure Don't throw on error .OUTPUTS N/A .EXAMPLE Retry {Install-ArcHciPrerequisites -subscriptionID $subscriptionID} -maxAttempts 3 -retryDelaySeconds 5 #> param( [Parameter(Mandatory=$true)][scriptblock]$operation, [Parameter(Mandatory=$false)][int]$maxAttempts = 3, [Parameter(Mandatory=$false)][int]$retryDelaySeconds = 5, [Parameter(Mandatory=$false)][switch]$forceRetry, [Parameter(Mandatory=$false)][switch]$ignoreFailure ) $attempts = 1 $ErrorActionPreferenceToRestore = $ErrorActionPreference $ErrorActionPreference = "Stop" do { try { $result = & $operation; if (!($forceRetry.IsPresent)) { break; } } catch [Exception] { Write-Log $_.Exception.Message } if ($attempts -le $maxAttempts) { Write-Log "[Warning] Action $operation failed. Waiting $retryDelaySeconds seconds before attempt $attempts of $maxAttempts." Write-Warning "Action $operation failed. Waiting $retryDelaySeconds seconds before attempt $attempts of $maxAttempts." $attempts++ Start-Sleep $retryDelaySeconds } else { $ErrorActionPreference = $ErrorActionPreferenceToRestore if ($ignoreFailure.IsPresent) { Write-Log "Max retry attempts of $maxAttempts reached. Ignoring error since ignoreFailure parameter is passed." Write-Log "[Warning] $($_.Exception.Message)" } else { Write-Log $_ Write-Error $_.Exception.Message } } } while ($attempts -le $maxAttempts) $ErrorActionPreference = $ErrorActionPreferenceToRestore return $result } function Install-ArcHciPrerequisites { <# .DESCRIPTION Install ArcHci pre-requisites. .PARAMETER subscriptionID Subscription ID .OUTPUTS N/A .EXAMPLE Install-ArcHciPrerequisites -subscriptionID $subscriptionID #> Param( [parameter(Mandatory = $true)] [GUID] $subscriptionID ) Write-Output "Installing ArcHci Pre-requisites...." Write-Log "Installing ArcHci Pre-requisites...." Invoke-ArcHciAzCommand "config set extension.use_dynamic_install=yes_without_prompt" -ignoreWarning Retry -operation {Register-ResourceProviders -subscriptionID $subscriptionID 2>&1} Write-Output "ArcHci Pre-requisites Installed Successfully" Write-Log "ArcHci Pre-requisites Installed Successfully" } function Register-ResourceProviders { <# .DESCRIPTION Onboard resource providers to the subscription .PARAMETER subscriptionID Subscription ID .OUTPUTS N/A .EXAMPLE Register-ResourceProviders -subscriptionID $subscriptionID #> # Set the default subscription Invoke-ArcHciAzCommand "account set -s $subscriptionID" Write-Verbose "Registering Resource Providers...." Write-Log "Registering Resource Providers...." Invoke-ArcHciAzCommand "provider register --namespace Microsoft.Kubernetes --wait" -logOutput Invoke-ArcHciAzCommand "provider register --namespace Microsoft.KubernetesConfiguration --wait" -logOutput Invoke-ArcHciAzCommand "provider register --namespace Microsoft.ExtendedLocation --wait" -logOutput Invoke-ArcHciAzCommand "provider register --namespace Microsoft.ResourceConnector --wait" -logOutput Invoke-ArcHciAzCommand "provider register --namespace Microsoft.AzureStackHCI --wait" -logOutput Invoke-ArcHciAzCommand "provider register --namespace Microsoft.HybridConnectivity --wait" -logOutput Invoke-ArcHciAzCommand "provider register --namespace Microsoft.HybridContainerService --wait" -logOutput Write-Verbose "Resource Providers registered successfully if not were already registered!" Write-Log "Resource Providers registered successfully if not were already registered!" } function Install-ArcHciMoc { <# .DESCRIPTION Install ArcHciMoc. .PARAMETER volumePath Optional parameter. Volume Path .PARAMETER cloudserviceIP Optional parameter. Cloud agent IP .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER workingDirectory Optional parameter. Working Directory Path. This will be deprecated. Please use workingDir instead .PARAMETER imageDir Optional parameter. Image Directory Path .PARAMETER isolateImageDir Optional parameter. Flag to isolate image dir .PARAMETER cloudConfigLocation Optional parameter. Cloud Config Location Path .PARAMETER version Optional parameter. Moc version .PARAMETER catalog Optional parameter. Catalog .PARAMETER ring Optional parameter. Ring .PARAMETER skip_prechecks Optional parameter. Flag to skip prechecks .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Install-ArcHciMoc -volumePath $volumePath -cloudserviceIP $cloudserviceIP -workingDir $workingDir -imageDir $imageDir -isolateImageDir $isolateImageDir -cloudConfigLocation $cloudConfigLocation -version $version -catalog $catalog -ring $ring -skip_prechecks -useStagingShare -stagingShare $stagingShare #> Param( [parameter(Mandatory = $false)] [ValidateScript({ if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $volumePath, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "cloudserviceIP" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $cloudserviceIP, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, # This should be deprecated. Please use $workingDir instead [parameter(DontShow)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDirectory, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $imageDirectory, [parameter(Mandatory = $false)] [switch] $isolateImageDir, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $cloudConfigLocation, [parameter(Mandatory = $false)] [string] $version, [parameter(Mandatory = $false)] [string] $catalog = "aks-hci-stable-catalogs-ext", [parameter(Mandatory = $false)] [string] $ring = "stable", [Parameter(Mandatory = $false)] [switch] $skip_prechecks, [Parameter(Mandatory = $false)] [switch] $useStagingShare, [parameter(Mandatory = $false)] [String] $stagingShare, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) $global:ProgressPreference = 'SilentlyContinue' Write-Log "Entered Install-ArcHciMoc" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciMoc" -version $moduleVersion try { Write-Log "Running Get-ArcHciMoc" $mocConfig = Get-ArcHciMoc -correlationId $correlationId # Convert the hashtable to a table format and then to a string $mocConfigString = $mocConfig | Format-Table -AutoSize | Out-String Write-Log "Mocconfig: $mocConfigString" if ($mocConfig.installState -eq 'NotInstalled') { if ([String]::IsNullOrEmpty($volumePath)) { $volumePath = Get-ArcHciDefaultPath } if (-not $skip_prechecks.IsPresent) { Write-Log "Running Test-ArcHciEnableMoc -volumePath $volumePath -cloudServiceIP $cloudserviceIP" $mocTestRes = Test-ArcHciEnableMoc -volumePath $volumePath -cloudServiceIP $cloudserviceIP -correlationId $correlationId Write-Log "Moc test results: $mocTestRes" } else { Write-Log "Skipping Moc prechecks" Write-Output "Skipping Moc prechecks" } if ($skip_prechecks.IsPresent -or $mocTestRes[$mocTestRes.length - 1].TestResult -eq "Succeeded") { Write-Output "Installing Moc...." Write-Log "Installing Moc...." if ([String]::IsNullOrEmpty($workingDir)) { # As a temp work around, set workingDir to workingDirectory value, if workingDir is empty and workingDirectory is set if (-not ([String]::IsNullOrEmpty($workingDirectory))) { $workingDir = $workingDirectory } Write-Log "Running Join-Path -Path $volumePath -ChildPath 'WorkingDirectory'" New-Item -Type Directory -Force -Path (Join-Path -Path $volumePath -ChildPath "WorkingDirectory") $workingDir = Join-Path -Path $volumePath -ChildPath "WorkingDirectory" } if ([String]::IsNullOrEmpty($imageDir)) { Write-Log "Running Join-Path -Path $workingDir -ChildPath 'ImageStore'" New-Item -Type Directory -Force -Path (Join-Path -Path $workingDir -ChildPath "ImageStore") $imageDir = (Join-Path -Path $workingDir -ChildPath "ImageStore") } if ([String]::IsNullOrEmpty($cloudConfigLocation)) { Write-Log "Running Join-Path -Path $workingDir -ChildPath 'CloudStore'" New-Item -Type Directory -Force -Path (Join-Path -Path $workingDir -ChildPath "CloudStore") $cloudConfigLoc = (Join-Path -Path $workingDir -ChildPath "CloudStore") } try { if ($useStagingShare.IsPresent) { Write-Log "Running Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false -useStagingShare -stagingShare $stagingShare" Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false -useStagingShare -stagingShare $stagingShare } else { Write-Log "Running Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false" Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false } Write-Log "Running Install-Moc" Install-Moc } catch { Write-Log "Correlation ID: $correlationId. $_" Write-Log "Uninstalling Moc..." Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocfailed" -message "Install MOC Failed - Exception: $_" Uninstall-Moc throw "Correlation ID: $correlationId. $_" } Write-Log "Running Get-ArcHciMoc" $mocConfig = Get-ArcHciMoc -correlationId $correlationId # Convert the hashtable to a table format and then to a string $mocConfigString = $mocConfig | Format-Table -AutoSize | Out-String Write-Log "Mocconfig: $mocConfigString" if ($mocConfig.installState -eq 'Installed') { Write-Output "Moc version $($mocConfig.version) installed successfully!" Write-Log "Moc version $($mocConfig.version) installed successfully" } else { Write-Output "Correlation ID: $correlationId. Moc install state: $($mocConfig.installState)" Write-Log "Correlation ID: $correlationId. Moc install state: $($mocConfig.installState)" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocfailed" -message "Install MOC Failed - Moc install state: $($mocConfig.installState)" } } else { Write-Log "Correlation ID: $correlationId. Terminating Moc Installation. $($mocTestRes[$mocTestRes.length - 1].Details)" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Install MOC Precheck Failed: $($mocTestRes[$mocTestRes.length - 1].Details)" throw "Correlation ID: $correlationId. $($mocTestRes[$mocTestRes.length - 1].Details)" } } else { Write-Log "Moc is already installed!" Write-Output "Moc is already installed!" Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig return $mocConfig } Write-Log "Exiting Install-ArcHciMoc" $global:ProgressPreference = 'Continue' } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } } function Invoke-ArcHciAzCommand { <# .DESCRIPTION Executes an az cli command. .PARAMETER arguments Arguments to pass to az cli. .PARAMETER ignoreError Optionally, ignore errors from the command (don't throw). .PARAMETER argumentsToFormat Specific arguments to format with quotes. .OUTPUTS N/A .EXAMPLE Invoke-ArcHciAzCommand -arguments "provider show --namespace 'Microsoft.Resources'" #> param ( [Parameter(Mandatory = $true)] [String]$arguments, [Parameter(Mandatory = $false)] [Switch]$ignoreError, [Parameter(Mandatory = $false)] [Switch]$ignoreWarning, [Parameter(Mandatory = $false)] [Switch]$logOutput, [Parameter(Mandatory = $false)] [String[]]$argumentsToFormat = @('-n','--name','-g', '--resource-group', '--cluster-name', '-l', '--location') ) $azCliFullPath = (Get-Command "az.cmd").Source if (-not $azCliFullPath) { throw $("Unable to find the `"az.cmd`" command. Please install azure cli client and then signoff from the machine and sign-in again") } if($ignoreWarning){ $arguments = $arguments + " --only-show-errors" } Write-Log "Running command [[""$azCliFullPath"" $arguments]] ..." -InformationAction Continue $response = Invoke-ArcHciAzCommandLine -Command $azCliFullPath -Arguments $arguments -ignoreError:$ignoreError -logOutput:$logOutput -ArgumentsToFormat $argumentsToFormat return $response } function FormatArgumentsWithQuotes { <# .DESCRIPTION Appends quotes around select arguments so parameters can be formatted properly when we run the az command. If there is a space for example and the argument does not have quotes around it, it will fail so we need to append quotes. .PARAMETER arguments Arguments to pass to az cli. .PARAMETER argumentsToFormat Arguments to add quotes to .OUTPUTS N/A .EXAMPLE FormatArgumentsWithQuotes -arguments 'group show -n "sample group"', -argumentsToFormat '-n' #> param ( [Parameter(Mandatory = $true)] [String]$arguments, [Parameter(Mandatory = $true)] [String[]]$argumentsToFormat ) foreach ($arg in $argumentsToFormat) { if ($arguments -match "($arg) `"(.+?)`"") { # The argument already has surrounding quotes so we can skip it continue } elseif ($arguments -match "($arg) (.+?) (?=-)") { # Case where the argument is any argument except the last argument in the argument list (meaning it is followed by a - symbol) # So for example if the arguments string looks like "-n custom location -l samplelocation" and the argumentsToFormat are "-n" the returned string will be "-n "custom location" -l samplelocation". $arguments = $arguments -replace "($arg) (.+?) (?=-)", '$1 "$2" ' } else { # Case where the argument is the last argument in the argument list so there is no - after the parameter. # If the argumentToFormat is the last argument of the string, place quotes around everything following it. # So for example if the arguments string looks like "-n customlocation -l sample location" and the argumentsToFormat are "-l" the returned string will be "-n customlocation -l "sample location"" since -l is the last parameter in the string. $arguments = $arguments -replace "($arg) (.+)$", '$1 "$2"' } } return $arguments } function Invoke-ArcHciAzCommandLine { <# .DESCRIPTION Executes a command and optionally ignores errors. .PARAMETER command Command to execute. .PARAMETER arguments Arguments to pass to the command. .PARAMETER ignoreError Optionally, ignore errors from the command (don't throw). .PARAMETER logOutput Optionally, log live output from the executing command. .PARAMETER showOutputAsProgress Optionally, show output from the executing command as progress bar updates. .PARAMETER progressActivity The activity name to display when showOutputAsProgress was requested. .PARAMETER argumentsToFormat Specific arguments to format with quotes. #> param ( [String]$command, [String]$arguments, [Switch]$ignoreError, [Switch]$logOutput, [Switch]$showOutputAsProgress, [String]$progressActivity, [String[]]$argumentsToFormat ) $previousErrorAction = $errorActionPreference $errorActionPreference = "Continue" $arguments = FormatArgumentsWithQuotes -arguments $arguments -argumentsToFormat $argumentsToFormat Write-Log "Reformatting command as [[""$azCliFullPath"" $arguments]]" -InformationAction Continue # Splits on spaces unless the space is between quotes and hence is part of an argument like resource-group. $argumentArray = $arguments -split ' (?=(?:[^\"]|\"[^\"]*\")*$)' try { if ($showOutputAsProgress.IsPresent) { $errorResult = $($result = (& $command $argumentArray | ForEach-Object { $status = $_ -replace "`t", " - " })) 2>&1 } elseif ($logOutput.IsPresent) { $errorResult = $($result = (& $command $argumentArray | ForEach-Object { Write-Log "$command $arguments $_"; return $_ })) 2>&1 } else { $errorResult = $($result = (& $command $argumentArray)) 2>&1 } $previousExitCode = $LASTEXITCODE } catch { if ($ignoreError.IsPresent) { return } Write-Log $_ throw } finally { $errorActionPreference = $previousErrorAction } $allErrorRecords = $errorResult | Where-Object { $_.gettype().Name -eq "ErrorRecord" } # Ignore known warnings that are returned as errors $errorRecords = $allErrorRecords | Where-Object { $_.Exception.Message -notmatch "Please let us know how we are doing" ` -and $_.Exception.Message -notmatch "The installed extension '.+' is experimental" ` -and $_.Exception.Message -notmatch "The installed extension '.+' is in preview." ` -and $_.Exception.Message -notmatch "Setting GA feature gate arcmonitoring=true" ` -and $_.Exception.Message -notmatch "Command group '.+' is in preview and under development." ` -and $_.Exception.Message -notmatch "UserWarning: You are using cryptography on a 32-bit Python on a 64-bit Windows Operating System. Cryptography will be significantly faster if you switch to using a 64-bit Python." } if ($null -ne $errorRecords) { $stack = Get-PSCallStack # An error message was returned, just throw that message $errMessage = "$command $arguments returned a non empty error stream [$errorRecords] at [$($stack)]" throw $errMessage } if ($null -ne $result) { $out = $result | Where-Object { $_.gettype().Name -ine "ErrorRecord" } # On a non-zero exit code, this may contain the error } if ($previousExitCode) { if ($null -ne $result) { $err = $result | Where-Object { $_.gettype().Name -eq "ErrorRecord" } } $errMessage = "$command $arguments returned a non zero exit code $previousExitCode [$err]" if ($ignoreError.IsPresent) { $ignoreMessage = "[IGNORED ERROR] $errMessage" return $ignoreMessage } throw $errMessage } return $out } function New-ArcHciApplianceConfigs { <# .DESCRIPTION Creates the Arc HCI config files .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER cloudName The name of the cloud. .PARAMETER armEndpoint The ARM endpoint of the cloud. .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER rbIpStart The starting ip address to use for Arc RB. .PARAMETER rbIpEnd Optional parameter. The ending ip address to use for Arc RB. .PARAMETER gateway Optional parameter. The gateway to use when using static IP .PARAMETER ipaddressprefix Optional parameter. The address prefix to use for static IP assignment .PARAMETER dnsservers Optional parameter. The dnsservers to use when using static IP .PARAMETER vlanID Optional parameter. The VLAN ID for the vnet .PARAMETER arcHciProxyConfig Optional parameter. Proxy Config .PARAMETER aksExtensionConfigFileName Optional parameter. Name of the AKS extension config file (optional parameter. Defaults to "aks-extension-config.json") .PARAMETER aksExtProxyConfig Optional parameter. Proxy Config for AKS Extension .OUTPUTS N/A .EXAMPLE New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $cloudName -armEndpoint $armEndpoint -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -aksExtProxyConfig $aksExtProxyConfig #> Param( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [string] $location, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -in $cloudNames) { return $true } throw "Cloud name '$_' is invalid. Please try one of the following: {1} $($cloudNames -join ', ')" })] [string] $cloudName, [parameter(Mandatory = $true)] [ValidateScript({ if($_ -notmatch $regexPatternHttpsUrl){ $parameter = "armEndpoint" throw $regexPatternHttpsUrlError -f $parameter,$_ } return $true })] [string] $armEndpoint, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "azstackhciImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "azstackhciVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "mocImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $mocImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "mocVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $mocVersion, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "vnetName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $vnetName, # vswitchName can accept any characters [parameter(Mandatory = $true)] [string] $vswitchName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolstart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolend, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [Parameter(Mandatory=$true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "ControlPlaneIP" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpStart, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpEnd, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $gateway, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternCIDRFormat) { $parameter = "ipaddressprefix" throw $regexPatternCIDRFormatError -f $_, $parameter } return $true })] [string] $ipAddressPrefix, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) { return $true } foreach ($i in $_) { $response = Test-IPV4Address -ip $i if (!$response) { $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter, $i } } return $true })] [string[]] $dnsServers = @(), [Parameter(Mandatory = $false)] [ValidateRange(0, 4094)] [int] $vlanID = 0, [Parameter(Mandatory = $false)] $arcHciProxyConfig, [Parameter(Mandatory=$false)] [String] $aksExtensionConfigFileName = "aks-extension-config.json", [Parameter(Mandatory = $false)] $aksExtProxyConfig ) Write-Log "Entered New-ArcHciApplianceConfigs" if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $mocConfig.workingDir } $workingDir = New-Item -Path $workingDir -Name "Appliance" -ItemType Directory -Force if ($null -ne $arcHciProxyConfig -and $arcHciProxyConfig.count -gt 0) { $proxyServerHTTP = $arcHciProxyConfig.proxyServerHTTP $proxyServerHTTPS = $arcHciProxyConfig.proxyServerHTTPS $proxyServerNoProxy = $arcHciProxyConfig.proxyServerNoProxy $certificateFilePath = $arcHciProxyConfig.certificateFilePath } Write-Log "Running Get-ArcHciParameters -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd" $arcHciParameters = Get-ArcHciParameters -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd $k8snodeippoolstart = $arcHciParameters.k8sNodeIpPoolStart $k8snodeippoolend = $arcHciParameters.k8sNodeIpPoolEnd Write-Log "Generating the config files..." Write-Log "Running New-ArcHciConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $cloudName -armEndpoint $armEndpoint -workDirectory $workingDir -controlPlaneIP $controlPlaneIP -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -vLanID $vlanID -proxyServerHTTP <obfuscated> -proxyServerHTTPS <obfuscated> -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -aksExtensionConfigFileName $aksExtensionConfigFileName -aksExtProxyConfig $aksExtProxyConfig" New-ArcHciConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $cloudName -armEndpoint $armEndpoint -workDirectory $workingDir -controlPlaneIP $controlPlaneIP -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -k8sNodeIpPoolStart $k8sNodeIpPoolStart -k8sNodeIpPoolEnd $k8sNodeIpPoolEnd -gateway $gateway -dnsServers $dnsServers -ipAddressPrefix $ipAddressPrefix -vLanID $vlanID -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -aksExtensionConfigFileName $aksExtensionConfigFileName -aksExtProxyConfig $aksExtProxyConfig Write-Log "Generated hci-appliance.yaml:" Write-Log "-----------------------------------" $content = Get-Content "$workingDir\hci-appliance.yaml" Write-Log $content Write-Log "-----------------------------------" Write-Log "Generated hci-infra.yaml:" Write-Log "-----------------------------------" $content = Get-Content "$workingDir\hci-infra.yaml" Write-Log $content Write-Log "-----------------------------------" Write-Log "Generated hci-resource.yaml:" Write-Log "-----------------------------------" $content = Get-Content "$workingDir\hci-resource.yaml" Write-Log $content Write-Log "-----------------------------------" Write-Log "Generated kvatoken.tok:" Write-Log "-----------------------------------" $content = Get-ChildItem -Path "$workingDir\kvatoken.tok" -File | Where-Object { $_.Name -eq "kvatoken.tok" } | Select-Object PSChildName, CreationTime, Length Write-Log $content Write-Log "-----------------------------------" Write-Log "Generated $aksExtensionConfigFileName" Write-Log "-----------------------------------" $content = Get-Content "$workingDir\$aksExtensionConfigFileName" Write-Log $content Write-Log "-----------------------------------" Write-Log "Exiting New-ArcHciApplianceConfigs" } function Install-ArcHciResourceBridge { <# .DESCRIPTION Installs an Arc Resource Bridge .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Name of the Azure resource .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId #> Param( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Install-ArcHciResourceBridge" Write-Output "Installing Arc Resource Bridge...." Write-Log "Installing Arc Resource Bridge...." if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $mocConfig.workingDir } $workingDir = "$workingDir\Appliance" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciResourceBridge" -version $moduleVersion try { Write-Log "Running az arcappliance prepare hci --config-file ""$workingDir\hci-appliance.yaml""" Invoke-ArcHciAzCommand "arcappliance prepare hci --config-file ""$workingDir\hci-appliance.yaml""" -ignoreWarning -logOutput Write-Log "Running az arcappliance deploy hci --config-file ""$workingDir\hci-appliance.yaml"" --outfile ""$workingDir\kubeconfig""" Write-Output "Arc resource bridge deployment can take 20 minutes or more" Invoke-ArcHciAzCommand "arcappliance deploy hci --config-file ""$workingDir\hci-appliance.yaml"" --outfile ""$workingDir\kubeconfig""" -ignoreWarning -logOutput Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir Write-Output "Arc Resource Bridge Installed Successfully" Write-Log "Arc Resource Bridge Installed Successfully" Write-Log "Setting ARB Install State to Succeeded" Set-ArcHciConfigValue -name "arbInstallState" -Value "Succeeded" Write-Log "Exiting Install-ArcHciResourceBridge" } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installarbfailed" -message "Failed to install arc resource bridge - Exception: $_" Write-Log "Setting ARB Install State to Failed" Set-ArcHciConfigValue -name "arbInstallState" -Value "Failed" throw "Correlation ID: $correlationId. $_" } finally { # always attempt to cleanup the ARB installation artifacts (i.e. ARB SSH logkey) from each cluster node and the local machine # regardless of whether the ARB installation succeeded or failed. this is to ensure unneeded artifacts are removed. Write-Log "Cleaning up unnecessary Arc Resource Bridge installation artifacts from each cluster node after installation" try { $path = "C:\ProgramData\kva\.ssh\logkey" if (Test-File $path) { Remove-Item -Path $path -Force } $path = "C:\ProgramData\kva\.ssh\logkey.pub" if (Test-File $path) { Remove-Item -Path $path -Force } Get-Nodes | ForEach-Object { Write-Log "Cleaning up unnecessary Arc Resource Bridge installation artifacts on node $_" Invoke-Command -ComputerName $_ -ScriptBlock { $path = "C:\ProgramData\kva\.ssh\logkey" if (Test-File $path) { Remove-Item -Path $path -Force } $path = "C:\ProgramData\kva\.ssh\logkey.pub" if (Test-File $path) { Remove-Item -Path $path -Force } } } } catch { Write-Log "Encountered error while cleaning up unnecessary Arc Resource Bridge installation artifacts after installation. Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installarbfailed" -message "Warning: failed to clean up unnecessary Arc Resource Bridge installation artifacts after installation - Exception: $_" } Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } } function Remove-ArcHciResourceBridge { <# .DESCRIPTION Remove Arc Resource Bridge .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Remove-ArcHciResourceBridge" Write-Output "Removing Arc Resource Bridge...." Write-Log "Removing Arc Resource Bridge...." if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $mocConfig.workingDir } $workingDir = "$workingDir\Appliance" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Remove-ArcHciResourceBridge" -version $moduleVersion try { Write-Log "Running az arcappliance delete hci --config-file ""$workingDir\hci-appliance.yaml"" --yes" Invoke-ArcHciAzCommand "arcappliance delete hci --config-file ""$workingDir\hci-appliance.yaml"" --yes" -ignoreWarning -logOutput Write-Log "Setting ARB Install State to NotInstalled" Set-ArcHciConfigValue -name "arbInstallState" -Value "NotInstalled" $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcRBDetails = "" while ($timeSinceStart -lt $timeout) { Write-Log "Waiting for ""az arcappliance delete hci --config-file $workingDir\hci-appliance.yaml --yes"" to finish..." Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" if ($arcRBDetails.provisioningState -eq "NotInstalled") { Write-Output "Arc Resource Bridge Removed Successfully" Write-Log "Arc Resource Bridge Removed Successfully" Write-Log "Exiting Remove-ArcHciResourceBridge" return } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeout" } throw ("Timed out deleting appliance. Delete state: $($arcRBDetails.status)") } catch { Write-Log "Correlation ID: $correlationId. $_" if ($_.Exception -notmatch "notfound|not found") { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletearbfailed" -message "Failed to delete arc resource bridge - Exception: $_" throw "Correlation ID: $correlationId. $_" } } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } } function Wait-ArcHciResourceBridge { <# .DESCRIPTION Wait For Arc Resource Bridge deployment to finish .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .OUTPUTS N/A .EXAMPLE Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir ) if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $mocConfig.workingDir $workingDir = "$workingDir\Appliance" } $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcRBDetails = "" while ($timeSinceStart -lt $timeoutDeployARB) { Write-Log "Waiting for ""az arcappliance deploy hci --config-file $workingDir\hci-appliance.yaml --outfile $workingDir\kubeconfig"" to finish..." Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" if ($arcRBDetails.status -eq "Running") { Write-Log "Deployed appliance successfully" return } if ($arcRBDetails.status -eq "Failed") { throw "Failed to deploy appliance" } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeoutDeployARB" } throw ("Timed out deploying appliance. Deploy state: $($arcRBDetails.status)") } function Install-ArcHciVMExtension { <# .DESCRIPTION Install VM Extension .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER ssvmExtensionName Optional parameter. SSVM Extension Name .PARAMETER ssvmExtVersion Optional parameter. SSVM Extension Version .PARAMETER releaseTrain Optional parameter. Release Train .PARAMETER shareDiagnosticData Optional parameter. Whether to send diagnostic data to Microsoft. Default: "true" Diagnostic data is used to help keep the service secure and up to date, troubleshoot problems, and make product improvements. .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $releaseTrain -workingDir $workingDir -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $ssvmExtensionName = "vmss-hci", [parameter(Mandatory = $false)] [string] $ssvmExtVersion, [parameter(Mandatory = $false)] [string] $releaseTrain = "stable", [parameter(Mandatory = $false)] [ValidateSet("true", "false")] [string] $shareDiagnosticData = "true", [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Install-ArcHciVMExtension" if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $($mocConfig.workingDir) } Set-ArcHciConfigValue -name "vmExtensionName" -Value $ssvmExtensionName $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciVMExtension" -version $moduleVersion Write-Log "Checking whether Arc resource bridge status is running before installing SSVM extension" Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "Installing SSVM k8s extension" Write-Output "Installing SSVM k8s extension" try { $K8SExtensionCreateCmd = "k8s-extension create --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --extension-type Microsoft.AZStackHCI.Operator --scope cluster --release-namespace helm-operator2 --configuration-settings shareDiagnosticData=$shareDiagnosticData Microsoft.CustomLocation.ServiceAccount=$defaultNamespace AgentOperationTimeoutInMinutes=$timeoutForExtensionCreateInMinutes --config-protected-file ""$workingDir\Appliance\hci-config.json"" --release-train $releaseTrain --auto-upgrade $false" if (-Not [String]::IsNullOrEmpty($ssvmExtVersion)) { $K8SExtensionCreateCmd += " --version $ssvmExtVersion" } if (Test-IsHCIMachine) { $hciClusterId = (Get-AzureStackHci).AzureResourceUri $K8SExtensionCreateCmd += " --configuration-settings HCIClusterID=$hciClusterId" } Write-Log "Running az $K8SExtensionCreateCmd" Invoke-ArcHciAzCommand $K8SExtensionCreateCmd -ignoreWarning $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcVMExtDetails = "" while (($timeSinceStart -lt $timeoutForExtensionCreate)) { Write-Log "Waiting for $K8SExtensionCreateCmd to finish..." Write-Log "Running Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" $arcVMExtDetails = Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName Write-Log "Arc VM extension details: $arcVMExtDetails" if ($arcVMExtDetails.provisioningState -eq "Succeeded") { Write-Log "SSVM k8s extension successfully installed" Write-Output "SSVM k8s extension successfully installed" Write-Log "Exiting Install-ArcHciVMExtension" return } if ($arcVMExtDetails.provisioningState -eq "Failed") { throw "Failed to install SSVM k8s extension" } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeoutForExtensionCreate " } throw ("Timed out installing k8s-extension. Installation state: $($arcVMExtDetails.provisioningState)") } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installvmextfailed" -message "Failed to install ArcVM extension - Exception: $_" throw "Correlation ID: $correlationId. $_" } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } } function Uninstall-ArcHciVMExtension { <# .DESCRIPTION Remove VM Extension .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER ssvmExtensionName Optional parameter. SSVM Extension Name .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName"), [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Uninstall-ArcHciVMExtension" Write-Log "Deleting ArcHci VM Extension" Write-Output "Deleting ArcHci VM Extension" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciVMExtension" -version $moduleVersion try { Write-Log "Running az k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --yes" Invoke-ArcHciAzCommand "k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --yes" -logOutput $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcVMExtDetails = "" while ($timeSinceStart -lt $timeout) { Write-Log "Waiting for ""k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --yes"" to finish..." Write-Log "Running Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" $arcVMExtDetails = Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName Write-Log "Arc VM extension details: $arcVMExtDetails" if ($arcVMExtDetails.provisioningState -eq "NotInstalled") { Write-Output "ArcHci VM Extension Deleted Successfully" Write-Log "ArcHci VM Extension Deleted Successfully" Write-Log "Exiting Uninstall-ArcHciVMExtension" return } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeout" } throw ("Timed out deleting ArcHci VM Extension. Delete state: $($arcVMExtDetails.provisioningState)") } catch { Write-Log "Correlation ID: $correlationId. $_" # If resource doesn't exist the flow shouldnt terminate with exception nothing to delete if ($_.Exception -notmatch $nothingToDelete) { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletevmextfailed" -message "Failed to delete ArcVM extension - Exception: $_" throw "Correlation ID: $correlationId. $_" } } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } } function New-ArcHciCustomLocation { <# .DESCRIPTION New CustomLocation .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER extensionName Optional parameter. Name of the extension .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE New-ArcHciCustomLocation -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -extensionName $extensionName -customLocationName $customLocationName -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [string] $location, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $extensionName, [parameter(Mandatory = $false)] [string] $customLocationName = "myResourceBridge-cl", [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered New-ArcHciCustomLocation" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "New-ArcHciCustomLocation" -version $moduleVersion try { Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $sub = $subscriptionID $rg = $resourceGroup $rn = $resourceName $en = $extensionName Write-Log "Creating Custom Location" Write-Output "Creating Custom Location" $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $CustomLocCreateCmd = "customlocation create --resource-group $resourceGroup --name $customLocationName --location $location --namespace $defaultNamespace --host-resource-id $($applianceDetails.id) --cluster-extension-ids ""/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.ResourceConnector/appliances/$rn/providers/Microsoft.KubernetesConfiguration/extensions/$en""" Set-ArcHciConfigValue -name "customLocationName" -Value $customLocationName Write-Log "Running az $CustomLocCreateCmd" Invoke-ArcHciAzCommand $CustomLocCreateCmd -ignoreWarning $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcCusLocDetails = "" while (($timeSinceStart -lt $timeout)) { Write-Log "Waiting for $CustomLocCreateCmd to finish..." Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String Write-Log "Archci custom location details: $arcCusLocDetailsString" if ($arcCusLocDetails.provisioningState -eq "Succeeded") { Write-Log "Custom location successfully created" Write-Output "Custom location successfully created" Write-Log "Exiting New-ArcHciCustomLocation" return } if ($arcCusLocDetails.provisioningState -eq "Failed") { throw "Failed to create custom location" } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeout" } throw ("Timed out creating custom location. Installation state: $($arcCusLocDetails.provisioningState)") } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createcustomlocationfailed" -message "Failed to create CustomLocation - Exception: $_" throw "Correlation ID: $correlationId. $_" } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } function Remove-ArcHciCustomLocation { <# .DESCRIPTION Remove Custom Location .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName"), [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Remove-ArcHciCustomLocation" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Remove-ArcHciCustomLocation" -version $moduleVersion try { $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName Write-Log "Checking if custom location host resource id match with arc appliance id before deleting it" if(-Not [String]::IsNullOrEmpty($arcCusLocDetails.hostResourceId) -and $applianceDetails.id -ne $arcCusLocDetails.hostResourceId){ Write-Log "ERROR: Custom location $customLocationName host resource id does not match with arc appliance id. Hence, uninstallation is terminated." throw "Custom location $customLocationName host resource id does not match with arc appliance id. Hence, uninstallation is terminated." } Write-Log "Deleting custom location" Write-Output "Deleting custom location" Write-Log "Running az customlocation delete --resource-group $resourceGroup --name $customLocationName --yes" Invoke-ArcHciAzCommand "customlocation delete --resource-group $resourceGroup --name $customLocationName --yes" $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcCusLocDetails = "" while ($timeSinceStart -lt $timeout) { Write-Log "Waiting for ""customlocation delete --resource-group $resourceGroup --name $customLocationName --yes"" to finish..." Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String Write-Log "Archci custom location details: $arcCusLocDetailsString" if ($arcCusLocDetails.provisioningState -eq "NotInstalled") { Write-Output "Custom location deleted Successfully" Write-Log "Custom location deleted Successfully" Write-Log "Exiting Remove-ArcHciCustomLocation" return } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeout" } throw ("Timed out deleting custom location. Delete state: $($arcCusLocDetails.provisioningState)") } catch { Write-Log "Correlation ID: $correlationId. $_" if ($_.Exception -notmatch "notfound|not found") { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletecustomlocationfailed" -message "Failed to delete CustomLocation - Exception: $_" throw "Correlation ID: $correlationId. $_" } } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } function Install-ArcHciHybridAKSExtension { <# .DESCRIPTION Install Hybrid AKS extension .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS Extension .PARAMETER hydaksExtVersion Optional parameter. Version of the Hybrid AKS Extension .PARAMETER releaseTrain Optional parameter. Release Train .PARAMETER enableOfflineDownload Optional parameter. Flag to set image download offline .PARAMETER correlationId Optional parameter. Identifier used for telemetry .PARAMETER aksExtensionConfigFileName Optional parameter. Name of the AKS extension config file (optional parameter. Defaults to "aks-extension-config.json") .PARAMETER workingDir Optional parameter. Working Directory Path .OUTPUTS N/A .EXAMPLE Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hydaksExtVersion -releaseTrain $releaseTrain -enableOfflineDownload -correlationId $correlationId -workingDir $workingDir #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $hydaksExtensionName = "hybridaks-hci", [parameter(Mandatory = $false)] [string] $hydaksExtVersion, [parameter(Mandatory = $false)] [string] $releaseTrain = "stable", [Parameter(Mandatory = $false)] [switch] $enableOfflineDownload, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString(), [Parameter(Mandatory=$false)] [String] $aksExtensionConfigFileName = "aks-extension-config.json", [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir ) Write-Log "Installing HybridAKS extension" Write-Output "Installing HybridAKS extension" # Align with Install-ArcHciVMExtension if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $($mocConfig.workingDir) Write-Log "Working directory is not provided. Using MOC working directory: $workingDir" } Set-ArcHciConfigValue -name "aksExtensionName" -Value $hydaksExtensionName $K8SExtensionCreateCmd = "k8s-extension create --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --extension-type Microsoft.HybridAKSOperator --release-train $releaseTrain" if (-Not [String]::IsNullOrEmpty($hydaksExtVersion)) { $K8SExtensionCreateCmd += " --version $hydaksExtVersion" $K8SExtensionCreateCmd += " --auto-upgrade $false" }else{ $K8SExtensionCreateCmd += " --auto-upgrade $true" } $configFile = "$workingDir\Appliance\$aksExtensionConfigFileName" if($enableOfflineDownload.IsPresent){ Add-ExtensionConfigToFile $configFile -key "offline-download" -value "true" } # --config or --configuration-settings can't be used together with --config-file Add-ExtensionConfigToFile $configFile -key "Microsoft.CustomLocation.ServiceAccount" -value $defaultNamespace Add-ExtensionConfigToFile $configFile -key "AgentOperationTimeoutInMinutes" -value $timeoutForExtensionCreateInMinutes if(Test-IsHCIMachine){ $hciClusterId = (Get-AzureStackHci).AzureResourceUri Add-ExtensionConfigToFile $configFile -key "HCIClusterID" -value $hciClusterId } try { $isHyperThreadingEnabled = Get-MocHyperThreadingEnabled -ErrorAction SilentlyContinue Add-ExtensionConfigToFile $configFile -key "HTEnabled" -value $isHyperThreadingEnabled } catch { Write-Log "Failed to get the hyperthreading status from MOC with the error below, skip this step." Write-Log $_ } $K8SExtensionCreateCmd += " --config-file $configFile" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciHybridAKSExtension" -version $moduleVersion try { Write-Log "Running $K8SExtensionCreateCmd" Invoke-ArcHciAzCommand $K8SExtensionCreateCmd -ignoreWarning $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $hybridAksInstallState = "" while (($timeSinceStart -lt $timeoutForExtensionCreate)) { Write-Log "Waiting for $K8SExtensionCreateCmd to finish..." Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails" if ($arcHciHybridAKSDetails.provisioningState -eq "Succeeded") { Write-Log "Hybrid AKS k8s extension successfully installed!" Write-Output "Hybrid AKS k8s extension successfully installed!" return } if ($arcHciHybridAKSDetails.provisioningState -eq "Failed") { throw "Failed to install Hybrid AKS k8s extension" } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeoutForExtensionCreate" } throw ("Timed out installing k8s-extension. Installation state: $hybridAksInstallState") } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Failed to install hybridaks extension - Exception: $_" throw "Correlation ID: $correlationId. $_" } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } } function Uninstall-ArcHciHybridAKS { <# .DESCRIPTION Remove Hybrid AKS .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS Extension .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Uninstall-ArcHciHybridAKS -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -correlationId $correlationId #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName"), [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Uninstall-ArcHciHybridAKS" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciHybridAKS" -version $moduleVersion try { Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location" $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location # Convert the hashtable to a table format and then to a string $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String Write-Log "Archci parameters: $arcHciParametersString" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails" if ($arcHciHybridAKSDetails.provisioningState -eq "Succeeded") { Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName }else{ Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig return $arcHciHybridAKSDetails } } catch { Write-Log "Correlation ID: $correlationId. $_" if ($_.Exception -notmatch $nothingToDelete) { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletehybridaksfailed" -message "Failed to remove hybridaks extension - Exception $_" throw "Correlation ID: $correlationId. $_" } } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } Write-Log "Exiting Uninstall-ArcHciHybridAKS" } function Uninstall-ArcHciHybridAKSExtension { <# .DESCRIPTION Remove Hybrid AKS extension .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS Extension .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName"), [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Uninstall-ArcHciHybridAKSExtension" Write-Log "Removing HybridAKS Extension" Write-Output "Removing HybridAKS Extension" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciHybridAKSExtension" -version $moduleVersion try { Write-Log "Running az k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --yes" Invoke-ArcHciAzCommand "k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --yes" $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $arcHciHybridAKSDetails = "" while ($timeSinceStart -lt $timeout) { Write-Log "Waiting for ""k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --yes"" to finish..." Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails" if ($arcHciHybridAKSDetails.provisioningState -eq "NotInstalled") { Write-Output "HybridAKS Extension Removed Successfully" Write-Log "HybridAKS Extension Removed Successfully" Write-Log "Exiting Uninstall-ArcHciHybridAKSExtension" return } Start-Sleep $sleepDuration $timeSinceStart = $($timer.Elapsed.TotalSeconds) Write-Log "Time elapsed since start, in seconds: $timeSinceStart" Write-Log "Timeout in seconds: $timeout" } throw ("Timed out deleting HybridAKS VM Extension. Delete state: $($arcHciHybridAKSDetails.provisioningState)") } catch { Write-Log "Correlation ID: $correlationId. $_" if ($_.Exception -notmatch $nothingToDelete) { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletehybridaksfailed" -message "Failed to remove hybridaks extension - Exception $_" throw "Correlation ID: $correlationId. $_" } } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } } function Update-ArcHciCustomLocation { <# .DESCRIPTION Update CustomLocation .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Update-ArcHciCustomLocation -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [string] $location, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName"), [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Update-ArcHciCustomLocation" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Update-ArcHciCustomLocation" -version $moduleVersion $sub = $subscriptionID $rg = $resourceGroup $rn = $resourceName try { Write-Log "Updating Custom Location $customLocationName" Write-Output "Updating Custom Location $customLocationName" $ssvmExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $hybridAKSExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "Running customlocation update --resource-group $resourceGroup --name $customLocationName --location $location --cluster-extension-ids $($hybridAKSExtDetails.id) $($ssvmExtDetails.id) --namespace $defaultNamespace --host-resource-id $($applianceDetails.id)" Invoke-ArcHciAzCommand "customlocation update --resource-group $resourceGroup --name $customLocationName --location $location --cluster-extension-ids $($hybridAKSExtDetails.id) $($ssvmExtDetails.id) --namespace $defaultNamespace --host-resource-id $($applianceDetails.id)" -ignoreWarning Write-Log "$customLocationName successfully updated!" Write-Output "$customLocationName successfully updated!" Write-Log "Exiting Update-ArcHciCustomLocation" } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "updatecustomlocationfailed" -message "Failed to update CustomLocation - Exception $_" throw "Correlation ID: $correlationId. $_" } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } function Get-ArcHciHybridAKSKubernetesVersion { <# .DESCRIPTION List the supported Kubernetes versions in the specified custom location. .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER customLocationName Name of the Custom Location .OUTPUTS N/A .EXAMPLE Get-ArcHciHybridAKSKubernetesVersion -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName #> param( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName") ) Write-Log "Entered Get-ArcHciHybridAKSKubernetesVersion" try { $customLocationId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.extendedlocation/customlocations/$customLocationName" Write-Log "Running Get-AzAksArcKubernetesVersion -CustomLocationName $customLocationId -ResourceGroupName $resourceGroup" $res = Get-AzAksArcKubernetesVersion -CustomLocationName $customLocationId -ResourceGroupName $resourceGroup } catch { Write-Log $_ if ($_.Exception -notmatch "notfound|not found") { throw $_ } } if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) { Write-Log "Not able to get K8s versions with custom location id $customLocationId" } Write-Log "Exiting Get-ArcHciHybridAKSKubernetesVersion" return $res } function Get-ArcHciHybridAKSVMSize { <# .DESCRIPTION List the supported vm sizes in the specified custom location. .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER customLocationName Name of the Custom Location .OUTPUTS N/A .EXAMPLE Get-ArcHciHybridAKSVMSize -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName #> param( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName") ) Write-Log "Entered Get-ArcHciHybridAKSVMSize" try { $customLocationId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.extendedlocation/customlocations/$customLocationName" Write-Log "Running Get-AzAksArcVMSKU -CustomLocationName $customLocationId -ResourceGroupName $resourceGroup" $res = Get-AzAksArcVMSKU -CustomLocationName $customLocationId -ResourceGroupName $resourceGroup } catch { Write-Log $_ if ($_.Exception -notmatch "notfound|not found") { throw $_ } } if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) { Write-Log "Not able to get K8s versions with custom location id $customLocationId" } Write-Log "Exiting Get-ArcHciHybridAKSVMSize" return $res } function Get-ArcHciCustomLocation { <# .DESCRIPTION Get CustomLocation Details .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER customLocationName Optional parameter. Name of the Custom Location .OUTPUTS N/A .EXAMPLE Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName #> param( [parameter(Mandatory = $true)] [string] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName") ) Write-Log "Entered Get-ArcHciCustomLocation" if (-not([String]::IsNullOrWhiteSpace($customLocationName))) { try { Write-Log "Running az customlocation show --subscription $subscriptionID --resource-group $resourceGroup --name $customLocationName" $res = (Invoke-ArcHciAzCommand -arguments " customlocation show --subscription $subscriptionID --resource-group $resourceGroup --name $customLocationName" -ignoreWarning) | ConvertFrom-Json } catch { Write-Log $_ if ($_.Exception -notmatch "notfound|not found") { throw $_ } } } if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) { $res = [pscustomobject]@{ 'provisioningState' = 'NotInstalled'; } Write-Log "Custom location with the name $customLocationName not found" } Write-Log "Exiting Get-ArcHciCustomLocation" return $res } function Get-ArcHciHybridAKS { <# .DESCRIPTION Get Hybrid AKS details .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS extension .OUTPUTS Hashtable containing Hybrid AKS details .EXAMPLE Get-ArcHciHybridAKS -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName") ) Write-Log "Entered Get-ArcHciHybridAKS" Test-ArcHciAzRequirements > $null $arcHciHybridAKSDetails = @{} Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location" $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location # Convert the hashtable to a table format and then to a string $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String Write-Log "Archci parameters: $arcHciParametersString" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName Register-ResourceProviders -subscriptionID $subscriptionID > $null Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails" Write-Log "Exiting Get-ArcHciHybridAKS" return $arcHciHybridAKSDetails } function Get-ArcHciVMExtension { <# .DESCRIPTION Get ArcHci VM extension details .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER ssvmExtensionName Optional parameter. Name of the SSVM extension .OUTPUTS Hashtable containing SSVM extension details .EXAMPLE Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName") ) Write-Log "Entered Get-ArcHciVMExtension" if (-not([String]::IsNullOrWhiteSpace($ssvmExtensionName))) { try{ Write-Log "Running az k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName" $res = (Invoke-ArcHciAzCommand -arguments "k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName" -ignoreWarning) | ConvertFrom-Json } catch{ Write-Log $_ if($_ -match "The refresh token has expired or is invalid"){ throw $_ } } } if ($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) { $res = [pscustomobject]@{ 'provisioningState' = 'NotInstalled'; } Write-Log "SSVM extension with the name $ssvmExtensionName is not installed" } Write-Log "Exiting Get-ArcHciVMExtension" return $res } function Get-ArcHciHybridAKSExtension { <# .DESCRIPTION Get Hybrid AKS extension details .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS extension .OUTPUTS Hashtable containing Hybrid AKS extension details .EXAMPLE Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName") ) Write-Log "Entered Get-ArcHciHybridAKSExtension" if (-not([String]::IsNullOrWhiteSpace($hydaksExtensionName))) { try{ Write-Log "Running az k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName" $res = (Invoke-ArcHciAzCommand -arguments "k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName" -ignoreWarning) | ConvertFrom-Json } catch{ Write-Log $_ if($_ -match "The refresh token has expired or is invalid"){ throw $_ } } } if ($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) { $res = [pscustomobject]@{ 'provisioningState' = 'NotInstalled'; } Write-Log "Hybrid AKS extension with name $hydaksExtensionName is not installed" } Write-Log "Exiting Get-ArcHciHybridAKSExtension" return $res } function Get-ArcHciResourceBridge { <# .DESCRIPTION Get Arc Resource Bridge details .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .OUTPUTS Hashtable containing Arc Resource Bridge details .EXAMPLE Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName #> param ( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName ) Write-Log "Entered Get-ArcHciResourceBridge" try { Write-Log "Running az arcappliance show --resource-group $resourceGroup --subscription $subscriptionID --name $resourceName" $res = (Invoke-ArcHciAzCommand -arguments "arcappliance show --resource-group $resourceGroup --subscription $subscriptionID --name $resourceName" -ignoreWarning) | ConvertFrom-Json } catch { Write-Log $_ if ($_.Exception -notmatch "notfound|not found") { throw $_ } } if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0){ $arbInstallState = Get-ArcHciConfigValue -name "arbInstallState" if($arbInstallState -eq "Failed"){ $res = [pscustomobject]@{ 'provisioningState' = "Failed"; } }else{ $res = [pscustomobject]@{ 'provisioningState' = 'NotInstalled'; } } Write-Log "Arc Resource Bridge with the name $resourceName is not installed" } Write-Log "Exiting Get-ArcHciResourceBridge" return $res } function Get-ArcHciMoc { <# .DESCRIPTION Get Moc details .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS Hashtable containing Moc details .EXAMPLE Get-ArcHciMoc #> Param ( [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Get-ArcHciMoc" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Get-ArcHciMoc" -version $moduleVersion try { Write-Log "Running Get-MocConfig" $mocConfig = Get-MocConfig } catch { Write-Log "Correlation ID: $correlationId. $_" $mocConfig = [pscustomobject]@{ 'installState' = 'NotInstalled'; } Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "getmocfailed" -message "Failed To Retrieve MOC config - Exception $_" } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } Write-Log "Exiting Get-ArcHciMoc" return $mocConfig } function Get-ArcHciMgmt { <# .DESCRIPTION Get ArcHciMgmt details .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER ssvmExtensionName Optional parameter. Name of the SSVM extension .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS extension .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS Hashtable containing Mgmt details .EXAMPLE Get-ArcHciMgmt -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -customLocationName $customLocationName #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName") , [parameter(Mandatory = $false)] [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName") , [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName") , [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) Write-Log "Entered Get-ArcHciMgmt" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Get-ArcHciMgmt" -version $moduleVersion try { Test-ArcHciAzRequirements > $null $arcHciMgmtDetails = @{} Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location" $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location # Convert the hashtable to a table format and then to a string $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String Write-Log "Archci parameters: $arcHciParametersString" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName $location = $arcHciParameters.Location Register-ResourceProviders -subscriptionID $subscriptionID > $null Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" $arcHciMgmtDetails.Add("ResourceBridge",$arcRBDetails) Write-Log "Running Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" $arcVMExtDetails = Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName Write-Log "Arc VM extension details: $arcVMExtDetails" $arcHciMgmtDetails.Add("VMExtension",$arcVMExtDetails) Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $HybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybrid AKS Details: $HybridAksExtDetails" $arcHciMgmtDetails.Add("HybridaksExtension",$HybridAksExtDetails) Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String Write-Log "Archci custom location details: $arcCusLocDetailsString" $arcHciMgmtDetails.Add("CustomLocation",$arcCusLocDetails) Write-Log "Exiting Get-ArcHciMgmt" return $arcHciMgmtDetails } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "getmgmtfailed" -message "Get ArcHci Mgmt Failed - Exception $_" throw "Correlation ID: $correlationId. $_" } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $arcCusLocDetails.name } } function Install-ArcHciMgmt { <# .DESCRIPTION Installs ArcHciMgmt(Arc Resource Bridge, SSVM Extension, and Custom Location) .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER location Optional parameter. Azure location .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER ssvmExtensionName Optional parameter. Name of the SSVM extension .PARAMETER ssvmExtVersion Optional parameter. Version of the SSVM extension .PARAMETER ssvmReleaseTrain Optional parameter. Name of the ssvm release train .PARAMETER ssvmShareDiagnosticData Optional parameter. Whether to send diagnostic data to Microsoft for the ssvm extension. Default: "true" Diagnostic data is used to help keep the service secure and up to date, troubleshoot problems, and make product improvements. .PARAMETER hybridaksExtensionName Optional parameter. Name of the Hybrid AKS extension .PARAMETER hybridaksExtVersion Optional parameter. Version of the Hybrid AKS extension .PARAMETER hybridaksReleaseTrain Optional parameter. Name of the hybridaks release train .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER rbIpStart The starting ip address to use for Arc RB. .PARAMETER rbIpEnd Optional parameter. The ending ip address to use for Arc RB. .PARAMETER gateway Optional parameter. The gateway to use when using static IP .PARAMETER ipaddressprefix Optional parameter. The address prefix to use for static IP assignment .PARAMETER dnsservers Optional parameter. The dnsservers to use when using static IP .PARAMETER vlanID Optional parameter. The VLAN ID for the vnet .PARAMETER arcHciProxyConfig Optional parameter. Proxy Config .PARAMETER enableOfflineDownload Optional parameter. Flag to set image download offline .PARAMETER skip_prechecks Optional parameter. Flag to skip prechecks .PARAMETER skip_cleanup Optional parameter. Flag to avoid cleaning up installed components .PARAMETER correlationId Optional parameter. Identifier used for telemetry .PARAMETER isolateImageDir Optional parameter. Flag to isolate image dir .PARAMETER aksExtProxyConfig Optional parameter. Proxy Config for AKS Extension .OUTPUTS N/A .EXAMPLE Install-ArcHciMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $releaseTrain -customLocationName $customLocationName -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -enableOfflineDownload -skip_prechecks -skip_cleanup -isolateImageDir $isolateImageDir -enableArcVMExtension -enableHybridAKS #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [string] $location, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "vnetName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $vnetName = "vnet-arcbridge", # vswitchName can accept any characters [parameter(Mandatory = $true)] [string] $vswitchName, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "VipPoolStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [String] $vippoolstart, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "VipPoolEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [String] $vippoolend, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "azstackhciImage" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [String] $azstackhciImage, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternVersionNumber) { $parameter = "azstackhciVersion" throw $regexPatternVersionNumberError -f $_, $parameter } return $true })] [String] $azstackhciVersion, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "mocImage" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [String] $mocImage, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternVersionNumber) { $parameter = "mocVersion" throw $regexPatternVersionNumberError -f $_, $parameter } return $true })] [String] $mocVersion, [parameter(Mandatory = $false)] [string] $ssvmExtensionName = "vmss-hci", [parameter(Mandatory = $false)] [string] $ssvmExtVersion, [parameter(Mandatory = $false)] [string] $ssvmReleaseTrain = "stable", [parameter(Mandatory = $false)] [ValidateSet("true", "false")] [string] $ssvmShareDiagnosticData = "true", [parameter(Mandatory = $false)] [string] $hybridaksExtensionName = "hybridaks-hci", [parameter(Mandatory = $false)] [string] $hybridaksExtVersion = "", [parameter(Mandatory = $false)] [string] $hybridaksReleaseTrain = "stable", [parameter(Mandatory = $false)] [string] $customLocationName = "myResourceBridge-cl", [Parameter(Mandatory = $true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "ControlPlaneIP" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [String] $controlPlaneIP, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpStart, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpEnd, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $gateway, [parameter(Mandatory = $false)] [string] $ipAddressPrefix, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) { return $true } foreach ($i in $_) { $response = Test-IPV4Address -ip $i if (!$response) { $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter, $i } } return $true })] [string[]] $dnsServers = @(), [Parameter(Mandatory = $false)] [ValidateRange(0, 4094)] [int] $vlanID, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [Parameter(Mandatory = $false)] $arcHciProxyConfig, [Parameter(Mandatory = $false)] [switch] $enableOfflineDownload, [Parameter(Mandatory = $false)] [switch] $skip_prechecks, [Parameter(Mandatory = $false)] [switch] $skip_cleanup, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString(), [parameter(Mandatory = $false)] [switch] $isolateImageDir, [parameter(Mandatory = $false)] [switch] $enableHybridAKS = [switch]::Present, [parameter(Mandatory = $false)] [switch] $enableArcVMExtension = [switch]::Present, [Parameter(Mandatory = $false)] $aksExtProxyConfig ) $global:ProgressPreference = 'SilentlyContinue' Write-Log "Entered Install-ArcHciMgmt" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciMgmt" -version $moduleVersion try { Write-Log "Running Get-ArcHciMoc" $mocConfig = Get-ArcHciMoc -correlationId $correlationId # Convert the hashtable to a table format and then to a string $mocConfigString = $mocConfig | Format-Table -AutoSize | Out-String Write-Log "Mocconfig: $mocConfigString" $azCloudContext = Get-AzCloudContext if ([string]::IsNullOrEmpty($azCloudContext)) { $err = "Correlation ID: $correlationId. Failed to retrieve cloud context from AzCLI" Write-Log $err throw $err } if ($mocConfig.installState -eq 7) { Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location" $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location # Convert the hashtable to a table format and then to a string $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String Write-Log "Archci parameters: $arcHciParametersString" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName $location = $arcHciParameters.Location Write-Log "Running Install-ArcHciPrerequisites -subscriptionID $subscriptionID" try { Install-ArcHciPrerequisites -subscriptionID $subscriptionID } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Failed to install ArcHci prerequisites - Exception $_" throw "Correlation ID: $correlationId. $_" } # New approach is to always remove any existing arcmgmt setup and do fresh installation every time Install-ArcHciMgmt is called Write-Output "Remove any existing arcmgmt setup" Write-Log "Remove any existing arcmgmt setup" Uninstall-ArcHciMgmt if (-not $skip_prechecks.IsPresent) { Write-Log "Running Test-ArcHciEnableArcMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipaddressPrefix $ipaddressPrefix -dnsservers $dnsservers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig" $mgmtTestRes = Test-ArcHciEnableArcMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipAddressPrefix $ipAddressPrefix -dnsServers $dnsServers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -correlationId $correlationId Write-Log "ArcHciMgmt test result: $($mgmtTestRes[$mgmtTestRes.length - 1].TestResult)" Write-Log "$($mgmtTestRes[$mgmtTestRes.length - 1].Details)" } else { Write-Log "Skipping Mgmt prechecks" Write-Output "Skipping Mgmt prechecks" } if ($skip_prechecks.IsPresent -or $mgmtTestRes[$mgmtTestRes.length - 1].TestResult -eq "Succeeded") { Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" try { if ($arcRBDetails.provisioningState -eq "NotInstalled") { Write-Log "Running New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $azCloudContext.name -armEndpoint $($azCloudContext.endpoints.resourceManager) -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipaddressPrefix $ipaddressPrefix -dnsservers $dnsservers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -aksExtProxyConfig $aksExtProxyConfig" New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $azCloudContext.name -armEndpoint $azCloudContext.endpoints.resourceManager -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipAddressPrefix $ipAddressPrefix -dnsServers $dnsServers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -aksExtProxyConfig $aksExtProxyConfig Write-Log "Running Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId" Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId #Temporary logic to mark container as isolated. We will get rid of this once permanent fix is available from ARB team if ($isolateImageDir.IsPresent) { Write-Log "Running Set-MocContainer -name MocStorageContainer -location MocLocation -isolated" Set-MocContainer -name "MocStorageContainer" -location "MocLocation" -isolated } } else { Write-Output "Arc resource bridge is already installed. Kindly run Get-ArcHciMgmt to know the status." Write-Log "Arc resource bridge is already installed. Kindly run Get-ArcHciMgmt to know the status." } } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installarbfailed" -message "Failed to install arc resource bridge - Exception $_" if (-not $skip_cleanup.IsPresent) { try { Write-Log "Collecting Logs..." Write-Output "Collecting Logs..." Get-ArcHciLogs } catch { Write-Log $_ } Write-Output "Rolling back Arcmgmt installation due to error $_" Write-Log "Rolling back Arcmgmt installation due to error $_" Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId" Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId Write-Log "Running Remove-ArcHciConfigFiles" Remove-ArcHciConfigFiles } throw "Correlation ID: $correlationId. $_" } # SSVM installation is turned off on non-hci machine if (Test-IsHCIMachine) { try { Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" if ($arcRBDetails.provisioningState -eq "Succeeded") { if($enableArcVMExtension.IsPresent){ Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "Arc VM extension details: $arcVMExtDetails" if ($arcVMExtDetails.provisioningState -eq "NotInstalled") { Write-Log "Running Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $ssvmReleaseTrain -shareDiagnosticData $ssvmShareDiagnosticData -workingDir $workingDir -correlationId $correlationId" Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $ssvmReleaseTrain -shareDiagnosticData $ssvmShareDiagnosticData -workingDir $workingDir -correlationId $correlationId } else { Write-Output "SSVM extension is already installed. Kindly run Get-ArcHciMgmt to know the status." Write-Log "SSVM extension is already installed. Kindly run Get-ArcHciMgmt to know the status." } } } else { Write-Output "Arc resource bridge is not installed. Please install Arc resource bridge first." Write-Log "Arc resource bridge is not installed. Please install Arc resource bridge first." } } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installvmextfailed" -message "Failed to install ArcVM extension - Exception $_" if (-not $skip_cleanup.IsPresent) { Write-Output "Rolling back Arcmgmt installation due to error $_" Write-Log "Rolling back Arcmgmt installation due to error $_" Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId" Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId Write-Log "Running Remove-ArcHciConfigFiles" Remove-ArcHciConfigFiles } throw "Correlation ID: $correlationId. $_" } try { Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "Arc VM extension details: $arcVMExtDetails" Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String Write-Log "Archci custom location details: $arcCusLocDetailsString" if ($arcCusLocDetails.provisioningState -eq "Succeeded") { Write-Log "Running Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName -correlationId $correlationId" Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName -correlationId $correlationId } elseif ($arcCusLocDetails.provisioningState -eq "NotInstalled") { Write-Log "Running New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $ssvmExtensionName -customLocationName $customLocationName -correlationId $correlationId" New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $ssvmExtensionName -customLocationName $customLocationName -correlationId $correlationId } else { Write-Output "Custom location with the name $customLocationName already exist. Kindly run Get-ArcHciMgmt to know the status." Write-Log "Custom location with the name $customLocationName already exist. Kindly run Get-ArcHciMgmt to know the status." $arcVMEnabledMsg = $null } } catch { Write-Log "Correlation ID: $correlationId. $_" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createorupdatecustomlocationfailed" -message "Create or update custom location failed during Mgmt installation - Exception $_" if (-not $skip_cleanup.IsPresent) { Write-Output "Rolling back Arcmgmt installation due to error $_" Write-Log "Rolling back Arcmgmt installation due to error $_" Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId" Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId" Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId Write-Log "Running Remove-ArcHciConfigFiles" Remove-ArcHciConfigFiles } throw "Correlation ID: $correlationId. $_" } try { if ($enableHybridAKS.IsPresent){ Write-Log "Running Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $hybridaksReleaseTrain -customLocationName $customLocationName -correlationId $correlationId -enableOfflineDownload:$enableOfflineDownload.IsPresent -skip_cleanup:$($skip_cleanup.IsPresent) -workingDir $workingDir" Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $hybridaksReleaseTrain -customLocationName $customLocationName -correlationId $correlationId -enableOfflineDownload:$enableOfflineDownload.IsPresent -skip_cleanup:$skip_cleanup.IsPresent -workingDir $workingDir } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Install hybrid AKS failed during Mgmt installation - Exception $_" if (-not $skip_cleanup.IsPresent) { Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId" Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId" Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId Write-Log "Running Remove-ArcHciConfigFiles" Remove-ArcHciConfigFiles } throw "Correlation ID: $correlationId. $_" } } else { Write-Log "SSVM and hybridaks installation is turned off on non-hci machine. Hence SSVM, hybridaks extension and custom location will not be installed/created." Write-Output "SSVM and hybridaks installation is turned off on non-hci machine. Hence SSVM, hybridaks extension and custom location will not be installed/created." } if ($null -ne $arcVMEnabledMsg) { Write-Log $arcVMEnabledMsg Write-Output $arcVMEnabledMsg } Write-Log "Exiting Install-ArcHciMgmt" } else { $err = "Correlation ID: $correlationId. Terminating ArcHciMgmt Installation. At least one of the prechecks required for ArcHciMgmt installation failed." Write-Log $err Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "ArcHciMgmt precheck failed: $err" throw $err } } else { Write-Log "Correlation ID: $correlationId. Terminating ArcHciMgmt Installation. Moc is not installed, please install Moc first" Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig throw "Correlation ID: $correlationId. Terminating ArcHciMgmt Installation. Moc is not installed, please install Moc first" } $global:ProgressPreference = 'Continue' } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } function Install-ArcHciHybridAKS { <# .DESCRIPTION Installs ArcHci Hybrid AKS .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER location Optional parameter. Azure location .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS extension .PARAMETER hybridaksExtVersion Optional parameter. Version of the Hybrid AKS extension .PARAMETER releaseTrain Optional parameter. Name of the release train .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER skip_cleanup Optional parameter. Flag to avoid cleaning up installed components .PARAMETER correlationId Optional parameter. Identifier used for telemetry .PARAMETER enableOfflineDownload Optional parameter. Flag to set image download offline .PARAMETER workingDir Optional parameter. Working Directory Path .OUTPUTS N/A .EXAMPLE Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -customLocationName $customLocationName -skip_cleanup -enableOfflineDownload -workingDir $workingDir #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [string] $location, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $hydaksExtensionName = "hybridaks-hci", [parameter(Mandatory = $false)] [string] $hybridaksExtVersion = "", [parameter(Mandatory = $false)] [string] $releaseTrain = "stable", [parameter(Mandatory = $false)] [string] $customLocationName = (Invoke-Command -ScriptBlock { $clName = $(Get-ArcHciConfigValue -name "customLocationName") if (-not [string]::IsNullOrEmpty($clName)) { $clName } else { "myResourceBridge-cl" }}), [Parameter(Mandatory = $false)] [switch] $skip_cleanup, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString(), [Parameter(Mandatory = $false)] [switch] $enableOfflineDownload, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir ) $global:ProgressPreference = 'SilentlyContinue' Write-Log "Entered Install-ArcHciHybridAKS" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciHybridAKS" -version $moduleVersion # Align with Install-ArcHciVMExtension if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $($mocConfig.workingDir) Write-Log "Working directory is not provided. Using MOC working directory: $workingDir" } try { Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location" $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location # Convert the hashtable to a table format and then to a string $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String Write-Log "Archci parameters: $arcHciParametersString" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName $location = $arcHciParameters.Location Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybridaks extension details: $arcHciHybridAKSDetails" if($arcHciHybridAKSDetails.provisioningState -eq "NotInstalled"){ Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" if ($arcRBDetails.provisioningState -eq "Succeeded") { try { Write-Log "Running Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -enableOfflineDownload:$enableOfflineDownload.IsPresent -workingDir $workingDir" Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -enableOfflineDownload:$enableOfflineDownload.IsPresent -workingDir $workingDir } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Failed to create hybridaks extension - Exception $_" Write-Log "Correlation ID: $correlationId. $_" if (-not $skip_cleanup.IsPresent) { Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName } throw "Correlation ID: $correlationId. $_" } try { Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcHybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "Hybrid AkS details: $arcHybridAksExtDetails" if ($arcHybridAksExtDetails.provisioningState -eq "Succeeded") { $hydaksExtensionName = $arcHybridAksExtDetails.name Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName $queryK8sVersionAndSizes = $false $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String Write-Log " Archci custom location details: $arcCusLocDetailsString" if ($arcCusLocDetails.provisioningState -eq "Succeeded") { $customLocationName = $arcCusLocDetails.name Write-Log "Running Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName" Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName -correlationId $correlationId $queryK8sVersionAndSizes = $true } elseif ($arcCusLocDetails.provisioningState -eq "NotInstalled") { Write-Log "Running New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $hydaksExtensionName -customLocationName $customLocationName" New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $hydaksExtensionName -customLocationName $customLocationName -correlationId $correlationId $queryK8sVersionAndSizes = $true } else { Write-Output "Custom location with the name $customLocationName couldn't be updated. Kindly run Get-ArcHciMgmt to know the status." Write-Log "Custom location with the name $customLocationName couldn't be updated. Kindly run Get-ArcHciMgmt to know the status." } # Make another call to get the latest custom location details. # Relook at the code changes when the feature is resolved https://dev.azure.com/msazure/One/_workitems/edit/24617280 if ($queryK8sVersionAndSizes) { Write-Log "Sleeping for 60 seconds before querying kubernetes versions and vm skus" Start-Sleep 60 #TODO remove -ignoreFailure parameter once we figure out the root cause for why we can't do a put on kubernetesversions sometimes $null = Retry -operation {Get-ArcHciHybridAKSKubernetesVersion -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName} -retryDelaySeconds 60 -ignoreFailure $null = Retry -operation {Get-ArcHciHybridAKSVMSize -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName} -retryDelaySeconds 60 -ignoreFailure } } else { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Install HybridAks K8s extension did not return success result - Provisioning State: $($arcHybridAksExtDetails.provisioningState)" } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createorupdatecustomlocationfailed" -message "Failed to create or update CustomLocation as part of hybrid AKS install - Exception $_" Write-Log "Correlation ID: $correlationId. $_" if (-not $skip_cleanup.IsPresent) { Write-Output "Rolling back Arcmgmt installation due to error $_" Write-Log "Rolling back Arcmgmt installation due to error $_" Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName } throw "Correlation ID: $correlationId. $_" } } else{ Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig Write-Log "Arc Resource Bridge is not installed. Please install Arc Resource Bridge first." throw "Arc Resource Bridge is not installed. Please install Arc Resource Bridge first." } } else { Write-Output "Hybridaks extension is already installed. Kindly run Get-ArcHciMgmt to know the status." Write-Log "Hybridaks extension is already installed. Kindly run Get-ArcHciMgmt to know the status." } } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } Write-Log "Exiting Install-ArcHciHybridAKS" $global:ProgressPreference = 'Continue' } function Uninstall-ArcHciMgmt { <# .DESCRIPTION Uninstall ArcHciMgmt .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER ssvmExtensionName Optional parameter. Name of the SSVM extension .PARAMETER hydaksExtensionName Optional parameter. Name of the Hybrid AKS Extension .PARAMETER customLocationName Optional parameter. Name of the Custom Location .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Uninstall-ArcHciMgmt -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -customLocationName $customLocationName -workingDir $workingDir #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName"), [parameter(Mandatory = $false)] [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName"), [parameter(Mandatory = $false)] [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName"), [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) $global:ProgressPreference = 'SilentlyContinue' Write-Log "Entered Uninstall-ArcHciMgmt" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciMgmt" -version $moduleVersion try { Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location" $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location # Convert the hashtable to a table format and then to a string $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String Write-Log "Archci parameters: $arcHciParametersString" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" try { $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String Write-Log " Archci custom location details: $arcCusLocDetailsString" if ($arcCusLocDetails.provisioningState -ne 'NotInstalled') { $customLocationName = $arcCusLocDetails.name Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletecustomlocationfailed" -message "Failed to delete CustomLocation - Exception $_" throw "Correlation ID: $correlationId. $_" } Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" try { $arcHybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName Write-Log "Hybrid AKS details: $arcHybridAksExtDetails" if($arcHybridAksExtDetails.provisioningState -ne 'NotInstalled'){ $hydaksExtensionName = $arcHybridAksExtDetails.name Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletehybridaksfailed" -message "Failed to delete hybrid AKS - Exception $_" throw "Correlation ID: $correlationId. $_" } Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" try { $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName Write-Log "Archci VM extension details: $arcVMExtDetails" if ($arcVMExtDetails.provisioningState -ne 'NotInstalled') { $ssvmExtensionName = $arcVMExtDetails.name Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletevmextfailed" -message "Failed to delete VM extension - Exception $_" throw "Correlation ID: $correlationId. $_" } Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName" try { $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "RB details: $arcRBDetails" if ($arcRBDetails.provisioningState -ne 'NotInstalled') { Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir" Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir Write-Log "Running Remove-ArcHciConfigFiles" Remove-ArcHciConfigFiles } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletearbfailed" -message "Failed to delete Azure resource bridge - Exception $_" throw "Correlation ID: $correlationId. $_" } Write-Log "Exiting Uninstall-ArcHciMgmt" $global:ProgressPreference = 'Continue' } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } function Get-ArcHciParameters { <# .DESCRIPTION Get ArcHciParameters like subscriptionID, location, resourceGroup, resourceName .PARAMETER subscriptionID Optional parameter. The Azure subscription GUID .PARAMETER location Optional parameter. Azure location .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER rbIpStart Optional parameter. The starting ip address to use for Arc RB. .PARAMETER rbIpEnd Optional parameter. The ending ip address to use for Arc RB. .OUTPUTS Hashtable containing parameters like subscriptionID, location, resourceGroup, resourceName .EXAMPLE Get-ArcHciParameters -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName #> param ( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [string] $location, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpStart, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpEnd ) Write-Log "Entered Get-ArcHciParameters" $arcHciParameters = @{} if (Test-IsHCIMachine) { try { $azureStackHciConfig = Get-AzureStackHci } catch { Write-Log "Azure Stack HCI cluster was not detected" } } $mocConfig = Get-ArcHciMoc $workingDir = $($mocConfig.workingDir) $workingDir = "$workingDir\Appliance" $fileExist = Test-Path -Path "$workingDir\hci-resource.yaml" -PathType leaf if ($fileExist) { $filecontent = Get-Content -Path "$workingDir\hci-resource.yaml" if ([String]::IsNullOrEmpty($subscriptionID) -or $subscriptionID -eq "00000000-0000-0000-0000-000000000000") { $subscriptionID = ($filecontent | Where-Object { $_ -like "*subscription:*" }).Split(':')[1].TrimStart() } if ([String]::IsNullOrEmpty($resourceGroup)) { $resourceGroup = ($filecontent | Where-Object { $_ -like "*resource_group:*" }).Split(':')[1].TrimStart() } if ([String]::IsNullOrEmpty($resourceName)) { $resourceName = ($filecontent | Where-Object { $_ -like "*name:*" }).Split(':')[1].TrimStart() } } elseif ($null -ne $azureStackHciConfig) { $uri = $azureStackHciConfig.AzureResourceUri $splitUriString = $uri -split "/" if ([String]::IsNullOrEmpty($subscriptionID) -or $subscriptionID -eq "00000000-0000-0000-0000-000000000000") { $subscriptionID = $splitUriString[2] } if ([String]::IsNullOrEmpty($resourceGroup)) { $resourceGroup = $splitUriString[4] } if ([String]::IsNullOrEmpty($resourceName)) { $resourceName = $azureStackHciConfig.AzureResourceName + "-arcbridge" } } $arcHciParameters.Add("SubscriptionID", $subscriptionID) $arcHciParameters.Add("ResourceGroup", $resourceGroup) $arcHciParameters.Add("ResourceName", $resourceName) if (-not[String]::IsNullOrEmpty($resourceName) -and [String]::IsNullOrEmpty($location)) { try{ $location = Invoke-ArcHciAzCommand "group show --name $resourceGroup --query ""location"" -o tsv" }catch{ Write-Log $_ } } $arcHciParameters.Add("Location", $location) $k8sNodeIpPoolEnd = "" $k8sNodeIpPoolStart = "" if (-not [string]::IsNullOrEmpty($rbIpEnd)){ $k8sNodeIpPoolEnd = $rbIpEnd $to_bytes = [IPAddress]::Parse($rbIpStart).GetAddressBytes() [Array]::Reverse($to_bytes) $k8sNodeIpPoolStartBytes = [BitConverter]::ToUInt32($to_bytes, 0) $k8sNodeIpPoolStartBytes = [BitConverter]::GetBytes($k8sNodeIpPoolStartBytes) [Array]::Reverse($k8sNodeIpPoolStartBytes) $k8sNodeIpPoolStart = $([System.Net.IPAddress]$k8sNodeIpPoolStartBytes).IPAddressToString } $arcHciParameters.Add("k8sNodeIpPoolStart", $k8sNodeIpPoolStart) $arcHciParameters.Add("k8sNodeIpPoolEnd", $k8sNodeIpPoolEnd) Write-Log "Exiting Get-ArcHciParameters" return $arcHciParameters } function Test-IsHCIMachine { <# .DESCRIPTION Mgmt Proxy Configuration .OUTPUTS Boolean value .EXAMPLE Test-IsHCIMachine #> # Check if the machine is Azure Stack HCI # The ps module "AzureStackHCI" is available only on the azstackhci machines if ($null -eq (Get-Module -ListAvailable -Name "AzureStackHCI" -ErrorAction:Ignore)) { return $false } return $true } function New-ArcHciMgmtProxyConfiguration { <# .DESCRIPTION Mgmt Proxy Configuration .PARAMETER proxyServerHTTP http urls for proxy .PARAMETER proxyServerHTTPS https urls for proxy .PARAMETER proxyServerNoProxy Comma separate list of URLs and IP ranges that should not be proxied. When proxy is enabled, the no proxy list must include at the minimum: localhost,127.0.0.1,.svc,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 .PARAMETER certificateFilePath Name of the cert File Path for proxy .OUTPUTS Hashtable containing proxy parameters .EXAMPLE New-ArcHciMgmtProxyConfiguration -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath #> param ( [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTP" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTP, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if(($_ -notmatch $regexPatternProxyUrl) -and ($_ -notmatch $regexPatternProxyIP)){ $parameter = "proxyServerHTTPS" throw $regexPatternProxyUrlError -f $parameter,$_ } return $true })] [String] $proxyServerHTTPS, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -match $spaceWithChar) { throw $spaceError } return $true })] [String] $proxyServerNoProxy, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [String] $certificateFilePath ) Write-Log "Entered New-ArcHciMgmtProxyConfiguration" $arcHciProxyConfig = @{} $arcHciProxyConfig.Add("proxyServerHTTP", $proxyServerHTTP) $arcHciProxyConfig.Add("proxyServerHTTPS", $proxyServerHTTPS) $arcHciProxyConfig.Add("proxyServerNoProxy", $proxyServerNoProxy) $arcHciProxyConfig.Add("certificateFilePath", $certificateFilePath) Write-Log "Exiting New-ArcHciMgmtProxyConfiguration" return $arcHciProxyConfig } function Test-ArcHciVmSwitchExists { <# .DESCRIPTION Checks for the existence of the vm switch on all nodes of the cluster. .PARAMETER vswitchName The name of the vswitch #> param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $vswitchName ) $testReport = [pscustomobject]@{ 'TestName' = 'Test VM Switch Exists'; 'TestResult' = "Succeeded"; 'Details' = "" } $nodes = Get-ClusterNode ForEach ($node in $nodes){ $switchObj = Get-VMSwitch -name $vswitchName -ComputerName $node.Name -ErrorAction:Ignore if ($null -eq $switchObj) { $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName does not exist on node $($node.Name). Please verify a virtual switch with name $vswitchName exists on all nodes of the cluster." Write-Error "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName does not exist on node $($node.Name). Please verify a virtual switch with name $vswitchName exists on all nodes of the cluster." return $testReport } if ($switchObj.SwitchType -ne "External"){ $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName exists on node $($node.Name) but is not an external switch. Please verify an external switch with the same name exists on all nodes of the cluster." Write-Error "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName exists on node $($node.Name) but is not an external switch. Please verify an external switch with the same name exists on all nodes of the cluster." return $testReport } } return $testReport } function Test-ArcHciClusterHealth { <# .DESCRIPTION Checks 1. All nodes of cluster are up. 2. Cluster network is up. 3. CSV is accessible and has more than 50 GB memory. .PARAMETER volumePath The path of the volume #> param ( [Parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [Parameter(Mandatory = $false)] [string] $resourceGroup, [Parameter(Mandatory = $false)] [string] $resourceName, [Parameter(Mandatory = $false)] [String] $volumePath ) $testReport = [pscustomobject]@{ 'TestName' = 'Test ArcHCI Cluster Health'; 'TestResult' = "Succeeded"; 'Details' = "" } if (Test-IsHCIMachine){ try { $azureStackHciConfig = Get-AzureStackHci } catch { $testReport.TestResult = "Failed" $testReport.Details = "Get-AzureStackHci failed." return $testReport } if ($azureStackHciConfig.RegistrationStatus -ne "Registered"){ $regStatus = $azureStackHciConfig.RegistrationStatus $testReport.TestResult = "Failed" $testReport.Details = "Azure Stack HCI cluster is not in the proper state. Current registration state is $regStatus. Please fix cluster registration and retry." return $testReport } } $nodes = Get-ClusterNode -ErrorAction:Ignore if ($null -eq $nodes){ $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciClusterHealth : No nodes found." Write-Error "Test-ArcHciClusterHealth : No nodes found." return $testReport } Get-ClusterNode -ErrorAction Stop | ForEach-Object { if ($_.State -ine "Up") { $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciClusterHealth : At least one node is down." Write-Error "Test-ArcHciClusterHealth : At least one node is down." } } if ($testReport.TestResult -eq "Failed"){ return $testReport } Get-ClusterNetwork -ErrorAction Stop | ForEach-Object { # we only want to fail when the network state is not 'up' AND the network role is not 'None' if ($_.State -ine "Up" -and $_.Role -eq "ClusterAndClient") { $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciClusterHealth : At least one clusternetwork is down." } } if ($testReport.TestResult -eq "Failed"){ Write-Error "Test-ArcHciClusterHealth : At least one clusternetwork is down." return $testReport } if(![string]::IsNullOrEmpty($volumePath)){ $volumeFound = $false $notEnoughSpaceinVolume = $false $volumePathLower = $volumePath.ToLower() $clusterShareVolumes = Get-ClusterSharedVolume -ErrorAction Stop | Where-Object { $_.Name -like "*Infrastructure*" } if ([string]::IsNullOrEmpty($clusterShareVolumes)) { $clusterShareVolumes = Get-ClusterSharedVolume -ErrorAction Stop } # Sort volumes by the length of their FriendlyVolumeName in descending order $csvList = $clusterShareVolumes | Sort-Object { $_.SharedVolumeInfo.FriendlyVolumeName.Length } -Descending $csvList | ForEach-Object { if ($_.State -eq "Online") { $sharedVolumePath = $_.SharedVolumeInfo.FriendlyVolumeName.ToLower() $lenSharedVolumePath = $sharedVolumePath.Length if ($lenSharedVolumePath -le $volumePathLower.Length){ $volumePathLower = $volumePathLower.substring(0,$lenSharedVolumePath) } if ($volumePathLower -eq $sharedVolumePath){ $freespaceGb = [Math]::Round($_.SharedVolumeInfo.Partition.FreeSpace / 1GB) if ($freespaceGb -gt 50){ $volumeFound = $true } else { $notEnoughSpaceinVolume = $true } } } } if ($notEnoughSpaceinVolume){ $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciClusterHealth : 50 GB of space not available in specified volume $volumePath" } if ($volumeFound -eq $false){ $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciClusterHealth : specified volume $volumePath was not found." } } else { $volumeUp = $false Get-ClusterSharedVolume -ErrorAction Stop | ForEach-Object { if ($_.State -eq "Online") { $volumeUp = $true } } if ($volumeUp -eq $false) { $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciClusterHealth : No Cluster Shared Volume is online." Write-Error "Test-ArcHciClusterHealth : No Cluster Shared Volume is online." return $testReport } } return $testReport } function Test-ArcHciMemoryRequirements{ <# .DESCRIPTION Checks 1. If 8GB of memory is available. 2. If 4 vCPU's are available. #> $testReport = [pscustomobject]@{ 'TestName' = 'Test Arc Mgmt Memory and vCPU Requirement'; 'TestResult' = "Succeeded"; 'Details' = "" } $testReportFailed = [pscustomobject]@{ 'TestName' = 'Test Arc Mgmt Memory and vCPU Requirement'; 'TestResult' = "Failed"; 'Details' = "" } $nodes = Get-ClusterNode $minimumRequirementMet = $false # threads * cores * physical number of cpu ForEach ($node in $nodes){ $memoryRequirementMet = $false $vCPURequirementMet = $false $nodeOSInfo = Get-CIMInstance Win32_OperatingSystem -ComputerName $node.name $mem = ($nodeOSInfo.FreePhysicalMemory) * 1KB / 1GB if ($mem -ge 8){ $memoryRequirementMet = $true } $processors = Get-CimInstance -ClassName Win32_Processor -ComputerName $node.name $totalVCpu = 0 $vCPUsInUse = 0 ForEach ($processor in $processors){ $totalVCpu += $processor.ThreadCount * $processor.NumberOfCores } $totalVmOnNode = Get-Vm -ComputerName $node.name ForEach ($vm in $totalVmOnNode){ $vCPUsInUse += $vm.ProcessorCount } $availablevCPUs = $totalVCpu - $vCPUsInUse if ($availablevCPUs -ge 4){ $vCPURequirementMet = $true } if ($memoryRequirementMet -and $vCPURequirementMet){ $minimumRequirementMet = $true } if (!$minimumRequirementMet) { $testReportFailed.Details += "The cluster node $($node.name) doesn't meet the minimum memory and vCPU requirements. At least 8 GB of free memory is required and at least 4 vCPU's should be available. Total memory available $($nodeOSInfo.TotalVisibleMemorySize) and memory in use $mem. Total vCPU's $totalVCpu and vCPU's in use $vCPUsInUse. " }else{ return $testReport } } Write-Error $testReportFailed.Details return $testReportFailed } function Test-ArcHciMocPowershellModulesExists{ $testReport = [pscustomobject]@{ 'TestName' = 'Validate Powershell Module Installation'; 'TestResult' = "Succeeded"; 'Details' = "" } #update once AzStackHci Module has been created $installed = Get-Module -ListAvailable -Name 'Moc' if ($installed -eq $null){ $testReport.TestResult = "Failed" $testReport.Details = "Moc powershell module not installed. Please install the latest version" return $testReport } return $testReport } function Test-ArcHciAzRequirements { <# .DESCRIPTION Checks if az login has been properly executed and access token exists for the current user. #> $testReport = [pscustomobject]@{ 'TestName' = 'Validate Az Requirements'; 'TestResult' = "Succeeded"; 'Details' = "" } try { Invoke-ArcHciAzCommand -arguments " account get-access-token" } catch { Write-Log $_ $err = $error[0] $testReport.TestResult = "Failed" $testReport.Details = $err.Exception.message Write-Error $err.Exception.message } return $testReport } function Test-ArcHciHyperVEnabled{ <# .DESCRIPTION Checks if required Hyper-V features are installed. #> $requiredServerFeatures = @( "Hyper-V", "Hyper-V-PowerShell", "RSAT-Clustering-PowerShell") $requiredServerFeaturesStandalone = @( "Microsoft-Hyper-V", "Microsoft-Hyper-V-Management-PowerShell") $testReport = [pscustomobject]@{ 'TestName' = 'Validate Windows HyperV Features Enabled'; 'TestResult' = "Succeeded"; 'Details' = "" } if (Test-IfCluster){ $nodes = Get-ClusterNode ForEach ($node in $nodes){ Invoke-Command -ComputerName $node.NodeName -ScriptBlock { $rebootRequired = $false $GenericLocMessage = $args[1] foreach($feature in $requiredServerFeatures) { $wf = Get-WindowsFeature -Name "$feature" if ($null -eq $wf) { $testReport.TestResult = "Failed" $testReport.Details = "Windows feature - $feature was not found on $node.NodeName.Please enable this feature." Write-Error "Windows feature - $feature was not found on $node.NodeName.Please enable this feature." } if ($wf.InstallState -ne "Installed") { $testReport.TestResult = "Failed" $testReport.Details = "Please check the state of windows feature - $feature on $node.NodeName" Write-Error "Please check the state of windows feature - $feature on $node.NodeName" } } } } } else { foreach($feature in $requiredServerFeaturesStandalone) { $wf = Get-WindowsOptionalFeature -Online -FeatureName "$feature" if ($null -eq $wf) { $testReport.TestResult = "Failed" $testReport.Details = "Windows feature - $feature was not found.Please enable this feature." Write-Error "Windows feature - $feature was not found.Please enable this feature." } if ($wf.State -ne "Enabled") { $testReport.TestResult = "Failed" $testReport.Details = "Please check the state of windows feature - $feature." Write-Error "Please check the state of windows feature - $feature." } } } return $testReport } function Test-ArcHciResourceBridgeIps { Param( [Parameter(Mandatory=$true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "ControlPlaneIP" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpStart, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpEnd, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $gateway, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternCIDRFormat) { $parameter = "ipaddressprefix" throw $regexPatternCIDRFormatError -f $_, $parameter } return $true })] [string] $ipAddressPrefix, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) { return $true } foreach ($i in $_) { $response = Test-IPV4Address -ip $i if (!$response) { $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter, $i } } return $true })] [string[]] $dnsServers = @() ) $testReport = [pscustomobject]@{ 'TestName' = 'Test resource bridge ips'; 'TestResult' = "Succeeded"; 'Details' = "" } $dhcpEnabledCluster = $True $clusterNetworks = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { ($_.Role -eq "ClusterAndClient" ) ` -and ($_.Ipv4Addresses.Count -gt 0 ) } | Select-Object -property Name, IPv4Addresses, IPV4PrefixLengths foreach ($clusterNetwork in $clusterNetworks) { for($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) { if ((Get-ClusterNetworkInterface -Network $($clusterNetwork.Name) | Select-Object -expandProperty "DhcpEnabled") -ne 1) { $dhcpEnabledCluster = $False } } } if ([string]::IsNullOrEmpty($rbIpStart)){ if (-Not $dhcpEnabledCluster){ $testReport.TestResult = "Failed" $testReport.Details = "This is not a dhcp enabled cluster. rbIpStart parameter is required." return $testReport } } if ([string]::IsNullOrEmpty($rbIpEnd)){ if (-Not $dhcpEnabledCluster){ $testReport.TestResult = "Failed" $testReport.Details = "This is not a dhcp enabled cluster. rbIpEnd parameter is required." return $testReport } } if ([string]::IsNullOrEmpty($gateway)){ if (-Not $dhcpEnabledCluster){ $testReport.TestResult = "Failed" $testReport.Details = "This is not a dhcp enabled cluster. gateway parameter is required." return $testReport } } if ([string]::IsNullOrEmpty($ipAddressPrefix)){ if (-Not $dhcpEnabledCluster){ $testReport.TestResult = "Failed" $testReport.Details = "This is not a dhcp enabled cluster. ipAddressPrefix parameter is required." return $testReport } } if ($dnsServers -eq $null -or $dnsServers.count -eq 0 -or $dnsServers.length -eq 0){ if (-Not $dhcpEnabledCluster){ $testReport.TestResult = "Failed" $testReport.Details = "This is not a dhcp enabled cluster. dnsServers parameter is required." return $testReport } } <# $foundInClusterNetwork = $False foreach ($clusterNetwork in $clusterNetworks) { for($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) { [System.Net.IPAddress]$ipv4 = $null $clusIpv4 = $clusterNetwork.Ipv4Addresses[$i] if (-Not [System.Net.IPAddress]::TryParse($clusIpv4, [ref] $ipv4)) { Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_ignore_failover_ip , $clusIpv4)) continue } $lastIp = [AKSHCI.IPUtilities]::GetLastIpInCidr($ipv4, $clusterNetwork.Ipv4PrefixLengths[$i]) if([AKSHCI.IPUtilities]::CompareIpAddresses($rbIpStart, $ipv4) -ge 0 -AND [AKSHCI.IPUtilities]::CompareIpAddresses($rbIpStart, $lastIp) -le 0) { $foundInClusterNetwork = $True #The cloud service CIDR is in the range of the cluster network IP! Compare prefix lengths now! break } } } if ($foundInClusterNetwork -ne $True) { $testReport.TestResult = "Failed" $testReport.Details = "Resource bridge Ip not in cluster network." } #> if(-Not $dhcpEnabledCluster){ $testRangeResult = Test-rbIpRange -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd if (-not $testRangeResult){ $testReport.TestResult = "Failed" $testReport.Details = "This is not a valid Ip range. Please specify a valid range for start and end ip address. There must be at least 2 Ips in the range." } } return $testReport } function Test-ArcHciAzArcApplianceValidate { <# .DESCRIPTION Checks if az arcappliance validate command is successful. .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER rbIpStart The starting ip address to use for Arc RB. .PARAMETER rbIpEnd Optional parameter. The ending ip address to use for Arc RB. .PARAMETER gateway Optional parameter. The gateway to use when using static IP .PARAMETER ipaddressprefix Optional parameter. The address prefix to use for static IP assignment .PARAMETER dnsservers Optional parameter. The dnsservers to use when using static IP .PARAMETER vlanID Optional parameter. The VLAN ID for the vnet .PARAMETER arcHciProxyConfig Optional parameter. Proxy Config .OUTPUTS N/A .EXAMPLE Test-ArcHciAzArcApplianceValidate -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig #> Param( [parameter(Mandatory = $true)] [GUID] $subscriptionID, [parameter(Mandatory = $true)] [string] $location, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "vnetName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $vnetName, # vswitchName can accept any characters [parameter(Mandatory = $true)] [string] $vswitchName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolstart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolend, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [String] $clusterName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "azstackhciImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "azstackhciVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternRFC1123){ $parameter = "mocImage" throw $regexPatternRFC1123Error -f $_,$parameter } return $true })] [String] $mocImage, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if($_ -notmatch $regexPatternVersionNumber){ $parameter = "mocVersion" throw $regexPatternVersionNumberError -f $_,$parameter } return $true })] [String] $mocVersion, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [Parameter(Mandatory=$true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "ControlPlaneIP" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpStart, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpEnd, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $gateway, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternCIDRFormat) { $parameter = "ipaddressprefix" throw $regexPatternCIDRFormatError -f $_, $parameter } return $true })] [string] $ipAddressPrefix, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) { return $true } foreach ($i in $_) { $response = Test-IPV4Address -ip $i if (!$response) { $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter, $i } } return $true })] [string[]] $dnsServers = @(), [Parameter(Mandatory = $false)] [ValidateRange(0, 4094)] [int] $vlanID = 0, [Parameter(Mandatory = $false)] $arcHciProxyConfig ) $testReport = [pscustomobject]@{ 'TestName' = 'Test az arcappliance validate'; 'TestResult' = "Succeeded"; 'Details' = "" } $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName if ($arcRBDetails.provisioningState -eq 'NotInstalled') { try { $azCloudContext = Get-AzCloudContext if ([string]::IsNullOrEmpty($azCloudContext)) { $err = "Correlation ID: $correlationId. Failed to retrieve cloud context from AzCLI" Write-Log $err throw $err } Write-Log "Running New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $azCloudContext.name -armEndpoint $($azCloudContext.endpoints.resourceManager) -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig" New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -cloudName $azCloudContext.name -armEndpoint $azCloudContext.endpoints.resourceManager -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsServers $dnsServers -ipAddressprefix $ipAddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig } catch { Write-Log "New-ArcHciApplianceConfigs failed with error $_" $testReport.TestResult = "Failed" $testReport.Details = $_ return $testReport } } try { Invoke-ArcHciAzCommand "provider register --namespace Microsoft.ResourceConnector --wait" } catch { $testReport.TestResult = "Failed" $testReport.Details = $_ return $testReport } try { Write-Log "Running az arcappliance validate hci --config-file ""$workingDir\Appliance\hci-appliance.yaml""" Invoke-ArcHciAzCommand "arcappliance validate hci --config-file ""$workingDir\Appliance\hci-appliance.yaml""" -ignoreWarning } catch { Write-Log "az arcappliance validate failed with error $_" $testReport.TestResult = "Failed" $testReport.Details = $_ } return $testReport } function Test-ArcHciMocInstallation{ <# .DESCRIPTION Checks if MOC is installed. #> $testReport = [pscustomobject]@{ 'TestName' = 'Validate MOC Installation'; 'TestResult' = "Succeeded"; 'Details' = "" } try { $mocInstallationState = Get-ArcHciMoc } catch { $testReport.TestResult = "Failed" $testReport.Details = $_.Exception.Message return $testReport } if ($mocInstallationState.installState -ne 7){ $testReport.TestResult = "Failed" $curState = [InstallState]$mocInstallationState.installState $testReport.Details = "MOC is in state $curState. Please ensure MOC is properly installed before proceeding." return $testReport } $mocConfig = Get-ArcHciMoc $tmpGroup = Get-ClusterGroup -Name $mocConfig.clusterRoleName -ErrorAction Ignore if ($tmpGroup.State -ne "Online") { $testReport.TestResult = "Failed" $testReport.Details = "wssdcloudagent is not running. Please confirm that MOC was installed properly." return $testReport } Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = ${_}.Name $tmpService = Get-Service wssdagent -ComputerName $nodeName if ($tmpService.Status -ne "Running") { $testReport.TestResult = "Failed" $testReport.Details = "wssdagent is not running on at least one node. Please confirm that MOC was installed properly and wssdagent service is running on all nodes." } } return $testReport } function Test-ArcHciEnableMoc { <# .DESCRIPTION Checks if requirements are satisfied for MOC to be installed. .PARAMETER volumePath The path of the volume .PARAMETER cloudserviceIP Optional parameter. Cloud agent IP .PARAMETER correlationId Optional parameter. Identifier used for telemetry #> param ( [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $volumePath, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "cloudserviceIP" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $cloudserviceIP, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Test-ArcHciEnableMoc" -version $moduleVersion if ([String]::IsNullOrEmpty($cloudserviceIP)) { $numberOfArcHciMocPrechecks = 3 } try { $testResults = @() $overallResult = $true $numOfFailedMocPrechecks = 0 $isCluster = Test-IfCluster if ([String]::IsNullOrEmpty($volumePath)) { $volumePath = Get-ArcHciDefaultPath } #check if moc module is installed Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 1,$numberOfArcHciMocPrechecks,"Test Moc Powershell Module Installation" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciMocPowershellModulesExists } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test Moc Powershell Module Installation'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedMocPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test Moc Powershell Module Installation failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } else { Write-Host "Test Succeeded" -ForegroundColor Green } # check if 50 gb exists in chsared memorycluster nodes and cluster network up Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 2,$numberOfArcHciMocPrechecks,"Test ArcHci Cluster Health" Write-Host $outputHeader -ForegroundColor White if ($isCluster){ try { $testReport = Test-ArcHciClusterHealth -volumePath $volumePath } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Validate Cluster Health'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } } else { $testReport = [pscustomobject]@{ 'TestName' = 'Validate Cluster Health'; 'TestResult' = "Succeeded"; 'Details' = "Test not required for standalone machine." } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedMocPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test Validate Cluster Health failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } else { Write-Host "Test Succeeded" -ForegroundColor Green } #hyperv enabled Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 3,$numberOfArcHciMocPrechecks,"Test Windows HyperV Features are enabled" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciHyperVEnabled } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test Windows HyperV Features are enabled'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedMocPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test Windows HyperV Features are enabled failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } else { Write-Host "Test Succeeded" -ForegroundColor Green } if ($numberOfArcHciMocPrechecks -eq 4) { #cloudserviceIp check Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 4, $numberOfArcHciMocPrechecks, "Test ArcHci Moc Cloud Service Ip" Write-Host $outputHeader -ForegroundColor White if ($isCluster) { try { $testReport = Test-ArcHciMocCloudServiceIP -cloudserviceIp $cloudServiceIP } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test ArcHci Moc Cloud Service Ip'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } } else { $testReport = [pscustomobject]@{ 'TestName' = 'Test ArcHci Moc Cloud Service Ip'; 'TestResult' = "Succeeded"; 'Details' = "Test not required for standalone machine." } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test ArcHci Moc Cloud Service Ip failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } else { Write-Host "Test Succeeded" -ForegroundColor Green } } #overall result test Write-Host '===============================================================================' -ForegroundColor White if ($overallResult -eq $false) { $testReport = [pscustomobject]@{ 'TestName' = 'Overall Enable Moc Precheck Result'; 'TestResult' = "Failed"; 'Details' = 'At least one of the prechecks required for MOC installation failed. See above tests for more details.' } } else { $testReport = [pscustomobject]@{ 'TestName' = 'Overall Enable Moc Precheck Result'; 'TestResult' = "Succeeded"; 'Details' = 'All prechecks required for MOC installation succeeded.' } } $testResults += $testReport Write-Host '===============================================================================' -ForegroundColor White if ($overallResult -eq $false){ Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test enable MOC enable failed " Write-Host "Correlation ID: $correlationId. Moc installation prechecks were not successful. $numOfFailedMocPrechecks/$numberOfArcHciMocPrechecks Prechecks Failed" -ForegroundColor Red } else{ Write-Host "All Moc installation precheck tests were successful" -ForegroundColor Green } Write-Host '===============================================================================' -ForegroundColor White return $testResults } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig } } function Test-ArcHciEnableArcMgmt { <# .DESCRIPTION Checks if all requirements for installtion arc mgmt are met. .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location .PARAMETER resourceGroup Name of the Azure resource group .PARAMETER resourceName Name of the Azure resource .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER workingDir Optional parameter. Working Directory Path .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER rbIpStart The starting ip address to use for Arc RB. .PARAMETER rbIpEnd Optional parameter. The ending ip address to use for Arc RB. .PARAMETER gateway Optional parameter. The gateway to use when using static IP .PARAMETER ipAddressPrefix Optional parameter. The address prefix to use for static IP assignment .PARAMETER dnsServers Optional parameter. The dnsservers to use when using static IP .PARAMETER vlanID Optional parameter. The VLAN ID for the vnet .PARAMETER arcHciProxyConfig Optional parameter. Proxy Config .PARAMETER aksExtProxyConfig Optional parameter. Proxy Config passed to AKS extension .PARAMETER correlationId Optional parameter. Identifier used for telemetry .OUTPUTS N/A .EXAMPLE Test-ArcHciEnableArcMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -aksExtProxyConfig $aksExtProxyConfig #> Param( [parameter(Mandatory = $false)] [GUID] $subscriptionID = [System.Guid]::empty, [parameter(Mandatory = $false)] [string] $location, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternAzureResourceGroup) { $parameter = "ResourceGroup" throw $regexPatternAzureResourceGroupError -f $_, $parameter } return $true })] [string] $resourceGroup, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "ResourceName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $resourceName, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "vnetName" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [string] $vnetName = "vnet-arcbridge", # vswitchName can accept any characters [parameter(Mandatory = $true)] [string] $vswitchName, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolStart" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolstart, [Parameter(Mandatory=$false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "VipPoolEnd" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $vippoolend, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "azstackhciImage" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [String] $azstackhciImage, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternVersionNumber) { $parameter = "azstackhciVersion" throw $regexPatternVersionNumberError -f $_, $parameter } return $true })] [String] $azstackhciVersion, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternRFC1123) { $parameter = "mocImage" throw $regexPatternRFC1123Error -f $_, $parameter } return $true })] [String] $mocImage, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternVersionNumber) { $parameter = "mocVersion" throw $regexPatternVersionNumberError -f $_, $parameter } return $true })] [String] $mocVersion, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir, [Parameter(Mandatory=$true)] [ValidateScript({ $response = Test-IPV4Address -ip $_ if(!$response){ $parameter = "ControlPlaneIP" throw "$ipv4ValidationError" -f $parameter,$_ } return $true })] [String] $controlPlaneIP, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpStart" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpStart, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "rbIpEnd" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $rbIpEnd, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "Gateway" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [string] $gateway, [parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -notmatch $regexPatternCIDRFormat) { $parameter = "ipaddressprefix" throw $regexPatternCIDRFormatError -f $_, $parameter } return $true })] [string] $ipAddressPrefix, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) { return $true } foreach ($i in $_) { $response = Test-IPV4Address -ip $i if (!$response) { $parameter = "DnsServers" throw "$ipv4ValidationError" -f $parameter, $i } } return $true })] [string[]] $dnsServers = @(), [Parameter(Mandatory = $false)] [ValidateRange(0, 4094)] [int] $vlanID, [Parameter(Mandatory = $false)] $arcHciProxyConfig, [Parameter(Mandatory = $false)] $aksExtProxyConfig, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Test-ArcHciEnableArcMgmt" -version $moduleVersion try { $testResults = @() $overallResult = $true $numOfFailedPrechecks = 0 if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $($mocConfig.workingDir) } $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName $location = $arcHciParameters.Location #cluster health check Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 1,$numberOfArcMgmtPrechecks,"Test ArcHci Cluster Health" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciClusterHealth } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test ArcHci Cluster Health'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test ArcHci Cluster Health Failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } else { Write-Host "Test Succeeded" -ForegroundColor Green } #MOC installation check Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 2,$numberOfArcMgmtPrechecks,"Test ArcHci MOC Installation state" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciMocInstallation } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test ArcHci MOC Installation state'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test ArcHci Moc Installation Failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } else { Write-Host "Test Succeeded" -ForegroundColor Green } #vm switch test Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 3,$numberOfArcMgmtPrechecks,"Test VM Switch Exists" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciVmSwitchExists -vswitchName $vswitchName } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test VM Switch Exists'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test ArcHci VM Switch Exists Failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } else { Write-Host "Test Succeeded" -ForegroundColor Green } #memory test # Write-Host '===============================================================================' -ForegroundColor White # $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 4,$numberOfArcMgmtPrechecks,"Test Arc Mgmt Memory and vCPU Requirement" # Write-Host $outputHeader -ForegroundColor White # try { # $testReport = Test-ArcHciMemoryRequirements # } # catch { # $testReport = [pscustomobject]@{ # 'TestName' = 'Test Arc Mgmt Memory and vCPU Requirement'; # 'TestResult' = "Failed"; # 'Details' = $_.Exception.Message # } # } # $testResults += $testReport # if ($testReport.TestResult -eq "Failed") { # $overallResult = $false # $numOfFailedPrechecks += 1 # Write-Host "Test Failed" -ForegroundColor Red # Write-Host "Details:"$testReport.Details -ForegroundColor Red # Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Arc Mgmt Memory and vCPU Requirement Failed: $($testReport.Details)" # Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName # } else { # Write-Host "Test Succeeded" -ForegroundColor Green # } #az requirements test Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 4,$numberOfArcMgmtPrechecks,"Test Az Requirements" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciAzRequirements } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test Az Requirements'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Az Requirements Failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } else { Write-Host "Test Succeeded" -ForegroundColor Green } #resource bridge ip address range check Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 5,$numberOfArcMgmtPrechecks,"Test Resource Bridge IPs" Write-Host $outputHeader -ForegroundColor White try { $testReport = Test-ArcHciResourceBridgeIps -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -ipAddressPrefix $ipAddressPrefix -gateway $gateway -dnsServers $dnsServers } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test Resource Bridge IPs'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Resource Bridge IPs Failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } else { Write-Host "Test Succeeded" -ForegroundColor Green } #ip address test Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 6,$numberOfArcMgmtPrechecks,"Test Az ArcAppliance Validate" Write-Host $outputHeader -ForegroundColor White try { Write-Log "Running Test-ArcHciAzArcApplianceValidate -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipaddressPrefix $ipaddressPrefix -dnsservers $dnsservers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig" $testReport = Test-ArcHciAzArcApplianceValidate -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipAddressPrefix $ipAddressPrefix -dnsServers $dnsServers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig } catch { $testReport = [pscustomobject]@{ 'TestName' = 'Test Az ArcAppliance Validate'; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false $numOfFailedPrechecks += 1 Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Az ArcAppliance Validate Failed: $($testReport.Details)" Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } else { Write-Host "Test Succeeded" -ForegroundColor Green } #overall result test Write-Host '===============================================================================' -ForegroundColor White if ($overallResult -eq $false) { $testReport = [pscustomobject]@{ 'TestName' = 'Overall ArcHci Mgmt Precheck Result'; 'TestResult' = "Failed"; 'Details' = 'At least one of the prechecks required for Arc Mgmt installation failed. See above tests for more details.' } } else { $testReport = [pscustomobject]@{ 'TestName' = 'Overall ArcHci Mgmt Precheck Result'; 'TestResult' = "Succeeded"; 'Details' = 'All prechecks required for Arc Mgmt installation succeeded.' } } $testResults += $testReport Write-Host '===============================================================================' -ForegroundColor White if ($overallResult -eq $false) { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test enable ArcHci failed" Write-Host "Correlation ID: $correlationId. All Arc Mgmt precheck tests were not successful" -ForegroundColor Red } else { Write-Host 'All Arc Mgmt precheck tests were successful' -ForegroundColor Green } Write-Host '===============================================================================' -ForegroundColor White return $testResults } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } function Get-ArcHciApplianceLogs { <# .DESCRIPTION Collect Arc Appliance logs .PARAMETER resourceGroup Optional parameter. Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Optional parameter. Name of the Azure resource .PARAMETER ip IP address of the ARC appliance VM or kubernetes api server .PARAMETER logDir Path to the directory to store the logs .PARAMETER kvaTokenPath Path to the KVA token (which was generated during the installation of the ARC resource bridge) .OUTPUTS N/A .EXAMPLE Get-ArcHciApplianceLogs -ip $ip -logDir $logDir -kvaTokenPath $kvaTokenPath #> param ( [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } $response = Test-IPV4Address -ip $_ if (!$response) { $parameter = "Ip" throw "$ipv4ValidationError" -f $parameter, $_ } return $true })] [String]$ip, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [String] $arcHciLogDir, [Parameter(Mandatory = $false)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [String] $kvaTokenPath = (Get-ArcHciConfigValue -name "kvaTokenLocation") ) if ([String]::IsNullOrEmpty($arcHciLogDir)) { $timestamp = Get-Date -Format "yyyyMMddHHmmss" $arcHciLogDir = $(Join-Path $arcHciLogDir "archcilogs_$timestamp") } if (-not ($arcHciLogDir | Test-Path)) { New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null } try { $mocConfig = Get-MocConfig } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } try { #Downloading logkey Write-Log "Running az arcappliance get-credentials --config-file ""$($mocConfig.workingDir)\Appliance\hci-appliance.yaml"" --overwrite-existing true --yes" Invoke-ArcHciAzCommand "arcappliance get-credentials --config-file ""$($mocConfig.workingDir)\Appliance\hci-appliance.yaml"" --overwrite-existing true --yes" -ignoreWarning | Out-Null } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } $cloudFqdn = $mocConfig.cloudFqdn if ([String]::IsNullOrEmpty($ip)) { $ip = $(Get-ArcHciFirstControlPlaneNodeIp -arcHciLogDir $arcHciLogDir) } try { Write-Log "Running az arcappliance logs hci --ip $ip --out-dir $arcHciLogDir --loginconfigfile $kvaTokenPath --cloudagent $cloudFqdn" Invoke-ArcHciAzCommand "arcappliance logs hci --ip $ip --out-dir $arcHciLogDir --loginconfigfile $kvaTokenPath --cloudagent $cloudFqdn" -ignoreWarning >> "$($arcHciLogDir)\commandlogs.txt" } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } } function Publish-ArcHciTelemetryEvent { param ( [Parameter(Mandatory=$true)] [PSTypeName('ArcHciTelemetryEventConfig')] $eventConfig, [Parameter(Mandatory=$false)] [guid] $subscriptionId = [System.Guid]::Empty, [Parameter(Mandatory=$false)] [string] $resourceGroup, [Parameter(Mandatory=$false)] [string] $resourceName, [Parameter(Mandatory=$false)] [string] $customLocationName ) try { $customLocationId = $null # generate CustomLocationId if required fields are present if(![string]::IsNullOrEmpty($customLocationName) -and ![string]::IsNullOrEmpty($subscriptionId) -and ![string]::IsNullOrEmpty($resourceGroup) -and ![string]::IsNullOrEmpty($resourceGroup)) { $customLocationId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.extendedlocation/customlocations/$customLocationName" } # Emit the event EmitArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionId -resourceGroup $resourceGroup -customLocationId $customLocationId } catch { # Preference is to not impact user flow if telemetry fails to emit Write-Host "Failed to emit telemetry event error={$_}" Write-Log "Failed to emit telemetry event error={$_}" } } function Test-ArcHciMocCloudServiceIP { <# .DESCRIPTION Checks if the cloudservice ip addresses is in the proper format and is in the same subnet as host network. .PARAMETER cloudServiceIP The value of the cloud service IP #> param ( [Parameter(Mandatory = $false)] [String] $cloudServiceIP ) $testReport = [pscustomobject]@{ 'TestName' = 'Test MOC cloud service ip'; 'TestResult' = "Succeeded"; 'Details' = "" } #Checks against cloudServiceIP $mocInstallationState = Get-ArcHciMoc if ($mocInstallationState.installState -eq 7) { $testReport.Details = "MOC is already Installed." return $testReport } $dhcpEnabledCluster = $True $clusterNetworks = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { ($_.Role -eq "ClusterAndClient" ) ` -and ($_.Ipv4Addresses.Count -gt 0 ) } | Select-Object -property Name, IPv4Addresses, IPV4PrefixLengths foreach ($clusterNetwork in $clusterNetworks) { for ($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) { #Multiple interfaces can be linked to a cluster network. if ((Get-ClusterNetworkInterface -Network $($clusterNetwork.Name) | Select-Object -expandProperty "DhcpEnabled") -ne 1) { $dhcpEnabledCluster = $False } } } if ([string]::IsNullOrEmpty($cloudServiceIP)) { if ($dhcpEnabledCluster) { $testReport.Details = "This is a dhcp enabled cluster. cloudServiceIp is not required" } else { $testReport.TestResult = "Failed" $testReport.Details = "This is a static ip cluster, cloudServiceIp is required. Please provide an ip address." } return $testReport } #Check if Cloud Service IP is in valid format. if (-Not [System.Net.IPAddress]::TryParse($cloudServiceIP, [ref]$null)) { $testReport.TestResult = "Failed" $testReport.Details = "CloudServiceIP is not in the proper format" return $testReport } #Check if cloud service IP is already in use if (Test-NetConnection $cloudServiceIP -InformationLevel "Quiet" -ErrorAction Ignore -WarningAction SilentlyContinue) { $testReport.TestResult = "Failed" $testReport.Details = "CloudServiceIP is already in use. Please specify a different ip address." return $testReport } $foundInClusterNetwork = $False #Check if Cloud service CIDR is part of Cluster network foreach ($clusterNetwork in $clusterNetworks) { for ($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) { [System.Net.IPAddress]$ipv4 = $null $clusIpv4 = $clusterNetwork.Ipv4Addresses[$i] if (-Not [System.Net.IPAddress]::TryParse($clusIpv4, [ref] $ipv4)) { Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_ignore_failover_ip , $clusIpv4)) continue } $lastIp = [AKSHCI.IPUtilities]::GetLastIpInCidr($ipv4, $clusterNetwork.Ipv4PrefixLengths[$i]) if ([AKSHCI.IPUtilities]::CompareIpAddresses($cloudServiceIP, $ipv4) -ge 0 -AND [AKSHCI.IPUtilities]::CompareIpAddresses($cloudServiceIP, $lastIp) -le 0) { $foundInClusterNetwork = $True #The cloud service CIDR is in the range of the cluster network IP! Compare prefix lengths now! break } } } if ($foundInClusterNetwork -ne $True) { $testReport.TestResult = "Failed" $testReport.Details = "CloudServiceIP not in subnet of host network." } return $testReport } function Reset-ArcHciConfigurationDirectory { <# .DESCRIPTION Set ArcHci configuration directory to different path. .PARAMETER volumePath Mandatory parameter. Volume Path .OUTPUTS N/A .EXAMPLE Reset-ArcHciConfigurationDirectory -volumePath $volumePath #> Param( [parameter(Mandatory = $true)] [ValidateScript({ if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $volumePath ) Reset-ArcHciConfigurationKey $workingDir = Join-Path -Path $volumePath -ChildPath "WorkingDirectory\Appliance" if (Test-Path $workingDir) { Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $workingDir $configDir = Get-ConfigurationDirectory -moduleName $global:ArcHciModule Write-Log "ArcHci configuration directory is set to $configDir" Write-Output "ArcHci configuration directory is set to $configDir" } else { Write-Log "Couldn't reset Archci configuration directory. $workingDir path doesn't exist" Write-Output "Couldn't reset Archci configuration directory. $workingDir path doesn't exist" } } function Get-AzureLocalCertificateFilePath { <# .DESCRIPTION For Azure.Local cloud only: Get the Azure.Local public root certificate file path from the environment variable or the default path .OUTPUTS The file path of the Azure local public root certificate .EXAMPLE Get-AzureLocalCertificateFilePath #> # Get the file path from the AZURE_LOCAL_ROOT_CERT environment variable $certfilePath = [System.Environment]::GetEnvironmentVariable("AZURE_LOCAL_ROOT_CERT") # If AZURE_LOCAL_ROOT_CERT is not set, use the default path if ([string]::IsNullOrEmpty($certfilePath)) { $appDataPath = [System.Environment]::GetEnvironmentVariable("APPDATA") $certfilePath = Join-Path -Path $appDataPath -ChildPath "AzureLocal\AzureLocalRootCert.cer" } return $certfilePath } function Add-ExtensionConfigToFile { <# .DESCRIPTION Add a key-value pair in the extension configuration file .PARAMETER configFilePath The file path of the extension configuration file .PARAMETER key The key to be added in the extension configuration file .PARAMETER value The value to be added in the extension configuration file .OUTPUTS N/A .EXAMPLE Add-ExtensionConfigToFile $configFilePath -key "key" -value "value" #> param ( [parameter(Mandatory = $true)] [string] $configFilePath, [parameter(Mandatory = $true)] [string] $key, [parameter(Mandatory = $true)] [string] $value ) if (Test-Path -Path $configFilePath) { $content = Get-Content -Path $configFilePath -Raw -ErrorAction SilentlyContinue } else { $content = "{}" } $configContentJson = $content | ConvertFrom-Json # skip if the key already exists if ($configContentJson.$key -eq $value) { return } Write-Log "Adding config key $key with value $value to $configFilePath" $configContentJson | Add-Member -Type NoteProperty -Name $key -Value $value $configContentJson | ConvertTo-Json -Depth 10 | Set-Content -Path $configFilePath } function Test-AzureLocalCloud { <# .DESCRIPTION Check if the cloud is Azure.Local .PARAMETER cloudName The name of the cloud. .OUTPUTS True if the cloud is Azure.Local, False otherwise. .EXAMPLE Test-AzureLocalCloud -cloudName "Azure.Local" #> param ( [parameter(Mandatory = $true)] [string] $cloudName ) return $cloudName -ieq "Azure.Local" } function Get-AzCloudContext { <# .DESCRIPTION Get current cloud context in Azure CLI .OUTPUTS Return current cloud context in Azure CLI .EXAMPLE Get-AzCloudContext #> try { $cloudContext = Invoke-ArcHciAzCommand "cloud show" | ConvertFrom-Json } catch { $err = "Failed to get current cloud context in Azure CLI, error={$_}" Write-Log $err throw $err } return $cloudContext } # SIG # Begin signature block # MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC5pHQ+TXnRz6Vq # DkqBLvZJgdXGejs3TgsxPogcTInk8KCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPD1FCSYEIaCvetlHnorhyUt # elbC1ciqdZF/AaQeAVypMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEACXV+nezpYTcF48pf+oSBZPLAmdLzy94aw1sG/BJdDt/0wCyPWiuUM2ik # YSBD9LGxcXOq1QXMVS0EyM+HVyQaNJ1Rf7+y3xF5xeLJaXBUfbvVH4BbyhqUhmzy # ph7WP2spI9e3sHR+/QZORkqALoU5i37Um869rJgJmALRsrY/R+bFQopCSf9s39hM # GB1X+OaF/5RNQyECJkzBLLJY10xVREk/gr5a/eemDP8M2EGm1YERTOhg0YxOTGdy # 0o3Z8gMhSWqyWsKGPDH8x6CSP+B1HGd3yg+Djm2GMVgPolL+Ua/cqhn5W4B1TxsH # F0bI+mw1HafnoImurePnwgaWWoJXNqGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC # F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCTLFhez22G7I5cjfE0nx+ZHnGPuOHs+FR5XFhZN0JoqAIGZxqD9Mgk # GBMyMDI0MTEwMTE4NDAyNy45MDdaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHqMIIHIDCCBQigAwIBAgITMwAAAfAqfB1ZO+YfrQABAAAB8DANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # NTFaFw0yNTAzMDUxODQ1NTFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQC1Hi1Tozh3O0czE8xfRnrymlJNCaGWommPy0eINf+4 # EJr7rf8tSzlgE8Il4Zj48T5fTTOAh6nITRf2lK7+upcnZ/xg0AKoDYpBQOWrL9Ob # FShylIHfr/DQ4PsRX8GRtInuJsMkwSg63bfB4Q2UikMEP/CtZHi8xW5XtAKp95cs # 3mvUCMvIAA83Jr/UyADACJXVU4maYisczUz7J111eD1KrG9mQ+ITgnRR/X2xTDMC # z+io8ZZFHGwEZg+c3vmPp87m4OqOKWyhcqMUupPveO/gQC9Rv4szLNGDaoePeK6I # U0JqcGjXqxbcEoS/s1hCgPd7Ux6YWeWrUXaxbb+JosgOazUgUGs1aqpnLjz0YKfU # qn8i5TbmR1dqElR4QA+OZfeVhpTonrM4sE/MlJ1JLpR2FwAIHUeMfotXNQiytYfR # BUOJHFeJYEflZgVk0Xx/4kZBdzgFQPOWfVd2NozXlC2epGtUjaluA2osOvQHZzGO # oKTvWUPX99MssGObO0xJHd0DygP/JAVp+bRGJqa2u7AqLm2+tAT26yI5veccDmNZ # sg3vDh1HcpCJa9QpRW/MD3a+AF2ygV1sRnGVUVG3VODX3BhGT8TMU/GiUy3h7ClX # OxmZ+weCuIOzCkTDbK5OlAS8qSPpgp+XGlOLEPaM31Mgf6YTppAaeP0ophx345oh # twIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFNCCsqdXRy/MmjZGVTAvx7YFWpslMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQA4IvSbnr4jEPgo5W4xj3/+0dCGwsz863QG # Z2mB9Z4SwtGGLMvwfsRUs3NIlPD/LsWAxdVYHklAzwLTwQ5M+PRdy92DGftyEOGM # Hfut7Gq8L3RUcvrvr0AL/NNtfEpbAEkCFzseextY5s3hzj3rX2wvoBZm2ythwcLe # ZmMgHQCmjZp/20fHWJgrjPYjse6RDJtUTlvUsjr+878/t+vrQEIqlmebCeEi+VQV # xc7wF0LuMTw/gCWdcqHoqL52JotxKzY8jZSQ7ccNHhC4eHGFRpaKeiSQ0GXtlbGI # bP4kW1O3JzlKjfwG62NCSvfmM1iPD90XYiFm7/8mgR16AmqefDsfjBCWwf3qheIM # fgZzWqeEz8laFmM8DdkXjuOCQE/2L0TxhrjUtdMkATfXdZjYRlscBDyr8zGMlprF # C7LcxqCXlhxhtd2CM+mpcTc8RB2D3Eor0UdoP36Q9r4XWCVV/2Kn0AXtvWxvIfyO # Fm5aLl0eEzkhfv/XmUlBeOCElS7jdddWpBlQjJuHHUHjOVGXlrJT7X4hicF1o23x # 5U+j7qPKBceryP2/1oxfmHc6uBXlXBKukV/QCZBVAiBMYJhnktakWHpo9uIeSnYT # 6Qx7wf2RauYHIER8SLRmblMzPOs+JHQzrvh7xStx310LOp+0DaOXs8xjZvhpn+Wu # Zij5RmZijDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN # MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjdGMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDC # KAZKKv5lsdC2yoMGKYiQy79p/6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6s+NzzAiGA8yMDI0MTEwMTE3MjYz # OVoYDzIwMjQxMTAyMTcyNjM5WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDqz43P # AgEAMAcCAQACAgaAMAcCAQACAhQEMAoCBQDq0N9PAgEAMDYGCisGAQQBhFkKBAIx # KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # hvcNAQELBQADggEBABUDqPfy95RJnsyKLTRI+Ac2IoX8Wj3tEe6W85yagBdWUAC6 # pZ0EyNJHyFvw9EhEOK9Z0ZpCjJcyvLkQxPDsP0zW68qJBk2vtHySOH/0IVbDeT8E # nldRnuYtncLryG4n4TFhEyhOFke4zj5kX91jzqWkIXhxVabvJtvYtnh01Q2A2rcv # baHEkFaSjdSD0zGH8GyDZYHWF22JFBjMvgZ/QUdTQmr/35T1ztnoVrQI5u3Xx3jh # d42kIBY34LqjkWIMVp2B1KRYtB7HL+Cwb5A6dV3etjPUuLuXWRkMXR//BU6nF4mB # 19/2h0kNbt+uHRV+c5QkCSmkgp27GUmPANqnB44xggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAfAqfB1ZO+YfrQABAAAB8DAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCC84QGI3YzCcWs8ZZh4I/vaCmvzPQ1bxxRBlwCEUlQqrjCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIFwBmqOlcv3kU7mAB5sWR74QFAiS # 6mb+CM6asnFAZUuLMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAHwKnwdWTvmH60AAQAAAfAwIgQgUNsWjK/z0mLwCGInDITmog9Zkmnk # D+WhTKdBrVlIookwDQYJKoZIhvcNAQELBQAEggIAYmljOe0MEyYhAIeeb2HgjQii # BKvGGOTxJ5RrIqtH8AFjO0Mx4JmwBZ+C7gUdWtv/Ow/dJqT/ifDjTAdxzT1j1bm/ # bFw6QFu9drkvWtWjnXo8AkKBdBT/Igpo+ff4its4sOPh4CSKfm8m+TwTJVWvSNT0 # WcsUGAqqOTCLk9XD5qjdCRzo5Vo2MsE0+kXOmtYtf54IabkUyih4jORkjAXQ4Q6Q # 8dTFxYRuVDPR8vzWRZ52XJfzGdRI8LEdNZZBWA00Mn8apn2R9tTZK4CtnItpI28P # UPv7LtZ3lbmguKHEY0cFDMMf5wj7gwqHSg6NCTIFc6Xt+FrvxaEnlgAYlLw8Slx2 # XM8RSNd4Ba30o7nok0JAOTfxY5vSine4cBo7hnOvWDRSJA64SZOxCdNZ9WkKvn8V # ff+mJcRVYA65DxSPXFlxnYox8LW1890tGHuwzsq7KNQnVs+n2r/InV/YVZASFYnD # cyii9xtQzrDq6pGGBbA9lhX0b3LxgAlBpOzqiRaoz1rx78x27r+ajLclgCS0dYk1 # A1mJE1JFWv7fdD+Ssf1ozbwxemSQprqvxJTbC2r6e4bgTlJQ10tQILjBQkySfKRw # 74kYDP3MdDTlxItqNAL4RrYEVQUhN+2vGvQhbycbNLhpgza+RCIegO1zKGmfO3w+ # bqxHdXs1KLkbZf6WE4M= # SIG # End signature block |