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 from Moc module # this also imports $global:defaultworkingDir Import-Module ((Get-Module "MOC" -ListAvailable | Sort-Object Version -Descending)[0].ModuleBase + "\common.psm1") $moduleName = "ArcHci" $moduleVersion = "0.2.26" $global:ArcHciModule = $moduleName $defaultTokenExpiryDays = 365 $global:kvaCtlFullPath = "C:\Program Files\AksHci\kvactl.exe" $global:sshPrivateKeyFile = "C:\ProgramData\kva\.ssh\logkey" $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}$" $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" $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 in 30min $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 = 7 $numberOfArcHciMocPrechecks = 4 $logPath = "" $logFile = "" $arcVMEnabledMsg = "Arc vm management successfully enabled!" 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 explictly 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 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 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" #Example if the proxy params are provided New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" -proxyServerHTTP "http://myproxy:8080" -proxyServerHTTPS "https://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=$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)] [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 } 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 = $global:defaultworkingDir ) if (-Not [string]::IsNullOrEmpty($k8snodeippoolstart) -and -Not [string]::IsNullOrEmpty($k8snodeippoolend)){ $verifyendIP = Test-K8snodeIPPoolRange -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend if(!$verifyendIP) { throw "$k8snodeippoolend $invalidaK8snodeendIP" } } 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 -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 -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 -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-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 } 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]$logDir = [System.Io.Path]::GetTempPath() ) $arcHciLogDir = $(Join-Path $logDir "archcilogs") # 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 | where { $_.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) { $volumePath = $volumes[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) #> 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") ) if ([String]::IsNullOrEmpty($logPath)) { $logPath = Get-ArcHciDefaultPath $logFile = Join-Path $logPath "ubercrud.log" } if ([String]::IsNullOrEmpty($logDir)) { $logDir = $logPath } $arcHciLogDir = $(Join-Path $logDir "archcilogs") 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 # 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 -logDir $logDir -ip $ip -kvaTokenPath $kvaTokenPath } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } az.cmd version > "$($arcHciLogDir)\cliversions.txt" Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Compressing Logs...") $zipName = Join-Path $logDir "archcilogs.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 -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)) New-MocGalleryImage -name $imageName -location $mocLocation -imagePath $result -container "MocStorageContainer" 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 #> 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" Invoke-Expression "$kubectlPath patch secret controllerconfig -p '{\""data\"":{\""LoginString\"":\""$loginString\""}}' -n moc-operator-system" 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") $LogMessage = "$Stamp $LogString" $LogMessage >> $logFile } 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 Register-ResourceProviders -subscriptionID $subscriptionID 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-Output "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-Output "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 imageDir Optional parameter. Image Directory Path .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 -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] $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)] [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 Write-Log "Mocconfig: $mocConfig" 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)) { Write-Log "Running Join-Path -Path $volumePath -ChildPath 'WorkingDirectory'" $workingDir = (Join-Path -Path $volumePath -ChildPath "WorkingDirectory") } if ([String]::IsNullOrEmpty($imageDir)) { Write-Log "Running Join-Path -Path $volumePath -ChildPath 'ImageStore'" $imageDir = (Join-Path -Path $workingDir -ChildPath "ImageStore") } if ([String]::IsNullOrEmpty($cloudConfigLocation)) { Write-Log "Running Join-Path -Path $volumePath -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 $_ Write-Log "Uninstalling Moc..." Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocfailed" -message "Install MOC Failed - Exception" Uninstall-Moc throw $_ } Write-Log "Running Get-ArcHciMoc" $mocConfig = Get-ArcHciMoc -correlationId $correlationId Write-Log "Mocconfig: $mocConfig" if ($mocConfig.installState -eq 'Installed') { Write-Output "Moc version $($mocConfig.version) installed successfully!" Write-Log "Moc version $($mocConfig.version) installed successfully" } else { Write-Output "Moc install state: $($mocConfig.installState)" Write-Log "Moc install state: $($mocConfig.installState)" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocfailed" -message "Install MOC Failed - InvalidState" } } else { Write-Log "Terminating Moc Installation. $($mocTestRes[$mocTestRes.length - 1].Details)" Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Install MOC Precheck Failed" throw $($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). .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 ) $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 return $response } 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. #> param ( [String]$command, [String]$arguments, [Switch]$ignoreError, [Switch]$logOutput, [Switch]$showOutputAsProgress, [String]$progressActivity ) $previousErrorAction = $errorActionPreference $errorActionPreference = "Continue" try { if ($showOutputAsProgress.IsPresent) { $errorResult = $($result = (& $command $arguments.Split(" ") | ForEach-Object { $status = $_ -replace "`t", " - " })) 2>&1 } elseif ($logOutput.IsPresent) { $errorResult = $($result = (& $command $arguments.Split(" ") | ForEach-Object { Write-Log "$_"; return $_ })) 2>&1 } else { $errorResult = $($result = (& $command $arguments.Split(" "))) 2>&1 } $previousExitCode = $LASTEXITCODE } catch { if ($ignoreError.IsPresent) { return } Write-Log $_ throw } finally { $errorActionPreference = $previousErrorAction } if ($null -ne $errorResult -and -not ($errorResult.Exception.Message -match "Please let us know how we are doing") ` -and -not ($errorResult.Exception.Message -match "The installed extension '.+' is experimental") ` -and -not ($errorResult.Exception.Message -match "The installed extension '.+' is in preview.") ` -and -not ($errorResult.Exception.Message -match "Setting GA feature gate arcmonitoring=true") ` -and -not ($errorResult.Exception.Message -match "Command group '.+' is in preview and under development.") ` -and -not ($errorResult.Exception.Message -match "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.")) { $stack = Get-PSCallStack #An error message was returned, just throw that message $errMessage = "$command $arguments returned a non empty error stream [$($errorResult.ToString())] at [$($stack)]" throw $errorResult } 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 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 .OUTPUTS N/A .EXAMPLE New-ArcHciApplianceConfigs -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 -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 = "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 ) 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 -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" New-ArcHciConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -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 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 "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 .OUTPUTS N/A .EXAMPLE Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -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)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir ) 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" 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 Write-Log "Running az arcappliance create hci --config-file ""$workingDir\hci-appliance.yaml"" --kubeconfig ""$workingDir\kubeconfig""" Write-Output "Creating Arc resource bridge...." Invoke-ArcHciAzCommand "arcappliance create hci --config-file ""$workingDir\hci-appliance.yaml"" --kubeconfig ""$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 "Exiting Install-ArcHciResourceBridge" } 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 .OUTPUTS N/A .EXAMPLE Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -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)] [ValidateScript({ if ([string]::IsNullOrEmpty($_)) { return $true } if ($_ -match $space) { throw $spaceErrorForFolderFilePath } if (-not ($_ | Test-Path)) { throw $fileFolderPathError } return $true })] [string] $workingDir ) 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" 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 } catch { Write-Log $_ Write-Log "Exiting Remove-ArcHciResourceBridge" return } $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.provisioningState)") } 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 $timeout) { Write-Log "Waiting for ""az arcappliance create hci --config-file $workingDir\hci-appliance.yaml --kubeconfig $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 sucessfully" 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: $timeout" } throw ("Timed out deploying appliance. Deploy state: $($arcRBDetails.provisioningState)") } 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 workingDir Optional parameter. Working Directory Path .OUTPUTS N/A .EXAMPLE Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $releaseTrain -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] $ssvmExtensionName = "vmss-hci", [parameter(Mandatory = $false)] [string] $ssvmExtVersion, [parameter(Mandatory = $false)] [string] $releaseTrain = "stable", [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 "Entered Install-ArcHciVMExtension" if ([String]::IsNullOrEmpty($workingDir)) { $mocConfig = Get-ArcHciMoc $workingDir = $($mocConfig.workingDir) } Write-Log "Installing SSVM k8s extension" Write-Output "Installing SSVM k8s extension" $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 Microsoft.CustomLocation.ServiceAccount=arc-extensions --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 $timeout)) { Write-Log "Waiting for ""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 Microsoft.CustomLocation.ServiceAccount=arc-extensions --config-protected-file ""$workingDir\Appliance\hci-config.json"" --release-train $releaseTrain"" 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 sucessfully installed" Write-Output "SSVM k8s extension sucessfully 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: $timeout" } throw ("Timed out installing k8s-extension. Installation state: $($arcVMExtDetails.provisioningState)") } 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 .OUTPUTS N/A .EXAMPLE Uninstall-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 ) Write-Log "Entered Uninstall-ArcHciVMExtension" Write-Log "Deleting ArcHci VM Extension" Write-Output "Deleting ArcHci VM Extension" if ([String]::IsNullOrEmpty($ssvmExtensionName)) { Write-Log "Running az k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup" $installedExtList = (Invoke-ArcHciAzCommand -arguments " k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup") | ConvertFrom-Json foreach ($ext in $installedExtList) { if ($ext.extensionType -eq "microsoft.azstackhci.operator") { $ssvmExtensionName = $ext.name break } } } 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)") } 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 #> 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 arc-extensions --host-resource-id $($applianceDetails.id) --cluster-extension-ids ""/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.ResourceConnector/appliances/$rn/providers/Microsoft.KubernetesConfiguration/extensions/$en""" 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 Write-Log "Archci custom location details: $arcCusLocDetails" if ($arcCusLocDetails.provisioningState -eq "Succeeded") { Write-Log "Custom location sucessfully created" Write-Output "Custom location sucessfully 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 { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createcustomlocationfailed" -message "Failed to create CustomLocation" throw $_ } 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 #> 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, [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 { Write-Log "Deleting custom location" Write-Output "Deleting custom location" if ([String]::IsNullOrEmpty($customLocationName)) { Write-Log "Running az customlocation list --subscription $subscriptionID --resource-group $resourceGroup" $custLocList = (Invoke-ArcHciAzCommand -arguments " customlocation list --subscription $subscriptionID --resource-group $resourceGroup") | ConvertFrom-Json $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName foreach ($custLoc in $custLocList) { if ($custLoc.hostResourceId -eq $applianceDetails.id) { $customLocationName = $custLoc.name break } } } 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 Write-Log "Archci custom location details: $arcCusLocDetails" 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 { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletecustomlocationfailed" -message "Failed to delete CustomLocation" throw $_ } 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 .OUTPUTS N/A .EXAMPLE Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hydaksExtVersion -releaseTrain $releaseTrain #> 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" ) Write-Log "Installing HybridAKS extension" Write-Output "Installing HybridAKS extension" $K8SExtensionCreateCmd = "k8s-extension create --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --extension-type Microsoft.HybridAKSOperator --config Microsoft.CustomLocation.ServiceAccount=default --release-train $releaseTrain" if (-Not [String]::IsNullOrEmpty($hydaksExtVersion)) { $K8SExtensionCreateCmd += " --version $hydaksExtVersion" $K8SExtensionCreateCmd += " --auto-upgrade $false" }else{ $K8SExtensionCreateCmd += " --auto-upgrade $true" } if(Test-IsHCIMachine){ $hciClusterId = (Get-AzureStackHci).AzureResourceUri $K8SExtensionCreateCmd += " --configuration-settings HCIClusterID=$hciClusterId" } Write-Log "Running $K8SExtensionCreateCmd" Invoke-ArcHciAzCommand $K8SExtensionCreateCmd -ignoreWarning $timer = [Diagnostics.Stopwatch]::StartNew() $timeSinceStart = 0 $hybridAksInstallState = "" while (($timeSinceStart -lt $timeout)) { 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: $timeout" } throw ("Timed out installing k8s-extension. Installation state: $hybridAksInstallState") } 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 #> 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, [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 Write-Log "Archci parameters: $arcHciParameters" $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 { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletek8sextfailed" -message "Failed to remove k8s extension" throw $_ } 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 .OUTPUTS N/A .EXAMPLE Uninstall-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 ) Write-Log "Entered Uninstall-ArcHciHybridAKSExtension" Write-Log "Removing HybridAKS Extension" Write-Output "Removing HybridAKS Extension" if ([String]::IsNullOrEmpty($hydaksExtensionName)) { Write-Log "Running az k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup" $installedExtList = (Invoke-ArcHciAzCommand -arguments " k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup") | ConvertFrom-Json foreach ($ext in $installedExtList) { if ($ext.extensionType -eq "microsoft.hybridaksoperator") { $hydaksExtensionName = $ext.name break } } } 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)") } 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 #> 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, [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 { if ([String]::IsNullOrEmpty($customLocationName)) { Write-Log "Running az customlocation list --subscription $subscriptionID --resource-group $resourceGroup" $custLocList = (Invoke-ArcHciAzCommand -arguments " customlocation list --subscription $subscriptionID --resource-group $resourceGroup") | ConvertFrom-Json foreach ($custLoc in $custLocList) { if ($custLoc.namespace -eq "arc-extensions") { $customLocationName = $custLoc.name break } } } 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 arc-extensions --host-resource-id $($applianceDetails.id)" Invoke-ArcHciAzCommand "customlocation update --resource-group $resourceGroup --name $customLocationName --location $location --cluster-extension-ids $($hybridAKSExtDetails.id) $($ssvmExtDetails.id) --namespace arc-extensions --host-resource-id $($applianceDetails.id)" -ignoreWarning Write-Log "$customLocationName successfully updated!" Write-Output "$customLocationName successfully updated!" Write-Log "Exiting Update-ArcHciCustomLocation" } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "updatecustomlocationfailed" -message "Failed to update CustomLocation" throw $_ } finally { Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName } } 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 ) Write-Log "Entered Get-ArcHciCustomLocation" try { Write-Log "Running az customlocation list --subscription $subscriptionID --resource-group $resourceGroup" $custLocList = (Invoke-ArcHciAzCommand -arguments " customlocation list --subscription $subscriptionID --resource-group $resourceGroup" -ignoreWarning) | ConvertFrom-Json if (-not[String]::IsNullOrEmpty($customLocationName)) { $res = $custLocList | Where-Object { $_.name -eq $customLocationName } } else { $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName Write-Log "Running $custLocList | Where-Object { $_.hostResourceId -eq $($applianceDetails.id) }" $res = $custLocList | Where-Object { $_.hostResourceId -eq $applianceDetails.id } } } catch { Write-Log $_ 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 ) 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 Write-Log "Archci parameters: $arcHciParameters" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName Register-ResourceProviders -subscriptionID $subscriptionID 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 ) Write-Log "Entered Get-ArcHciVMExtension" try{ Write-Log "Running az k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup" $installedExtList = (Invoke-ArcHciAzCommand -arguments "k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup" -ignoreWarning) | ConvertFrom-Json if (-not[String]::IsNullOrEmpty($ssvmExtensionName)) { $res = $installedExtList | Where-Object { $_.name -eq $ssvmExtensionName } }else { $res = $installedExtList | Where-Object { $_.extensionType -eq "microsoft.azstackhci.operator" } } } 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 ) Write-Log "Entered Get-ArcHciHybridAKSExtension" try{ Write-Log "Running az k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup" $installedExtList = (Invoke-ArcHciAzCommand -arguments " k8s-extension list --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup" -ignoreWarning) | ConvertFrom-Json if (-not[String]::IsNullOrEmpty($hydaksExtensionName)) { $res = $installedExtList | Where-Object { $_.name -eq $hydaksExtensionName } }else { $res = $installedExtList | Where-Object { $_.extensionType -eq "microsoft.hybridaksoperator" } } } 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 list --resource-group $resourceGroup --subscription $subscriptionID" $arcRBList = (Invoke-ArcHciAzCommand -arguments "arcappliance list --resource-group $resourceGroup --subscription $subscriptionID" -ignoreWarning) | ConvertFrom-Json if (-not[String]::IsNullOrEmpty($resourceName)) { $res = $arcRBList | Where-Object { $_.name -eq $resourceName } } } catch { Write-Log $_ throw $_ } if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0){ $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 $_ $mocConfig = [pscustomobject]@{ 'installState' = 'NotInstalled'; } Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "getmocfailed" -message "Failed To Retrieve MOC config" } 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, [parameter(Mandatory = $false)] [string] $hydaksExtensionName, [parameter(Mandatory = $false)] [string] $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 Write-Log "Archci parameters: $arcHciParameters" $subscriptionID = $arcHciParameters.SubscriptionID $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName $location = $arcHciParameters.Location Register-ResourceProviders -subscriptionID $subscriptionID 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 Write-Log "Archci custom location details: $arcCusLocDetails" $arcHciMgmtDetails.Add("CustomLocation",$arcCusLocDetails) Write-Log "Exiting Get-ArcHciMgmt" return $arcHciMgmtDetails } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "getmgmtfailed" -message "Get ArcHci Mgmt Failed" throw $_ } 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 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 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 .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 -skip_prechecks -skip_cleanup #> 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)] [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] $skip_prechecks, [Parameter(Mandatory = $false)] [switch] $skip_cleanup, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) $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 Write-Log "Mocconfig: $mocConfig" 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 Write-Log "Archci parameters: $arcHciParameters" $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" throw $_ } 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 -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" New-ArcHciApplianceConfigs -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 Write-Log "Running Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir" Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir } 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 $_ Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installarbfailed" -message "Failed to install Azure resoruce bridge" if (-not $skip_cleanup.IsPresent) { try { Write-Log "Collecting ARC appliance Logs..." Write-Output "Collecting ARC appliance Logs..." Get-ArcHciApplianceLogs -resourceGroup $resourceGroup -resourceName $resourceName } catch { Write-Log $_ } 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 } throw $_ } # 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") { Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName 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 -workingDir $workingDir" Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $ssvmReleaseTrain -workingDir $workingDir } 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 $_ Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installvmextfailed" -message "Failed to install Azure resoruce bridge" if (-not $skip_cleanup.IsPresent) { 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 } throw $_ } try { Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName Write-Log "Arc VM extension details: $arcVMExtDetails" if ($arcVMExtDetails.provisioningState -eq "Succeeded") { $ssvmExtensionName = $arcVMExtDetails.name } else { $ssvmExtensionName = "" } Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName Write-Log " Archci custom location details: $arcCusLocDetails" if ($arcCusLocDetails.provisioningState -eq "NotInstalled") { Write-Log "Running New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $ssvmExtensionName -customLocationName $customLocationName" 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 $_ Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createcustomlocationfailed" -message "Create custom location failed during Mgmt installation" if (-not $skip_cleanup.IsPresent) { Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName 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 } throw $_ } try { Write-Log "Running Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $hybridaksReleaseTrain -customLocationName $customLocationName" Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $hybridaksReleaseTrain -customLocationName $customLocationName -correlationId $correlationId } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Install hybrid AKS failed during Mgmt installation" if (-not $skip_cleanup.IsPresent) { Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName" Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName Write-Log "Running Uninstall-ArcHciHybridAKS -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName" Uninstall-ArcHciHybridAKS -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName 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 } throw $_ } } 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 { Write-Log "Terminating ArcHciMgmt Installation. At least one of the prechecks required for ArcHciMgmt installation failed." Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "ArcHciMgmt precheck failed" throw "Terminating ArcHciMgmt Installation. At least one of the prechecks required for ArcHciMgmt installation failed." } } else { Write-Log "Terminating ArcHciMgmt Installation. Moc is not installed, please install Moc first" Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig throw "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 .OUTPUTS N/A .EXAMPLE Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -customLocationName $customLocationName -skip_cleanup #> 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, [Parameter(Mandatory = $false)] [switch] $skip_cleanup, [parameter(Mandatory = $false)] [string] $correlationId = [System.Guid]::NewGuid().ToString() ) $global:ProgressPreference = 'SilentlyContinue' Write-Log "Entered Install-ArcHciHybridAKS" $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-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 Write-Log "Archci parameters: $arcHciParameters" $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" Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installk8sextfailed" -message "Failed to create CustomLocation" Write-Log $_ throw $_ } try { Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" $arcHybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName 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 -customLocationName $customLocationName" $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName Write-Log " Archci custom location details: $arcCusLocDetails" 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 } 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 } 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." } } else { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installk8sextfailed" -message "Install K8s extension did not return success result" } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createcustomlocationfailed" -message "Failed to create CustomLocation as part of hybrid AKS install" Write-Log $_ if (-not $skip_cleanup.IsPresent) { Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName" Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName } throw $_ } } 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, [parameter(Mandatory = $false)] [string] $hydaksExtensionName, [parameter(Mandatory = $false)] [string] $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 Write-Log "Archci parameters: $arcHciParameters" $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 Write-Log " Archci custom location details: $arcCusLocDetails" if ($arcCusLocDetails.provisioningState -eq "Succeeded") { $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" throw $_ } 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 -eq "Succeeded") { $ssvmExtensionName = $arcVMExtDetails.name Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName" Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName } } catch { Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletevmextfailed" -message "Failed to delete VM extension" throw $_ } 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 -eq "Succeeded"){ $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" throw $_ } 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 -eq "Succeeded") { 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" throw $_ } 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 list --query ""[?name=='$resourceGroup'].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." Write-Error "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName does not exist." return $testReport } if ($switchObj.SwitchType -ne "External"){ $testReport.TestResult = "Failed" $testReport.Details = "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName exists 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 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() Get-ClusterSharedVolume -ErrorAction Stop | 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' = "" } $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){ $testReport.TestResult = "Failed" $testReport.Details = "No cluster node meets the minimum memory and vCPU requirements At least 8 GB of memory is required and at least 4 vCPU's are required." Write-Error "No cluster node meets the minimum memory and vCPU requirements At least 8 GB of memory is required and at least 4 vCPU's are required." } return $testReport } function Test-ArcHciMocPowershellModulesExists{ $testReport = [pscustomobject]@{ 'TestName' = 'Validate Powershell Module Installation'; 'TestResult' = "Succeeded"; 'Details' = "" } #update once AzStackHci Module has been created $installed = Get-Module 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" -logOutput } 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' = "" } try { Write-Log "Running New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -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 -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 = "wsscloudagent 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 confirmt 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 } 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 } 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 } 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 } 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 "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 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 #> 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)] [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 } 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 } 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 } 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 } else { Write-Host "Test Succeeded" -ForegroundColor Green } #az requirements test Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 5,$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 } else { Write-Host "Test Succeeded" -ForegroundColor Green } #resource bridge ip address range check Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 6,$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 } else { Write-Host "Test Succeeded" -ForegroundColor Green } #ip address test Write-Host '===============================================================================' -ForegroundColor White $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 7,$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 } 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 'All Arc Mgmt precheck tests were not successfull' -ForegroundColor Red } else { Write-Host 'All Arc Mgmt precheck tests were successfull' -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 -resourceGroup $resourceGroup -resourceName $resourceName -ip $ip -logDir $logDir -kvaTokenPath $kvaTokenPath #> param ( [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 = "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]$logDir, [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") ) Test-ArcHciAzRequirements > $null if ([String]::IsNullOrEmpty($logDir)) { $logDir = Get-ArcHciDefaultPath } $arcHciLogDir = $(Join-Path $logDir "archcilogs") if (-not ($arcHciLogDir | Test-Path)) { New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null } try { $mocConfig = Get-MocConfig } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } $arcHciParameters = Get-ArcHciParameters -resourceGroup $resourceGroup -resourceName $resourceName $resourceGroup = $arcHciParameters.ResourceGroup $resourceName = $arcHciParameters.ResourceName try { #Downloading logkey Write-Log "Running az arcappliance get-credentials --resource-group $resourceGroup --name $resourceName" Invoke-ArcHciAzCommand "arcappliance get-credentials --resource-group $resourceGroup --name $resourceName" -ignoreWarning | Out-Null } catch { $_ >> "$($arcHciLogDir)\commandlogs.txt" } $cloudFqdn = $mocConfig.cloudFqdn $ip = $(Get-ArcHciFirstControlPlaneNodeIp -logDir $logDir) try { Invoke-ArcHciAzCommand "arcappliance logs hci --ip $ip --out-dir $arcHciLogDir --loginconfigfile $kvaTokenPath --cloudagent $cloudFqdn" -ignoreWarning } 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 } # SIG # Begin signature block # MIInlgYJKoZIhvcNAQcCoIInhzCCJ4MCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAZvu/eDG940RBP # d9owL+Wz7ZAMtmMGVMHCEts8qPMcxaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 # esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU # p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 # 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm # WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa # +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq # jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk # mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 # TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 # kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d # hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM # pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh # JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX # UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir # IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 # 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A # Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H # tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGXYwghlyAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJmXLh6xO1Px9NXxAkRlMgq7 # 05ca9kf7cOkeGQ/JbWOTMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAXkYAZfY9pmutni56CvLEoci6epHpdlOL+xgXR8WOOR+vDESrxB9vgD1P # 1GGdFi+u0FbZSST+MtNNy5oFPY5rnEJJG5Ei9uOa0JWn/qyJzAe7Ypss9TmyE808 # 84+pDAfnl2trG+wVQhDCAw0/c3QIohL8dTwSPgknNgxfX+7jb9l5y0YN2YmAobDc # PMX+Ww0fZSOkKIOKoW90ERj30+y3Iy+Pr2AAJeYPY+WX/CVliy+UlZ0yAMZCUF2U # d8LiVSdp3u2sCm/D2eyaO3DnJyFpTxxS8PiVUi50YGum0YemWOHiiTPRYShRyXpM # D6RucaHbaJ/NoJOsVcygvMA7Rup0+qGCFwAwghb8BgorBgEEAYI3AwMBMYIW7DCC # FugGCSqGSIb3DQEHAqCCFtkwghbVAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFRBgsq # hkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCC8C4mg+nLNO44YDRuJgcCjECjzTKU1P75SUNgB74imxwIGZMl/0KZ6 # GBMyMDIzMDgwMzE2MTY0NS4wODdaMASAAgH0oIHQpIHNMIHKMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0OUJDLUUz # N0EtMjMzQzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCC # EVcwggcMMIIE9KADAgECAhMzAAABwFWkjcNkFcVLAAEAAAHAMA0GCSqGSIb3DQEB # CwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIyMTEwNDE5MDEy # NVoXDTI0MDIwMjE5MDEyNVowgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMx # JjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjQ5QkMtRTM3QS0yMzNDMSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAvO1g+2NhhmBQvlGlCTOMaFw3jbIhUdDTqkaQhRpdHVb+ # huU/0HNhLmoRYvrp7z5vIoL1MPAkVBFWJIkrcG7sSrednyZwreY207C9n8XivL9Z # BOQeiUeL/TMlJ6VinrcafbhdnkNO5JDlPozC9dGySiubryds5GKtu69D1wNat9DI # Ql6alFO6pncZK4RIzfv+KzkM7RkY3vHphV0C8EFUpF+lysaGJXFf9QsUUHwj9XKW # Hfc9BfhLoCReXUzvgrspdFmVnA9ATYXmidSjrshf8A+E0/FpTdhXPI9XXqsZDHBq # r7DlYoSCU3lvrVDRu1p5pHHf7s3kM16HpK6arDtY3ai1soASmEpv3C2N/y5MDBAp # Dd4SpSkLMa7+6es/daeS7zdH1qdCa2RoJPM6Eh/6YmBfofhfLQofKPJl34ALlZWK # 5AzVtFRNOXacoj6MAG2dT8Rc5fpKCH1E3n7Zje0dK24QVfSv/YOxw52ECaMLlW5P # hHT3ZINNaCmRgcHCTClOKzC2FOr03YBc2zPOW6bIVdXloPmBMVaE+thXqPmANBw0 # YsncaOkVggjDb5O5VqOp98MklHpJoJI6pk5zAlx8/OtC7FutrdtYNUC6ykXzMAPF # uYkWGgx/W7A0itKW8WzYzwO3bAhprwznouGZmRiw2k8pen80BzqzdyPvbzTxQsMC # AwEAAaOCATYwggEyMB0GA1UdDgQWBBQARMZ480jwpK3P6quVWUEJ0c30hTAfBgNV # HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU # aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG # CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV # HRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IC # AQCtTh0EQn16kKQyCeVk9Vc10m6L0EwLRo3ATRouP7Yd2hWeEB2Y4ZF4CJKe9qfX # WGJKzV7tMUm6DAsBKYH/nT+8ybI8uJiHGnfnVi6Sh7gFjnTpfh1j1T90H/uLeoFj # pOn/+eoCoJmorW5Gb2ezlTlo5I0kNAubxtCxqbLizuPNPob8kRAKQgv+4/CC1Jmi # UFG0uKINlKj9SsHcrWeBBQHX62nNgziIwT44JqHrA02I6cmQAi9BZcsf57OOLpRY # lzoPH3x/+ldSySXAmyLq2uSbWtQuD84I/0ZgS/B5L3ewqTdiE1KbKX89MW5JqCK/ # yI/mAIQammAlHPqU9eZZTMPOHQs0XrpCijlk+qyo2JaHiySww6nuPqXzU3sEj3VW # 00YiVSayKEu1IrRzzX3La8qe6OqLTvK/6gu5XdKq7TT852nB6IP0QM+Budtr4Fbx # 4/svpKHGpK9/zBuaHHDXX5AoSksh/kSDYKfefQIhIfQJJzoE3X+MimMJrgrwZXlt # b6j1IL0HY3qCpa03Ghgi0ITzqfkw3Man3G8kB1Ql+SeNciPUj73Kn2veJenGLtT8 # JkUM9RUi0woO0iuY4tJnYuS+SeqavXUOWqUYVY19FIr1PLqpmWkbrO5xKjkyOHoA # mLxjNbKjOnkAwft+1G00kulKqzqPbm+Sn+47JsGQFhNGbTCCB3EwggVZoAMCAQIC # EzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoX # DTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC # 0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VG # Iwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP # 2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/P # XfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361 # VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwB # Sru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9 # X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269e # wvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDw # wvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr # 9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+e # FnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAj # BgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+n # FV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEw # PwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9j # cy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3 # FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAf # BgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNS # b29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl # ckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4Swf # ZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTC # j/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu # 2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/ # GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3D # YXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbO # xnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqO # Cb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I # 6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0 # zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaM # mdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNT # TY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLOMIICNwIBATCB+KGB0KSBzTCByjEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWlj # cm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBF # U046NDlCQy1FMzdBLTIzM0MxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVABAQ7ExF19KkwVL1E3Ad8k0Peb6doIGD # MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF # BQACBQDodfh6MCIYDzIwMjMwODAzMTc1NzE0WhgPMjAyMzA4MDQxNzU3MTRaMHcw # PQYKKwYBBAGEWQoEATEvMC0wCgIFAOh1+HoCAQAwCgIBAAICAsgCAf8wBwIBAAIC # Ej0wCgIFAOh3SfoCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK # MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAz+t7Mdx/Z # WGt4kFenDs5J6o4f5wc5W7H17d36B0kw08V65x+MmJZD0TF5hrh0hxNItUlF0rZi # QJWglRX7ELsQ2w6VmdjX990+E2eqgh6NjDuj0qt0B/qXLKdAEqwkpWBVpndZtP/v # k2RVUXDsXPJp11iXDq8mUBkRoj5EXnusvDGCBA0wggQJAgEBMIGTMHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABwFWkjcNkFcVLAAEAAAHAMA0GCWCG # SAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZI # hvcNAQkEMSIEIAHPdkjroZMeTAw9fnZLw4yJI9dEDnhja1epsLaw7GwSMIH6Bgsq # hkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgWvFYolIIXME0zK/W6XsCkkYX7lYNb9yA # 8JxwY04Pk08wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT # MwAAAcBVpI3DZBXFSwABAAABwDAiBCCeYIs6vw9UULez73jG13lJkgNeu7GIdsOO # cJPGTXPp4zANBgkqhkiG9w0BAQsFAASCAgB6PUMRlyGnJQ8uFgl9+7kWyBPegKuB # qi37dPi+aOxqfqwPEcDBd+3bsnOO+6J0moKAkPIMBgi08loUKAWlvF80RtsnLJDb # 3zWckfj3SXY2n2hPQd/0+U2MwW+P6SC4CnkcGMhg9TMwy476PpGUBppw/oM0LguX # 4TZAXKNa8khyI7YU6gl+08a210GifTRKXCma8M/gAoNTScMmedXeoEb+aPhy/cIl # oxo1jGnUKZz6LD4uBzQDXgGRQFUoLPxL/gDHn51l1H1YLQNAfssDT2kTA6KJrdlA # SshvjIZTr8kDizCTsUUdFVKnZEQ7UhSDHvF912dGbczweVcMUF52kphqeDc1v/R9 # m/A2YUJijs0JPenaLSiU+iJ0OfOXiLz5E4+xaYst7eewZKUgaClfKHOHp/v0JxvQ # ZHhYEFag2QX/x7XwU3P7cuEw9Y8cnb0UCynghwwr455qRkeoEisUUEOcMF4xRQIP # puH3Fy6yU0cHrsDg6mnaAJpI7W+yoeiUNWvjD1HXB6h7bgYztgpJjbaLNHYSiBjx # o+wZsUGilHas08Vs7OPxBGIw/3GzHNvYiz2ZH4QOPG9gt1/zgNzVc1TuSsPjFW1S # VkGh/uy8u/eneWspWm09gU3+p/oaR3YbCe8AujC81fb8c5LUvI31WHdpYy0K0r6+ # hbf3wt9/pQIRuw== # SIG # End signature block |