├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── configs └── basiccluster_staticnetwork.yaml ├── coreos-hyperv.psd1 ├── files └── config2_base.vhdx.bz2 └── modules ├── coreos-hyperv-config.psm1 ├── coreos-hyperv-functions.psm1 ├── coreos-hyperv-images.psm1 └── coreos-hyperv.psm1 /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Paul Shirley 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | coreos-hyperv 2 | ============= 3 | 4 | These are some basic functions to help bootstrap a coreos cluster on Hyper-V. 5 | 6 | ## Prerequisites ## 7 | Windows 8.1 or Windows Server 2012 R2 and Hyper-V turned on. 8 | Internet Connection. 9 | At least one virtual switch created in hyper-v. 10 | Bunzip. This is used to decompress the coreos images. There is a version of bunzip packaged with msysgit which is used if git is installed. Otherwise you can download [Bunzip for Windows](http://gnuwin32.sourceforge.net/packages/bzip2.htm) and add it to your path. 11 | 12 | (Although the script will work in Windows 8 and Windows Server 2012 I haven't been able to test coreos running on those platforms. Some users have had issues running in the past but this might have changed now that coreos provides images for Hyper-v.) 13 | 14 | ## Installation ## 15 | ### Clone to PSModule Path ### 16 | From powershell run the following: 17 | 18 | ``` 19 | git clone https://github.com/paulshir/coreos-hyperv "$($env:home)\Documents\WindowsPowershell\Modules\coreos-hyperv" 20 | Import-Module coreos-hyperv 21 | ``` 22 | 23 | ## Create a basic cluster with a static network configuration ## 24 | To create a basic cluster with a static network configuration we can do the following. First we create the network config. The following is all run from an administrator powershell prompt. 25 | 26 | ``` 27 | $NetworkConfig = New-CoreosNetworkConfig -SwitchName 'External Virtual Switch 1' -Gateway '192.168.1.1' -SubnetBits 24 -RangeStartIP '192.168.1.200' -DNSServers @('8.8.8.8', '8.8.4.4') 28 | ``` 29 | 30 | This creates a powershell variable with all the network info required to modify the configuration files with. 31 | 32 | The next thing you need to do is to add your ssh public key to the configuration. For this example we will be using the config/basiccluster_staticnetwork.yaml file. You can generate a key with puttygen and insert the public key into the config (line 5). Now with the network config and the config file ready we can create the cluster. 33 | 34 | ``` 35 | New-CoreosCluster -Name coreos-basiccluster0 -Count 3 -NetworkConfigs $NetworkConfig -Channel Alpha -Config .\coreos-hyperv\configs\basiccluster_staticnetwork.yaml | Start-CoreosCluster 36 | ``` 37 | 38 | The following commands can now be used to stop and start a cluster. 39 | 40 | ``` 41 | $cluster = Get-CoreosCluster -ClusterName coreos-basiccluster0 42 | $cluster | Stop-CoreosCluster 43 | $cluster | Start-CoreosCluster 44 | ``` 45 | 46 | It is also good to ssh into the VMs and check if the cluster vms are talking to each other. You can test this by running the command `etcdctl set /foo bar`. This command should fail if the cluster isn't working properly but if it works you can ssh into another VM and run `etcdctl get /foo` to see if it has propagated. The result should be `bar` 47 | 48 | ## Configuration ## 49 | ### General Configuration ### 50 | Included in the script are some basic template replacement handles to ease the generation of config files. This should make it easier to create configurations that can be used across multiple machines. 51 | 52 | The Handles that are available are listed here. 53 | 54 | `{{VM_NAME}}` The name of the vm. 55 | `{{VM_NUMBER}}` The number of the VM in the cluster. 56 | `{{VM_NUMBER_00}}` The number of the VM prefixed with a 0 if it is less that 10. 57 | `{{CLUSTER_NAME}}` The name of the cluster. 58 | `{{ETCD_DISCOVERY_TOKEN}}` Each coreos cluster generates a discovery token. This can be added to the config here. 59 | 60 | ### Network configuration ### 61 | In addition to the general configuration there is also the ability to configure network settings for multiple adapters. 62 | 63 | The following handles can be used for configuring networks where X is replaced with the index of the network settings. For example for the first network configuration X would be replaced with 0 and for the second network configuration X would be replaced with 1. 64 | 65 | `{{IP_ADDRESS[NET_X]}}` The IP Address for Network Config X. (IP Address is determined from the VM Number and the Start IP Address of the network config). 66 | `{{GATEWAY[NET_X]}}` The gateway for Network Config X. 67 | `{{DNS_SERVER_Y[NET_X]}}` The DNS Server Y for Network Config X. (Each DNS Server is represented by it's index Y). 68 | `{{SUBNET_BITS[NET_X]}}` The count of 1 bits in the subnet mask for Network Config X. 69 | 70 | ## Troubleshooting ## 71 | ### Networking ### 72 | I've found the easiest way for networking is to set up a seperate VM to act as a NAT. To do this you can use something like Windows Server or ClearOS. This way you have more control over what ip addresses your VMS are assigned etc. This also means that even if you are on different networks (i.e. when you are on a laptop in a different location) the connections and cluster will continue to work. 73 | 74 | ### Modules Functions ### 75 | To see the functions of this powershell modules you can use the `Show-Command` powershell function. You can also find out how to use the functions using `Get-Help `. This will give you a description on all the parameters and options for the functions. 76 | -------------------------------------------------------------------------------- /configs/basiccluster_staticnetwork.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | --- 4 | hostname: {{VM_NAME}} 5 | ssh_authorized_keys: 6 | - ssh-rsa INSERT_PUBLIC_KEY_HERE 7 | coreos: 8 | etcd2: 9 | name: {{VM_NAME}} 10 | discovery: {{ETCD_DISCOVERY_TOKEN}} 11 | advertise-client-urls: http://{{IP_ADDRESS[NET_0]}}:2379 12 | initial-advertise-peer-urls: http://{{IP_ADDRESS[NET_0]}}:2380 13 | listen-client-urls: http://0.0.0.0:2379 14 | listen-peer-urls: http://{{IP_ADDRESS[NET_0]}}:2380 15 | fleet: 16 | public-ip: {{IP_ADDRESS[NET_0]}} 17 | metadata: cluster={{CLUSTER_NAME}},machine={{VM_NAME}} 18 | units: 19 | - name: static.network 20 | command: start 21 | content: | 22 | [Match] 23 | Name=eth0 24 | 25 | [Network] 26 | Address={{IP_ADDRESS[NET_0]}}/{{SUBNET_BITS[NET_0]}} 27 | DNS={{DNS_SERVER_0[NET_0]}} 28 | DNS={{DNS_SERVER_1[NET_0]}} 29 | Gateway={{GATEWAY[NET_0]}} 30 | - name: docker-tcp.socket 31 | command: start 32 | enable: true 33 | content: | 34 | [Unit] 35 | Descritption=Docker Socket for the API 36 | 37 | [Socket] 38 | ListenStream=2375 39 | BindIPv6Only=both 40 | Service=docker.service 41 | 42 | [Install] 43 | WantedBy=sockets.target 44 | - name: etcd2.service 45 | command: start 46 | - name: fleet.service 47 | command: start 48 | - name: docker.service 49 | command: start 50 | update: 51 | reboot-strategy: etcd-lock 52 | group: {{CHANNEL}} 53 | manage_etc_hosts: localhost 54 | write_files: 55 | - path: /etc/environment 56 | permissions: 0644 57 | content: | 58 | COREOS_PUBLIC_IPV4={{IP_ADDRESS[NET_0]}} 59 | COREOS_PRIVATE_IPV4={{IP_ADDRESS[NET_0]}} -------------------------------------------------------------------------------- /coreos-hyperv.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulshir/coreos-hyperv/b687ceaf916bce3ff9be7c6367f37494fa16d237/coreos-hyperv.psd1 -------------------------------------------------------------------------------- /files/config2_base.vhdx.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulshir/coreos-hyperv/b687ceaf916bce3ff9be7c6367f37494fa16d237/files/config2_base.vhdx.bz2 -------------------------------------------------------------------------------- /modules/coreos-hyperv-config.psm1: -------------------------------------------------------------------------------- 1 | ############################ 2 | ##### Public Functions ##### 3 | ############################ 4 | <# 5 | .SYNOPSIS 6 | Creates a config file with the coreos settings. 7 | .DESCRIPTION 8 | Outputs a config file with the specified settings based on a template config. 9 | .PARAMETER Path 10 | The path to the config file. 11 | .PARAMETER Destination 12 | The path to output the config file to. 13 | .PARAMETER VMName 14 | The name of the VM the config should have. 15 | {{VM_NAME}} will be replaced in the template config file with this value. 16 | .PARAMETER VMNumber 17 | The number of the VM in the Cluster (Starting at 0). 18 | {{VM_NUMBER}} will be replaced in the template config file with this value. 19 | This value will also be used in the assigning of IP Addresses for network config. 20 | .PARAMETER Channel 21 | The channel (e.g. alpha, beta, stable or master) to use. 22 | {{CHANNEL}} will be replaced in the template config with the lowercase version of this value. 23 | .PARAMETER EtcdDiscoveryToken 24 | The etcd discovery used so that machines can all join the same cluster. 25 | {{ETCD_DISCOVERY_TOKEN}} will be replaced in the template config with this value. 26 | .PARAMETER ClusterName 27 | The name of the cluster the virtual machine is in. 28 | {{CLUSTER_NAME}} will be replaced in the template config with this value. 29 | .PARAMETER NetworkConfigs 30 | This in an array of Network Config Objects. For each network config certain values will be 31 | replaced in the template config. 32 | {{IP_ADDRESS[NET_0]}} will be replaced with the calculated IP address from the VM Number and the 33 | starting IP address of NetworkConfig[0] 34 | {{GATEWAY[NET_0]}} will be replaced in the template config with the gateway for the first network config. 35 | {{DNS_SERVER_0[NET_0]}} will be replaced in the template config with the first DNS Server in the first network config. 36 | Multiple DNS Servers can be added as needed. 37 | {{SUBNET_BITS[NET_0]}} will be replaced in the template config with the number of bits in the subnet for the first network. 38 | .OUTPUTS 39 | The path that the destination file was saved to. 40 | #> 41 | Function New-CoreosConfig { 42 | [CmdletBinding()] 43 | Param ( 44 | [Parameter (Mandatory=$true)] 45 | [String] $Path, 46 | 47 | [Parameter (Mandatory=$true)] 48 | [String] $Destination, 49 | 50 | [Parameter (Mandatory=$true)] 51 | [Alias("Name")] 52 | [String] $VMName, 53 | 54 | [Parameter (Mandatory=$false)] 55 | [int] $VMNumber, 56 | 57 | [Parameter (Mandatory=$false)] 58 | [ValidateSet("Alpha","Beta","Stable","Master")] 59 | [String] $Channel, 60 | 61 | [Parameter (Mandatory=$false)] 62 | [String] $EtcdDiscoveryToken, 63 | 64 | [Parameter (Mandatory=$false)] 65 | [String] $ClusterName, 66 | 67 | [Parameter (Mandatory=$false)] 68 | [PSObject[]] $NetworkConfigs 69 | ) 70 | 71 | PROCESS { 72 | if (!(Test-Path $Path)) { 73 | throw "Config doesn't exist" 74 | } 75 | 76 | if (Test-Path $Destination) { 77 | Move-Item -Path $Destination -Destination "$Destination.$(Get-DateTimeStamp)_bak" 78 | } 79 | 80 | $vmnumber00 = $VMNumber.ToString("00") 81 | 82 | $cfg = Get-Content $Path | foreach { $_ -replace '{{VM_NAME}}', $VMName } 83 | if ($VMNumber -or $VMNumber -eq 0) { $cfg = $cfg | foreach { $_ -replace '{{VM_NUMBER}}', $VMNumber.ToString() } | foreach { $_ -replace '{{VM_NUMBER_00}}', $vmnumber00 } } 84 | if ($EtcdDiscoveryToken) { $cfg = $cfg | foreach { $_ -replace '{{ETCD_DISCOVERY_TOKEN}}', $EtcdDiscoveryToken } } 85 | if ($ClusterName) { $cfg = $cfg | foreach { $_ -replace '{{CLUSTER_NAME}}', $ClusterName } } 86 | if ($Channel) { $cfg = $cfg | foreach { $_ -replace '{{CHANNEL}}', $Channel.ToLower()}} 87 | 88 | for ($i=0; $i -lt $NetworkConfigs.Length; $i++) { 89 | $Network = $NetworkConfigs[$i] 90 | 91 | if ($Network.RangeStartIP) { 92 | $OffsetIP = Get-OffsetIPAddress -RangeStartIP $Network.RangeStartIP -Count $VMNumber 93 | $ReplaceText = "{{IP_ADDRESS\[NET_$($i)]}}" 94 | $cfg = $cfg | foreach { $_ -replace $ReplaceText, $OffsetIP } 95 | } 96 | 97 | if ($Network.Gateway) { 98 | $ReplaceText = "{{GATEWAY\[NET_$($i)]}}" 99 | $cfg = $cfg | foreach { $_ -replace $ReplaceText, $Network.Gateway } 100 | } 101 | 102 | if ($Network.DNSServers) { 103 | for ($j=0; $j -lt $Network.DNSServers.Length; $j++) { 104 | $ReplaceText = "{{DNS_SERVER_$($j)\[NET_$($i)]}}" 105 | $cfg = $cfg | foreach { $_ -replace $ReplaceText, $Network.DNSServers[$j] } 106 | } 107 | } 108 | 109 | if ($Network.SubnetBits) { 110 | $ReplaceText = "{{SUBNET_BITS\[NET_$($i)]}}" 111 | $cfg = $cfg | foreach { $_ -replace $ReplaceText, $Network.SubnetBits } 112 | } 113 | } 114 | 115 | $cfg | Out-File $Destination -Encoding ascii 116 | 117 | Get-Item $Destination 118 | } 119 | } 120 | 121 | <# 122 | .SYNOPSIS 123 | Create a coreos network config. 124 | .DESCRIPTION 125 | A method that makes it easy to create a network config that can be used in the creation of coreos clusters. 126 | .PARAMETER SwitchName 127 | The Hyper-V virtual switch to use for this network config. 128 | .PARAMETER RangeStartIP 129 | The starting IP address to be used in the cluster. 130 | .PARAMETER Gateway 131 | The network gateway that can be assigned to clusters. 132 | .PARAMETER SubnetBits 133 | The number of bits in the subnet mask for the configuration. 134 | .PARAMETER DNSServers 135 | An array of DNS Servers to use for the config. 136 | #> 137 | Function New-CoreosNetworkConfig { 138 | [CmdletBinding()] 139 | Param ( 140 | [Parameter (Mandatory=$true)] 141 | [String] $SwitchName, 142 | 143 | [Parameter (Mandatory=$false)] 144 | [String] $RangeStartIP, 145 | 146 | [Parameter (Mandatory=$false)] 147 | [String] $Gateway, 148 | 149 | [Parameter (Mandatory=$false)] 150 | [Int] $SubnetBits, 151 | 152 | [Parameter (Mandatory=$false)] 153 | [String[]] $DNSServers 154 | ) 155 | 156 | PROCESS { 157 | Get-VMSwitch -Name $SwitchName -ErrorAction:Stop | Out-Null 158 | $NetworkConfig = New-Object PSObject 159 | $NetworkConfig | Add-Member SwitchName $SwitchName 160 | if ($RangeStartIP) { $NetworkConfig | Add-Member RangeStartIP $RangeStartIP } 161 | if ($Gateway) { $NetworkConfig | Add-Member Gateway $Gateway } 162 | if ($SubnetBits) { $NetworkConfig | Add-Member SubnetBits $SubnetBits } 163 | if ($DNSServers) { $NetworkConfig | Add-Member DNSServers $DNSServers } 164 | 165 | Write-Output $NetworkConfig 166 | } 167 | } 168 | 169 | <# 170 | .SYNOPSIS 171 | Get a discovery token for etcd. 172 | .PARAMETER size 173 | Sets the number etcd instances to be used in the quorom. Machines added outside of this 174 | value will be proxy instances by default. See https://github.com/coreos/etcd for more information. 175 | Setting the default value to 3 to match the website. 176 | #> 177 | Function New-EtcdDiscoveryToken { 178 | Param( 179 | [Parameter (Mandatory=$false)] 180 | [Int] $Size = 3 181 | ) 182 | 183 | if ($Size -gt 9) { 184 | $Size = 9 185 | } 186 | 187 | if ($Size -le 0) { 188 | $Size = 0 189 | } 190 | 191 | $wr = Invoke-WebRequest -Uri "https://discovery.etcd.io/new?size=$Size" 192 | Write-Output $wr.Content 193 | } 194 | 195 | ############################ 196 | #### Protected Functions ### 197 | ############################ 198 | Function New-CoreosConfigDrive { 199 | Param( 200 | [Parameter (Mandatory=$true)] 201 | [String] $BaseConfigDrivePath, 202 | 203 | [Parameter (Mandatory=$true, ValueFromPipeline=$true)] 204 | [String] $ConfigPath 205 | ) 206 | 207 | PROCESS { 208 | $vhdLocation = "$ConfigPath.vhdx" 209 | 210 | Copy-Item -Path:$BaseConfigDrivePath -Destination:$vhdLocation 211 | $vhd = Mount-VHD -Path $vhdLocation -ErrorAction:Stop -Passthru | Get-Disk | Get-Partition | Get-Volume 212 | Start-Sleep -s 1 213 | 214 | & cmd /C "IF NOT EXIST `"$($vhd.DriveLetter):\openstack\latest`" (mkdir $($vhd.DriveLetter):\openstack\latest)" | Out-Null 215 | & cmd /C "copy `"$configPath`" $($vhd.DriveLetter):\openstack\latest\user_data" | Out-Null 216 | 217 | Dismount-VHD $vhdLocation | Out-Null 218 | 219 | Write-Output $vhdLocation 220 | } 221 | } 222 | 223 | ############################ 224 | #### Private Functions ##### 225 | ############################ 226 | <# 227 | .SYNOPSIS 228 | Gets an IP address offsetted from the original. 229 | Currently only works with the last octet. 230 | #> 231 | Function Get-OffsetIPAddress { 232 | [CmdletBinding()] 233 | Param ( 234 | [Parameter (Mandatory=$true)] 235 | [String] $RangeStartIP, 236 | 237 | [Parameter (Mandatory=$true, ValueFromPipeline="True")] 238 | [Int] $Count 239 | ) 240 | 241 | PROCESS { 242 | $StartIP = $RangeStartIP.Split('.') | foreach { $_ -as [int] } 243 | $StartIP[3] += $Count 244 | $OffesetIP = $StartIP -join '.' 245 | 246 | Write-Output $OffesetIP 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /modules/coreos-hyperv-functions.psm1: -------------------------------------------------------------------------------- 1 | ############################ 2 | #### External Functions #### 3 | ############################ 4 | <# 5 | .SYNOPSIS 6 | Gets a coreos files directory. 7 | .DESCRIPTION 8 | Gets the directory that information about coreos clusters and the virtual machines, 9 | and other files in relation to cluster generation are stored. 10 | .OUTPUTS 11 | Returns the directory object where the files are stored. 12 | #> 13 | Function Get-CoreosFilesDirectory { 14 | [CmdletBinding()] 15 | Param ( 16 | 17 | ) 18 | 19 | PROCESS { 20 | if ($env:COREOS_HYPERV_COREOS_FILES_DIR) { 21 | $dir = $env:COREOS_HYPERV_COREOS_FILES_DIR 22 | } else { 23 | $dir = Join-Path -Path $((Get-VMHost).VirtualMachinePath) "Coreos Files" 24 | } 25 | 26 | if (!(Test-Path $dir)) { 27 | New-Item $dir -Type directory 28 | } else { 29 | Get-Item $dir 30 | } 31 | } 32 | } 33 | 34 | ############################ 35 | ###### Get Functions ####### 36 | ############################ 37 | Function Get-DateTimeStamp { 38 | [CmdletBinding()] 39 | Param ( 40 | ) 41 | 42 | PROCESS { 43 | Get-Date -UFormat "%Y_%m_%d_%H_%M_%S" | Write-Output 44 | } 45 | } 46 | 47 | Function Get-ModuleFilesDirectory { 48 | [CmdletBinding()] 49 | Param ( 50 | ) 51 | 52 | PROCESS { 53 | (Get-Module coreos-hyperv).FileList[0] | Write-Output 54 | } 55 | } 56 | 57 | <# 58 | .SYNOPSIS 59 | Gets the clusters directory storing metadata about the clusters. 60 | #> 61 | Function Get-CoreosClustersDirectory { 62 | [CmdletBinding()] 63 | Param ( 64 | ) 65 | 66 | PROCESS { 67 | Join-Path -Path $(Get-CoreosFilesDirectory) "Clusters" 68 | } 69 | } 70 | 71 | <# 72 | .SYNOPSIS 73 | Gets the cluster directory storing metadata about the cluster. 74 | #> 75 | Function Get-CoreosClusterDirectory { 76 | [CmdletBinding()] 77 | Param ( 78 | [Parameter (Mandatory=$true)] 79 | [String] $ClusterName 80 | ) 81 | 82 | PROCESS { 83 | Join-Path -Path $(Get-CoreosClustersDirectory) $ClusterName 84 | } 85 | } 86 | 87 | Function Get-ImageDirectory { 88 | [CmdletBinding()] 89 | Param ( 90 | ) 91 | 92 | PROCESS { 93 | $path = Join-Path -Path $(Get-CoreosFilesDirectory) "Images" 94 | 95 | if (Test-Path $path) { 96 | Get-Item $path 97 | } else { 98 | New-Item $Path -Type directory 99 | } 100 | } 101 | } 102 | 103 | ############################ 104 | ## Other General Functions # 105 | ############################ 106 | 107 | <# 108 | .SYNOPSIS 109 | Tests if the current user is runnign as an administrator. 110 | #> 111 | Function Test-IsShellAdmin { 112 | ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 113 | } 114 | 115 | <# 116 | .SYNOPSIS 117 | Write Coroes Cluster Info to file 118 | #> 119 | Function Out-CoreosClusterInfo { 120 | [CmdletBinding()] 121 | Param( 122 | [Parameter (Mandatory=$true)] 123 | [PSObject] $ClusterInfo 124 | ) 125 | 126 | PROCESS { 127 | $outFile = "$(Get-CoreosClustersDirectory)\$($ClusterInfo.Name)\cluster-info.json" 128 | $ClusterInfo | ConvertTo-Json -depth 4 | Out-File $outFile -Force 129 | } 130 | } -------------------------------------------------------------------------------- /modules/coreos-hyperv-images.psm1: -------------------------------------------------------------------------------- 1 | ############################ 2 | ##### Public Functions ##### 3 | ############################ 4 | <# 5 | .SYNOPSIS 6 | Gets the release version of a coreos cluster. 7 | .PARAMETER Channel 8 | The channel to get the release version of. 9 | Valid values are alpha, beta, stable and master. 10 | #> 11 | Function Get-CoreosCurrentReleaseNumber { 12 | [CmdletBinding()] 13 | Param ( 14 | [Parameter (Mandatory=$true)] 15 | [ValidateSet("Alpha","Beta","Stable","Master")] 16 | [String] $Channel 17 | ) 18 | 19 | PROCESS { 20 | $release = "" 21 | 22 | if ($Channel -eq "Master") { 23 | $uri = "http://storage.core-os.net/coreos/amd64-usr/master/version.txt" 24 | } else { 25 | $uri = "http://$($Channel.ToLower()).release.core-os.net/amd64-usr/current/version.txt" 26 | } 27 | 28 | $release = ((Invoke-WebRequest -Uri:$uri).Content -split('[\r\n]')) | foreach { if ($_ -like "COREOS_VERSION*") { Write-Output $(($_ -split('='))[1]) }} | Select-Object -First 1 29 | 30 | if ($release -eq "") { 31 | throw "Release does not exist in channel $Channel" 32 | return 33 | } 34 | 35 | Write-Output $release 36 | } 37 | } 38 | 39 | ############################ 40 | #### Protected Functions ### 41 | ############################ 42 | <# 43 | .SYNOPSIS 44 | Gets a coreos image to use for coreos vms. 45 | .DESCRIPTION 46 | Gets a coreos image to use for coreos vms. Checks if the release required is available locally. 47 | If it isn't available loacally the image will be downloaded and uncompressed. If no release is 48 | specified then the current version will be queried and downloaded. 49 | .PARAMETER ImageDir 50 | The directory to download the images to. 51 | .PARAMETER Channel 52 | The coreos channel to download the image from. The possible values are Alpha, Beta or Stable. 53 | .PARAMETER Release 54 | The release to get. If none is specified then the current version will be retrieved. 55 | .OUTPUTS 56 | Outputs an object with the release number ($_.Release) and the image path ($_.ImagePath). 57 | #> 58 | Function Get-CoreosImage { 59 | [CmdletBinding()] 60 | Param ( 61 | [Parameter (Mandatory=$true)] 62 | [String] $ImageDir, 63 | 64 | [Parameter (Mandatory=$true)] 65 | [ValidateSet("Alpha","Beta","Stable","Master")] 66 | [String] $Channel, 67 | 68 | [Parameter (Mandatory=$false)] 69 | [String] $Release = "" 70 | ) 71 | 72 | PROCESS { 73 | if ($Release -eq "" -or $Release.ToLower -eq "current") { 74 | $Release = Get-CoreosCurrentReleaseNumber -Channel:$Channel 75 | } 76 | 77 | $r = New-Object PSObject 78 | $r | Add-Member Release $Release 79 | 80 | $localPath = Get-ReleaseLocalPath -ImageDir:$ImageDir -Channel:$Channel -Release:$Release 81 | $r | Add-Member ImagePath $localPath 82 | 83 | if (Test-Path $localPath) { 84 | Write-Output $r 85 | return 86 | } 87 | 88 | if (!(Test-BzipCommandAvailable)) { 89 | throw "bunzip2 not in PATH nor a git installation. Bzip is required to decompress images." 90 | return 91 | } 92 | 93 | Get-CoreosImageFromSite -ImageSavePath:$localPath -Channel:$Channel -Release:$Release 94 | 95 | Write-Output $r 96 | } 97 | } 98 | 99 | <# 100 | .SYNOPSIS 101 | Gets the base config drive vhd to build config drives off. 102 | .PARAMETER ModuleFilesDir 103 | The directory conatining files for the module. 104 | .PARAMETER ImageDir 105 | The directory that images are saved to. 106 | #> 107 | Function Get-BaseConfigDrive { 108 | [CmdletBinding()] 109 | Param ( 110 | [Parameter (Mandatory=$true)] 111 | [String] $ModuleFilesDir, 112 | 113 | [Parameter (Mandatory=$true)] 114 | [String] $ImageDir 115 | ) 116 | 117 | PROCESS { 118 | $vhd = Join-Path -Path $ImageDir "config2_base.vhdx" 119 | 120 | if (Test-Path $vhd) { 121 | Write-Output $vhd 122 | return 123 | } 124 | 125 | $base = Join-Path -Path $ModuleFilesDir "config2_base.vhdx.bz2" 126 | 127 | if (!(Test-BzipCommandAvailable)) { 128 | throw "bunzip2 not in PATH nor a git installation. Bzip is required to decompress images." 129 | return 130 | } 131 | 132 | Invoke-Bunzip -Target:$base -Destination:$vhd 133 | 134 | Write-Output $vhd 135 | } 136 | } 137 | 138 | ############################ 139 | #### Private Functions ##### 140 | ############################ 141 | Function Get-ReleaseLocalPath { 142 | [CmdletBinding()] 143 | Param ( 144 | [Parameter (Mandatory=$true)] 145 | [String] $ImageDir, 146 | 147 | [Parameter (Mandatory=$true)] 148 | [ValidateSet("Alpha","Beta","Stable","Master")] 149 | [String] $Channel, 150 | 151 | [Parameter (Mandatory=$true)] 152 | [String] $Release 153 | ) 154 | 155 | PROCESS { 156 | $r = $Release -replace "\.", "_" 157 | Write-Output $(Join-Path -Path $ImageDir "coreos-hyperv_$($Channel.ToLower())_$r.vhd") 158 | } 159 | } 160 | 161 | Function Get-MsysgitBunzipCommand { 162 | [CmdletBinding()] 163 | Param () 164 | 165 | PROCESS { 166 | $default = "C:\Program Files\Git\usr\bin\bzip2.exe" 167 | if (Test-Path $default) { 168 | Write-Output $default 169 | return 170 | } 171 | 172 | try { 173 | $msysgit = Split-Path (get-command git).Definition -Parent | Split-Path -Parent 174 | $bzip = Join-Path $msysgit "usr\bin\bzip2.exe" 175 | if (Test-Path $bzip) { 176 | Write-Output $bzip 177 | return 178 | } 179 | } catch { 180 | Write-Verbose $_.Exception 181 | } 182 | 183 | Write-Output $null 184 | } 185 | } 186 | 187 | Function Get-BzipCommand { 188 | [CmdletBinding()] 189 | Param ( 190 | ) 191 | 192 | PROCESS { 193 | $bzip = $null 194 | try { 195 | Get-Command bunzip2 -ErrorAction:Stop | Out-Null 196 | $bzip = "bunzip2" 197 | } catch { 198 | } 199 | 200 | if ($bzip -eq $null) { 201 | $bzip = Get-MsysgitBunzipCommand 202 | } 203 | 204 | Write-Output $bzip 205 | } 206 | } 207 | 208 | Function Test-BzipCommandAvailable { 209 | [CmdletBinding()] 210 | Param ( 211 | ) 212 | 213 | PROCESS { 214 | if ($(Get-BzipCommand) -eq $null) { 215 | Write-Output $false 216 | } 217 | 218 | Write-Output $true 219 | } 220 | } 221 | 222 | Function Get-CoreosImageFromSite { 223 | [CmdletBinding()] 224 | Param ( 225 | [Parameter (Mandatory=$true)] 226 | [String] $ImageSavePath, 227 | 228 | [Parameter (Mandatory=$true)] 229 | [ValidateSet("Alpha","Beta","Stable","Master")] 230 | [String] $Channel, 231 | 232 | [Parameter (Mandatory=$true)] 233 | [String] $Release 234 | ) 235 | 236 | PROCESS { 237 | $tmp = "$ImageSavePath.bz2" 238 | 239 | if (Test-Path $tmp) { 240 | Remove-Item -Force $tmp 241 | } 242 | 243 | if ($Channel -eq "Master") { 244 | $uri = "http://storage.core-os.net/coreos/amd64-usr/master/coreos_production_hyperv_image.vhd.bz2" 245 | } else { 246 | $uri = "http://$($Channel.ToLower()).release.core-os.net/amd64-usr/$Release/coreos_production_hyperv_image.vhd.bz2" 247 | } 248 | 249 | try { 250 | Invoke-WebRequest -Uri $uri -OutFile $tmp 251 | } catch { 252 | throw "Release $Release not found on $Channel channel. $url" 253 | return 254 | } 255 | 256 | Invoke-Bunzip -Target:$tmp -Destination:$ImageSavePath 257 | 258 | if (!(Test-Path $ImageSavePath)) { 259 | throw "Failed to uncompress image." 260 | return 261 | } 262 | } 263 | } 264 | 265 | Function Invoke-Bunzip { 266 | [CmdletBinding()] 267 | Param ( 268 | [Parameter (Mandatory=$true)] 269 | [String] $Target, 270 | 271 | [Parameter (Mandatory=$true)] 272 | [String] $Destination 273 | ) 274 | 275 | PROCESS { 276 | $bzip = Get-BzipCommand 277 | Write-Verbose "Bunzipping $Target to $Destination" 278 | Write-Verbose "`"$Bzip`" -c -d -k `"$Target`" > `"$Destination`"" 279 | & cmd /C "`"`"$Bzip`" -c -d -k `"$Target`" > `"$Destination`"`"" | Out-Null 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /modules/coreos-hyperv.psm1: -------------------------------------------------------------------------------- 1 | ############################ 2 | # Manage Cluster Functions # 3 | ############################ 4 | <# 5 | .SYNOPSIS 6 | Creates and installs coreos on a cluster of virtual machines. 7 | .DESCRIPTION 8 | Creates and installs coreos on a cluster of virtual machines. 9 | .PARAMETER Name 10 | The Name of the Cluster. 11 | .PARAMETER Count 12 | The number of VMs in the cluster. 13 | .PARAMETER NetworkConfigs 14 | An array of objects that contain information on the hyper-v virtual switch and settings for the network. 15 | These configs can easily be created using the New-CoreosNetworkConfig function. 16 | .PARAMETER Config 17 | Set a default config to be applied for all clusters. 18 | .PARAMETER Configs 19 | Instead of setting a default config you can set a config for each machine individually. The count of configs 20 | must match the count of VMs being created in the cluster. 21 | .OUTPUTS 22 | Outputs a ClusterInfo object with information about the cluster, the configuration and the virtual machines. 23 | #> 24 | Function New-CoreosCluster { 25 | [CmdletBinding(DefaultParameterSetName="SingleConfig")] 26 | Param ( 27 | [Parameter (Mandatory=$true)] 28 | [Alias("ClusterName")] 29 | [String] $Name, 30 | 31 | [Parameter (Mandatory=$true)] 32 | [Int] $Count, 33 | 34 | [Parameter (Mandatory=$true)] 35 | [PSObject[]] $NetworkConfigs, 36 | 37 | [Parameter (Mandatory=$false)] 38 | [ValidateSet("Alpha","Beta","Stable","Master")] 39 | [String] $Channel = "Alpha", 40 | 41 | [Parameter (Mandatory=$false)] 42 | [String] $Release = "", 43 | 44 | [Parameter (Mandatory=$false, ParameterSetName="SingleConfig")] 45 | [String] $Config, 46 | 47 | [Parameter (Mandatory=$false, ParameterSetName="MultipleConfigs")] 48 | [String[]] $Configs 49 | ) 50 | 51 | PROCESS { 52 | if (!(Test-IsShellAdmin)) { 53 | throw "You must run as an administrator to run this script." 54 | return 55 | } 56 | 57 | $ClusterFilesDirectory = Get-CoreosClusterDirectory -ClusterName:$Name 58 | 59 | if (Test-Path $ClusterFilesDirectory) { 60 | throw "Cluster $Name already exists. Exiting." 61 | return 62 | } 63 | 64 | if ($Configs -and $Configs.length -ne $Count) { throw "Number of config files doesn't match the count in the cluster"; return } 65 | 66 | New-Item -Type Directory $ClusterFilesDirectory | Out-Null 67 | 68 | # Store the Cluster Config Info 69 | $ConfigInfo = New-Object PSObject 70 | $ConfigInfo | Add-Member Networks $NetworkConfigs 71 | $ConfigInfo | Add-Member EtcdDiscoveryToken $(New-EtcdDiscoveryToken -Size $Count) 72 | 73 | if ($Config) { 74 | if (Test-Path $Config) { 75 | $ConfigTemplate = "template.yaml" 76 | $ConfigTemplatePath = Join-Path -Path (Get-CoreosClusterDirectory -ClusterName:$Name) $ConfigTemplate 77 | 78 | Copy-Item $Config $ConfigTemplatePath 79 | $ConfigInfo | Add-Member DefaultConfigTemplate $ConfigTemplate 80 | } else { 81 | throw ("Config $Config not found.") 82 | } 83 | } 84 | 85 | # Store the VMs Info 86 | $ClusterInfo = New-Object PSObject 87 | $ClusterInfo | Add-Member Channel $Channel 88 | $ClusterInfo | Add-Member Release $Release 89 | $ClusterInfo | Add-Member Name $Name 90 | $ClusterInfo | Add-Member Config $ConfigInfo 91 | $ClusterInfo | Add-Member VMs @() 92 | $ClusterInfo | Add-Member Version $(Get-ModuleVersion) 93 | 94 | for ($i = 0; $i -lt $Count; $i++) { 95 | if ($Configs) { 96 | New-VMInCoreosClusterInfo -ClusterInfo:$ClusterInfo -Config:$Configs[$i] 97 | } else { 98 | New-VMInCoreosClusterInfo -ClusterInfo:$ClusterInfo 99 | } 100 | } 101 | 102 | Out-CoreosClusterInfo -ClusterInfo:$ClusterInfo 103 | Invoke-CoreosClusterBuilder -ClusterInfo:$ClusterInfo 104 | 105 | Write-Output $ClusterInfo 106 | } 107 | } 108 | 109 | <# 110 | .SYNOPSIS 111 | Removes a cluster of coreos virtual machines and associated files. 112 | .DESCRIPTION 113 | Removes a cluster of coreos virtual machines and associated files. 114 | .PARAMETER ClusterInfo 115 | Specifies which cluster to remove based on the cluster info object. 116 | This can be piped from Get-ClusterInfo. 117 | .PARAMETER ClusterName 118 | Specifies which cluster to remove based on the cluster name. 119 | #> 120 | Function Remove-CoreosCluster { 121 | [CmdletBinding(DefaultParameterSetName="ClusterInfo")] 122 | Param ( 123 | [Parameter (Mandatory=$true, ParameterSetName="ClusterInfo", ValueFromPipeline=$true)] 124 | [PSObject] $ClusterInfo, 125 | 126 | [Parameter (Mandatory=$true, ParameterSetName="ClusterName")] 127 | [String] $ClusterName 128 | ) 129 | 130 | PROCESS { 131 | if ($ClusterName) { 132 | $ClusterInfo = Get-CoreosCluster -ClusterName:$ClusterName 133 | } 134 | 135 | $ClusterInfo | Stop-CoreosCluster 136 | $ClusterInfo.VMs | foreach { 137 | if ($_.State -eq "Queued" -and $_.Action -ne "Remove") { 138 | $_.State = "Complete" 139 | } else { 140 | $_.State = "Queued"; 141 | } 142 | 143 | $_.Action = "Remove"; 144 | } 145 | 146 | Out-CoreosClusterInfo -ClusterInfo:$ClusterInfo 147 | Invoke-CoreosClusterBuilder -ClusterInfo:$ClusterInfo 148 | 149 | $ClusterFilesDirectory = Get-CoreosClusterDirectory -ClusterName:$ClusterInfo.Name 150 | Remove-Item -Force -Recurse $ClusterFilesDirectory 151 | } 152 | } 153 | 154 | <# 155 | .SYNOPSIS 156 | Starts up a cluster of coreos virtual machines. 157 | .DESCRIPTION 158 | Starts up all the virtual machines in a coreos cluster. 159 | .PARAMETER ClusterInfo 160 | Specifies which cluster to start from the cluster info object. 161 | .PARAMETER ClusterName 162 | Specifies which cluster to start from the name of the cluster. 163 | #> 164 | Function Start-CoreosCluster { 165 | [CmdletBinding(DefaultParameterSetName="ClusterInfo")] 166 | Param ( 167 | [Parameter (Mandatory=$true, ParameterSetName="ClusterInfo", ValueFromPipeline=$true)] 168 | [PSObject] $ClusterInfo, 169 | 170 | [Parameter (Mandatory=$true, ParameterSetName="ClusterName")] 171 | [String] $ClusterName 172 | ) 173 | 174 | PROCESS { 175 | if ($ClusterName) { 176 | $ClusterInfo = Get-CoreosCluster -ClusterName:$ClusterName 177 | } 178 | 179 | $ClusterInfo.VMs | foreach { Start-VM -VMName $_.Name | Out-Null } 180 | } 181 | } 182 | 183 | <# 184 | .SYNOPSIS 185 | Stops a cluster of coreos virtual machines. 186 | .DESCRIPTION 187 | Stops a cluster of coreos virtual machines. 188 | .PARAMETER ClusterInfo 189 | Specifies which cluster to stop from the cluster info object. 190 | .PARAMETER ClusterName 191 | Specifies which cluster to stop from the name of the cluster. 192 | #> 193 | Function Stop-CoreosCluster { 194 | [CmdletBinding(DefaultParameterSetName="ClusterInfo")] 195 | Param ( 196 | [Parameter (Mandatory=$true, ParameterSetName="ClusterInfo", ValueFromPipeline=$true)] 197 | [PSObject] $ClusterInfo, 198 | 199 | [Parameter (Mandatory=$true, ParameterSetName="ClusterName")] 200 | [String] $ClusterName 201 | ) 202 | 203 | PROCESS { 204 | if ($ClusterName) { 205 | $ClusterInfo = Get-CoreosCluster -ClusterName:$ClusterName 206 | } 207 | 208 | $ClusterInfo.VMs | foreach { Stop-VM -VMName $_.Name | Out-Null } 209 | } 210 | } 211 | 212 | <# 213 | .SYNOPSIS 214 | Gets a cluster of coreos virtual machines. 215 | .DESCRIPTION 216 | Gets a cluster of coreos virtual machines. This returns a ClusterInfo Object. 217 | The ClusterInfo object can be piped to other commands. 218 | .PARAMETER ClusterName 219 | The name of the cluster to get the info for. 220 | .OUTPUTS 221 | Outputs a ClusterInfo object with information about the cluster, the configuration and the virtual machines. 222 | #> 223 | Function Get-CoreosCluster { 224 | [CmdletBinding()] 225 | Param ( 226 | [Parameter (Mandatory=$true)] 227 | [String] $ClusterName 228 | ) 229 | 230 | PROCESS { 231 | $infoFile = Join-Path -Path $(Get-CoreosClustersDirectory) "$ClusterName\cluster-info.json" 232 | if (!(Test-Path $infoFile)) { 233 | throw "Coreos Cluster $ClusterName doesn't exist." 234 | return 235 | } 236 | 237 | Get-Content $infoFile | Out-String | ConvertFrom-Json 238 | } 239 | } 240 | 241 | ############################ 242 | ### Manage VM Functions #### 243 | ############################ 244 | <# 245 | .SYNOPSIS 246 | Creates and installs a coreos virtual machine in a coreos cluster. 247 | .DESCRIPTION 248 | Creates and installs a coreos virtaul machine in a coreos cluster. 249 | Applies the same values to the config as were applied to the other virtual machines in the cluster. 250 | .PARAMETER ClusterName 251 | The name of the cluster to add the new vm to. 252 | .PARAMETER Config 253 | The path to the config file to use for this VM. If no config file is specified then the default config file for the 254 | cluster will be used. 255 | .OUTPUTS 256 | Outputs an updated ClusterInfo object with information about the cluster, the configuration and the virtual machines. 257 | #> 258 | Function New-CoreosVM { 259 | [CmdletBinding()] 260 | Param ( 261 | [Parameter (Mandatory=$true)] 262 | [String] $ClusterName, 263 | 264 | [Parameter (Mandatory=$false)] 265 | [String] $Config 266 | ) 267 | 268 | PROCESS { 269 | if (!(Test-IsShellAdmin)) { 270 | throw "You must run as an administrator to run this script." 271 | return 272 | } 273 | 274 | $ClusterInfo = Get-CoreosCluster -ClusterName $ClusterName -ErrorAction:Stop 275 | 276 | if ($Config) { 277 | New-VMInCoreosClusterInfo -ClusterInfo:$ClusterInfo -Config:$Config 278 | } else { 279 | New-VMInCoreosClusterInfo -ClusterInfo:$ClusterInfo 280 | } 281 | 282 | Out-CoreosClusterInfo -ClusterInfo:$ClusterInfo 283 | Invoke-CoreosClusterBuilder -ClusterInfo:$ClusterInfo 284 | 285 | Write-Output $ClusterInfo 286 | } 287 | } 288 | 289 | ############################ 290 | #### Internal Functions #### 291 | ############################ 292 | <# 293 | .SYNOPSIS 294 | Adds a new VM to the coreos cluster info. 295 | #> 296 | Function New-VMInCoreosClusterInfo { 297 | [CmdletBinding()] 298 | Param( 299 | [Parameter (Mandatory=$true)] 300 | [PSObject] $ClusterInfo, 301 | 302 | [Parameter (Mandatory=$false)] 303 | [String] $Config 304 | ) 305 | 306 | PROCESS { 307 | $VMNumber = ($ClusterInfo.VMs | Measure).Count 308 | $VMNumber_00 = $VMNumber.ToString("00") 309 | 310 | $VMName = "$($ClusterInfo.Name)_$VMNumber_00" 311 | $ConfigTemplate = $null 312 | 313 | if ($Config) { 314 | if (Test-Path $Config) { 315 | $ConfigTemplate = "template_$VMNumber_00.yaml" 316 | $ConfigTemplatePath = Join-Path -Path (Get-CoreosClusterDirectory -ClusterName:$($ClusterInfo.Name)) $ConfigTemplate 317 | 318 | Copy-Item $Config $ConfigTemplatePath 319 | } else { 320 | throw ("Config $Config not found.") 321 | } 322 | } elseif ($ClusterInfo.Config.DefaultConfigTemplate) { 323 | $ConfigTemplate = $ClusterInfo.Config.DefaultConfigTemplate 324 | } 325 | 326 | $VM = New-Object PSObject 327 | $VM | Add-Member Name $VMName 328 | $VM | Add-Member Number $VMNumber 329 | $VM | Add-Member Number_00 $VMNumber_00 330 | $VM | Add-Member State "Queued" 331 | $VM | Add-Member Action "Create" 332 | if ($ConfigTemplate) { 333 | $VM | Add-Member ConfigTemplate $ConfigTemplate 334 | } 335 | 336 | $ClusterInfo.VMs += $VM 337 | } 338 | } 339 | 340 | <# 341 | .SYNOPSIS 342 | Takes in a cluster info and takes necessary steps to build the cluster vms. 343 | #> 344 | Function Invoke-CoreosClusterBuilder { 345 | [CmdletBinding()] 346 | Param( 347 | [Parameter (Mandatory=$true)] 348 | [PSObject] $ClusterInfo 349 | ) 350 | 351 | PROCESS { 352 | $ClusterFilesDirectory = Get-CoreosClusterDirectory -ClusterName $ClusterInfo.Name 353 | $NetworkSwitchNames = @() 354 | $ClusterInfo.Config.Networks | foreach { $NetworkSwitchNames += $_.SwitchName } 355 | 356 | # Clean up in the event the previous run was aborted. 357 | $failedVms = $ClusterInfo.VMs | where { $_.State -eq "InProgress" } 358 | if ($failedVMs) { 359 | $failedVMs | foreach { 360 | Write-Warning "VM $($_.Name) found in an incomplete state. Marking as failed." 361 | $_.State = "Failed" 362 | } 363 | 364 | Out-CoreosClusterInfo -ClusterInfo $ClusterInfo 365 | } 366 | 367 | $queued = $ClusterInfo.VMs | where { $_.State -eq "Queued" } 368 | if (!$queued) { 369 | return 370 | } 371 | 372 | $toCreate = $queued | where { $_.Action -eq "Create"} 373 | if ($toCreate) { 374 | 375 | # Get the image 376 | $image = Get-CoreosImage -ImageDir:$(Get-ImageDirectory) -Channel:$($ClusterInfo.Channel) -Release:$($ClusterInfo.Release) -ErrorAction:Stop 377 | 378 | $baseConfigDrive = Get-BaseConfigDrive -ModuleFilesDir:$(Get-ModuleFilesDirectory) -ImageDir:$(Get-ImageDirectory) -ErrorAction:Stop 379 | 380 | # Generate the config 381 | $queued | where { $_.Action -eq "Create" } | foreach { $_.State = "InProgress" } 382 | Write-Verbose "Creating the Config Files and drives for the VMs." 383 | 384 | $toCreate | where { $_.ConfigTemplate -and -not $_.Config } | foreach { 385 | $ConfigTemplatePath = Join-Path -Path $ClusterFilesDirectory $_.ConfigTemplate 386 | $Config = "config_$($_.Number_00).yaml" 387 | $ConfigPath = Join-Path -Path $ClusterFilesDirectory $Config 388 | New-CoreosConfig ` 389 | -Path:$ConfigTemplatePath ` 390 | -Destination:$ConfigPath ` 391 | -VMName:$_.Name ` 392 | -ClusterName:$ClusterInfo.Name ` 393 | -VMNumber:$_.Number ` 394 | -Channel:$ClusterInfo.Channel ` 395 | -EtcdDiscoveryToken:$ClusterInfo.Config.EtcdDiscoveryToken ` 396 | -NetworkConfigs:$ClusterInfo.Config.Networks | Out-Null 397 | 398 | $ConfigDrive = New-CoreosConfigDrive -BaseConfigDrivePath:$baseConfigDrive -ConfigPath:$ConfigPath 399 | 400 | $_ | Add-Member Config $Config 401 | $_ | Add-Member ConfigDrive $ConfigDrive 402 | } 403 | 404 | Out-CoreosClusterInfo -ClusterInfo $ClusterInfo 405 | 406 | # Create the VM 407 | Write-Verbose "Creating the VMs." 408 | $toCreate | foreach { 409 | $VMName = $_.Name 410 | $vhdLocation = "$((Get-VMHost).VirtualHardDiskPath)\$VMName.vhd" 411 | 412 | # Create the VM - Windows 2012 doesn't support -Generation 413 | if ((Get-WmiObject Win32_OperatingSystem).Version -ge 6.3) { 414 | # Windows 6.3 and higher = 2012 R2 + 81. http://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx 415 | $vm = New-VM -Name $VMName -MemoryStartupBytes 1024MB -NoVHD -Generation 1 -BootDevice CD -SwitchName $NetworkSwitchNames[0] 416 | } else { 417 | $vm = New-VM -Name $VMName -MemoryStartupBytes 1024MB -NoVHD -BootDevice CD -SwitchName $NetworkSwitchNames[0] 418 | } 419 | $vm | Set-VMMemory -DynamicMemoryEnabled:$true 420 | $NetworkSwitchNames | Select-Object -Skip 1 | foreach { Add-VMNetworkAdapter -VMName $VMName -SwitchName $_ } | Out-Null 421 | 422 | # Copy the image to the vhd location. 423 | Copy-Item -Path:$image.ImagePath -Destination:$vhdLocation 424 | Resize-VHD -Path:$vhdLocation -SizeBytes:10GB 425 | Add-VMHardDiskDrive -VMName $VMName -ControllerType IDE -ControllerNumber 0 -ControllerLocation 0 -Path $vhdLocation 426 | Remove-VMDvdDrive -VMName $VMName -ControllerNumber 1 -ControllerLocation 0 | Out-Null 427 | 428 | if ($_.ConfigDrive) { 429 | Add-VMHardDiskDrive -VMName $VMName -ControllerType IDE -ControllerNumber 1 -ControllerLocation 0 -Path $_.ConfigDrive 430 | } 431 | 432 | $_.State = "Complete" 433 | $_.Action = "None" 434 | } 435 | 436 | Out-CoreosClusterInfo -ClusterInfo $ClusterInfo 437 | } 438 | 439 | $queued | where { $_.Action -eq "Remove" } | foreach { $_.State = "InProgress" } 440 | 441 | $toRemove = $queued | where { $_.Action -eq "Remove" } 442 | if ($toRemove) { 443 | Write-Verbose "Removing VMS" 444 | 445 | $toRemove | foreach { 446 | $vhdPaths = (Get-VMHardDiskDrive -VMName $_.Name).Path 447 | Get-VMHardDiskDrive -VMName $_.Name | Remove-VMHardDiskDrive 448 | $vhdPaths | Remove-Item -Force 449 | 450 | Remove-VM -VMName $_.Name 451 | $_.State = "Removed" 452 | $_.Action = "None" 453 | } 454 | 455 | Out-CoreosClusterInfo -ClusterInfo:$ClusterInfo 456 | } 457 | } 458 | } 459 | 460 | Function Get-ModuleVersion { 461 | Param() 462 | PROCESS { 463 | (Get-Module coreos-hyperv).Version 464 | } 465 | } 466 | --------------------------------------------------------------------------------