/// <summary> /// Removes a hive certificate if it exists. /// </summary> /// <param name="name">The certificate name.</param> public void Remove(string name) { Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(name)); hive.Vault.Client.DeleteAsync(HiveHelper.GetVaultCertificateKey(name)).Wait(); hive.SignalTrafficManagerUpdate(); }
/// <summary> /// Adds or updates a hive certificate. /// </summary> /// <param name="name">The certificate name.</param> /// <param name="certificate">The certificate.</param> /// <exception cref="ArgumentException">Thrown if the certificate is not valid.</exception> /// <remarks> /// <note> /// The <paramref name="certificate"/> must be fully parsed (it's /// <see cref="TlsCertificate.Parse()"/> method must have been called at /// some point to load the <see cref="TlsCertificate.Hosts"/>, /// <see cref="TlsCertificate.ValidFrom"/> and <see cref="TlsCertificate.ValidUntil"/> /// properties). /// </note> /// </remarks> public void Set(string name, TlsCertificate certificate) { Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(name)); Covenant.Requires <ArgumentNullException>(certificate != null); hive.Vault.Client.WriteJsonAsync(HiveHelper.GetVaultCertificateKey(name), certificate).Wait(); hive.SignalTrafficManagerUpdate(); }
/// <summary> /// Returns the folder path where the VPN hive client folders will /// be located. /// </summary> /// <returns>The folder path.</returns> public static string GetVpnFolder() { if (HiveHelper.InToolContainer) { return("/dev/shm/vpn"); } else { return(Path.Combine(HiveHelper.GetHiveUserFolder(), "vpn")); } }
/// <summary> /// Constructs a hive proxy from a hive login. /// </summary> /// <param name="hiveLogin">The hive login.</param> /// <param name="nodeProxyCreator"> /// The optional application supplied function that creates a node proxy /// given the node name, public address or FQDN, private address, and /// the node definition. /// </param> /// <param name="appendLog">Optionally have logs appended to an existing log file rather than creating a new one.</param> /// <param name="useBootstrap"> /// Optionally specifies that the instance should use the HiveMQ client /// should directly eference to the HiveMQ cluster nodes for broadcasting /// proxy update messages rather than routing traffic through the <b>private</b> /// traffic manager. This is used internally to resolve chicken-and-the-egg /// dilemmas for the traffic manager and proxy implementations that rely on /// HiveMQ messaging. /// </param> /// <param name="defaultRunOptions"> /// Optionally specifies the <see cref="RunOptions"/> to be assigned to the /// <see cref="SshProxy{TMetadata}.DefaultRunOptions"/> property for the /// nodes managed by the hive proxy. This defaults to <see cref="RunOptions.None"/>. /// </param> /// <remarks> /// The <paramref name="nodeProxyCreator"/> function will be called for each node in /// the hive definition giving the application the chance to create the management /// proxy using the node's SSH credentials and also to specify logging. A default /// creator that doesn't initialize SSH credentials and logging is used if a <c>null</c> /// argument is passed. /// </remarks> public HiveProxy( HiveLogin hiveLogin, Func <string, string, IPAddress, bool, SshProxy <NodeDefinition> > nodeProxyCreator = null, bool appendLog = false, bool useBootstrap = false, RunOptions defaultRunOptions = RunOptions.None) : this(hiveLogin.Definition, nodeProxyCreator, appendLog : appendLog, useBootstrap : useBootstrap, defaultRunOptions : defaultRunOptions) { Covenant.Requires <ArgumentNullException>(hiveLogin != null); this.HiveLogin = hiveLogin; // This ensures that the local machine is initialized properly for the login. HiveHelper.InitLogin(hiveLogin); }
/// <summary> /// Restarts the hive registry caches as required, using the /// upstream credentials passed. /// </summary> /// <param name="registry">The target registry hostname.</param> /// <param name="username">The username.</param> /// <param name="password">The password.</param> /// <returns><c>true</c> if the operation succeeded or was unnecessary.</returns> /// <remarks> /// <note> /// This method currently does nothing but return <c>true</c> if the /// registry specified is not the Docker public registry because the /// cache supports only the public registry or if the registry cache /// is not enabled for this hive. /// </note> /// </remarks> public bool RestartCache(string registry, string username, string password) { // Return immediately if this is a NOP. if (!HiveHelper.IsDockerPublicRegistry(registry) || !hive.Definition.Docker.RegistryCache) { return(true); } // We're not going to restart these in parallel so only one // manager cache will be down at any given time. This should // result in no cache downtime for hives with multiple // managers. foreach (var manager in hive.Managers) { if (!manager.RestartRegistryCache(registry, username, password)) { return(false); } } return(true); }
/// <summary> /// Retrieves a hive certificate. /// </summary> /// <param name="name">The certificate name.</param> /// <returns>The certificate if present or <c>null</c> if it doesn't exist.</returns> public TlsCertificate Get(string name) { Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(name)); return(hive.Vault.Client.ReadJsonOrDefaultAsync <TlsCertificate>(HiveHelper.GetVaultCertificateKey(name)).Result); }
/// <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(); } }