Ejemplo n.º 1
0
        /// <summary>
        /// Performs any required Hyper-V initialization before host nodes can be provisioned.
        /// </summary>
        private void PrepareHyperV()
        {
            // Determine where we're going to place the VM hard drive files and
            // ensure that the directory exists.

            if (!string.IsNullOrEmpty(hive.Definition.Hosting.VmDriveFolder))
            {
                vmDriveFolder = hive.Definition.Hosting.VmDriveFolder;
            }
            else
            {
                vmDriveFolder = HyperVClient.DefaultDriveFolder;
            }

            Directory.CreateDirectory(vmDriveFolder);

            // Download the zipped VHDX template if it's not already present or has
            // changed.  Note that we're going to name the file the same as the file
            // name from the URI and we're also going to persist the ETAG and file
            // length in file with the same name with a [.info] extension.
            //
            // Note that I'm not actually going check for ETAG changes to update
            // the download file.  The reason for this is that I want to avoid the
            // situation where the user has provisioned some nodes with one version
            // of the template and then goes on later to provision new nodes with
            // an updated template.  The [neon hive setup --remove-templates]
            // option is provided to delete any cached templates.
            //
            // This should only be an issue for people using the default "latest"
            // drive template.  Production hives should reference a specific
            // drive template.

            var driveTemplateUri  = new Uri(hive.Definition.Hosting.LocalHyperV.HostVhdxUri);
            var driveTemplateName = driveTemplateUri.Segments.Last();

            driveTemplatePath = Path.Combine(HiveHelper.GetVmTemplatesFolder(), driveTemplateName);

            var driveTemplateInfoPath  = Path.Combine(HiveHelper.GetVmTemplatesFolder(), driveTemplateName + ".info");
            var driveTemplateIsCurrent = true;
            var driveTemplateInfo      = (DriveTemplateInfo)null;

            if (!File.Exists(driveTemplatePath) || !File.Exists(driveTemplateInfoPath))
            {
                driveTemplateIsCurrent = false;
            }
            else
            {
                try
                {
                    driveTemplateInfo = NeonHelper.JsonDeserialize <DriveTemplateInfo>(File.ReadAllText(driveTemplateInfoPath));

                    if (new FileInfo(driveTemplatePath).Length != driveTemplateInfo.Length)
                    {
                        driveTemplateIsCurrent = false;
                    }
                }
                catch
                {
                    // The [*.info] file must be corrupt.

                    driveTemplateIsCurrent = false;
                }
            }

            if (!driveTemplateIsCurrent)
            {
                controller.SetOperationStatus($"Download Template VHDX: [{hive.Definition.Hosting.LocalHyperV.HostVhdxUri}]");

                Task.Run(
                    async() =>
                {
                    using (var client = new HttpClient())
                    {
                        // Download the file.

                        var response = await client.GetAsync(hive.Definition.Hosting.LocalHyperV.HostVhdxUri, HttpCompletionOption.ResponseHeadersRead);

                        response.EnsureSuccessStatusCode();

                        var contentLength = response.Content.Headers.ContentLength;

                        try
                        {
                            using (var fileStream = new FileStream(driveTemplatePath, FileMode.Create, FileAccess.ReadWrite))
                            {
                                using (var downloadStream = await response.Content.ReadAsStreamAsync())
                                {
                                    var buffer = new byte[64 * 1024];
                                    int cb;

                                    while (true)
                                    {
                                        cb = await downloadStream.ReadAsync(buffer, 0, buffer.Length);

                                        if (cb == 0)
                                        {
                                            break;
                                        }

                                        await fileStream.WriteAsync(buffer, 0, cb);

                                        if (contentLength.HasValue)
                                        {
                                            var percentComplete = (int)(((double)fileStream.Length / (double)contentLength) * 100.0);

                                            controller.SetOperationStatus($"Downloading VHDX: [{percentComplete}%] [{hive.Definition.Hosting.LocalHyperV.HostVhdxUri}]");
                                        }
                                        else
                                        {
                                            controller.SetOperationStatus($"Downloading VHDX: [{fileStream.Length} bytes] [{hive.Definition.Hosting.LocalHyperV.HostVhdxUri}]");
                                        }
                                    }
                                }
                            }
                        }
                        catch
                        {
                            // Ensure that the template and info files are deleted if there were any
                            // errors, to help avoid using a corrupted template.

                            if (File.Exists(driveTemplatePath))
                            {
                                File.Decrypt(driveTemplatePath);
                            }

                            if (File.Exists(driveTemplateInfoPath))
                            {
                                File.Decrypt(driveTemplateInfoPath);
                            }

                            throw;
                        }

                        // Generate the [*.info] file.

                        var templateInfo = new DriveTemplateInfo();

                        templateInfo.Length = new FileInfo(driveTemplatePath).Length;

                        if (response.Headers.TryGetValues("ETag", out var etags))
                        {
                            // Note that ETags look like they're surrounded by double
                            // quotes.  We're going to strip these out if present.

                            templateInfo.ETag = etags.SingleOrDefault().Replace("\"", string.Empty);
                        }

                        File.WriteAllText(driveTemplateInfoPath, NeonHelper.JsonSerialize(templateInfo, Formatting.Indented));
                    }
                }).Wait();

                controller.SetOperationStatus();
            }

            // Handle any necessary Hyper-V initialization.

            using (var hyperv = new HyperVClient())
            {
                // We're going to create the [neonHIVE] external switch if there
                // isn't already an external switch.

                controller.SetOperationStatus("Scanning network adapters");

                var switches       = hyperv.ListVMSwitches();
                var externalSwitch = switches.FirstOrDefault(s => s.Type == VirtualSwitchType.External);

                if (externalSwitch == null)
                {
                    hyperv.NewVMExternalSwitch(switchName = defaultSwitchName, IPAddress.Parse(hive.Definition.Network.Gateway));
                }
                else
                {
                    switchName = externalSwitch.Name;
                }

                // Ensure that the hive virtual machines exist and are stopped,
                // taking care to issue a warning if any machines already exist
                // and we're not doing [force] mode.

                controller.SetOperationStatus("Scanning virtual machines");

                var existingMachines = hyperv.ListVMs();
                var conflicts        = string.Empty;

                controller.SetOperationStatus("Stopping virtual machines");

                foreach (var machine in existingMachines)
                {
                    var nodeName    = ExtractNodeName(machine.Name);
                    var drivePath   = Path.Combine(vmDriveFolder, $"{machine.Name}.vhdx");
                    var isClusterVM = hive.FindNode(nodeName) != null;

                    if (isClusterVM)
                    {
                        if (forceVmOverwrite)
                        {
                            if (machine.State != VirtualMachineState.Off)
                            {
                                hive.GetNode(nodeName).Status = "stop virtual machine";
                                hyperv.StopVM(machine.Name);
                                hive.GetNode(nodeName).Status = string.Empty;
                            }

                            // The named machine already exists.  For force mode, we're going to stop and
                            // reuse the machine but replace the hard drive file as long as the file name
                            // matches what we're expecting for the machine.  We'll delete the VM if
                            // the names don't match and recreate it below.
                            //
                            // The reason for doing this is to avoid generating new MAC addresses
                            // every time we reprovision a VM.  This could help prevent the router/DHCP
                            // server from running out of IP addresses for the subnet.

                            var drives = hyperv.GetVMDrives(machine.Name);

                            if (drives.Count != 1 || !drives.First().Equals(drivePath, StringComparison.InvariantCultureIgnoreCase))
                            {
                                // Remove the machine and recreate it below.

                                hive.GetNode(nodeName).Status = "delete virtual machine";
                                hyperv.RemoveVM(machine.Name);
                                hive.GetNode(nodeName).Status = string.Empty;
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else
                        {
                            // We're going to report errors when one or more machines already exist and
                            // [--force] was not specified.

                            if (conflicts.Length > 0)
                            {
                                conflicts += ", ";
                            }

                            conflicts += nodeName;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(conflicts))
                {
                    throw new HyperVException($"[{conflicts}] virtual machine(s) already exist and cannot be automatically replaced unless you specify [--force].");
                }

                controller.SetOperationStatus();
            }
        }