Exemple #1
0
        /// <summary>
        /// Validates the options.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <exception cref="ClusterDefinitionException">Thrown if the definition is not valid.</exception>
        internal void Validate(ClusterDefinition clusterDefinition)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            var minioOptionsPrefix = $"{nameof(ClusterDefinition.Storage)}.{nameof(ClusterDefinition.Storage.Minio)}";

            if (!clusterDefinition.Nodes.Any(n => n.Labels.Minio))
            {
                if (clusterDefinition.Kubernetes.AllowPodsOnControlPlane.GetValueOrDefault() == true)
                {
                    foreach (var node in clusterDefinition.Nodes)
                    {
                        node.Labels.MinioInternal = true;
                    }
                }
                else
                {
                    foreach (var node in clusterDefinition.Workers)
                    {
                        node.Labels.MinioInternal = true;
                    }
                }
            }
            else
            {
                foreach (var node in clusterDefinition.Nodes.Where(n => n.Labels.Minio))
                {
                    node.Labels.MinioInternal = true;
                }
            }

            var serverCount = clusterDefinition.Nodes.Where(n => n.Labels.MinioInternal).Count();

            if (serverCount * VolumesPerNode < 4)
            {
                throw new ClusterDefinitionException($"Minio requires at least [4] volumes within the cluster.  Increase [{minioOptionsPrefix}.{nameof(MinioOptions.VolumesPerNode)}] so the number of nodes hosting Minio times [{VolumesPerNode}] is at least [4].");
            }

            var minOsDiskAfterMinio = ByteUnits.Parse(KubeConst.MinimumOsDiskAfterMinio);

            foreach (var node in clusterDefinition.Nodes.Where(node => node.Labels.MinioInternal))
            {
                var osDisk       = ByteUnits.Parse(node.GetDataDiskSize(clusterDefinition));
                var minioVolumes = ByteUnits.Parse(VolumeSize) * VolumesPerNode;

                if (osDisk - minioVolumes < minOsDiskAfterMinio)
                {
                    throw new ClusterDefinitionException($"Node [{node.Name}] Operating System (boot) disk is too small.  Increase this to at least [{ByteUnits.Humanize(minOsDiskAfterMinio + minioVolumes, powerOfTwo: true, spaceBeforeUnit: false)}].");
                }
            }
        }
Exemple #2
0
        /// <summary>
        /// Returns the size in bytes of RAM to allocate to the MDS cache
        /// on this node integrated Ceph storage cluster is enabled and
        /// MDS is deployed to the node.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <returns>The size in bytes or zero if Ceph is not enabled.</returns>
        public decimal GetCephMDSCacheSize(ClusterDefinition clusterDefinition)
        {
            if (!clusterDefinition.Ceph.Enabled)
            {
                return(0);
            }

            if (string.IsNullOrEmpty(Labels.CephMDSCacheSize))
            {
                Labels.CephMDSCacheSize = clusterDefinition.Ceph.MDSCacheSize;
            }

            return(ByteUnits.Parse(Labels.CephMDSCacheSize));
        }
Exemple #3
0
        /// <summary>
        /// Parses a dictionary of name/value labels by setting the appropriate
        /// properties of the parent node.
        /// </summary>
        /// <param name="labels">The label dictionary.</param>
        internal void Parse(Dictionary <string, string> labels)
        {
            // WARNING:
            //
            // This method will need to be updated whenever new standard labels are added or changed.

            foreach (var label in labels)
            {
                switch (label.Key)
                {
                case LabelAddress:                      Node.Address = label.Value; break;

                case LabelRole:                         Node.Role = label.Value; break;

                case LabelIngress:                      ParseCheck(label, () => { Node.Ingress = NeonHelper.ParseBool(label.Value); }); break;

                case LabelOpenEbs:                      ParseCheck(label, () => { Node.OpenEbsStorage = NeonHelper.ParseBool(label.Value); }); break;

                case LabelAzureVmSize:
                case LabelAzureStorageType:
                case LabelAzureDriveSize:

                    if (Node.Azure == null)
                    {
                        Node.Azure = new AzureNodeOptions();
                    }

                    switch (label.Key)
                    {
                    case LabelAzureVmSize:          Node.Azure.VmSize = label.Value; break;

                    case LabelAzureDriveSize:       Node.Azure.DiskSize = label.Value; break;

                    case LabelAzureStorageType:     ParseCheck(label, () => { Node.Azure.StorageType = NeonHelper.ParseEnum <AzureStorageType>(label.Value); }); break;
                    }
                    break;

                case LabelStorageSize:                  ParseCheck(label, () => { Node.Labels.StorageSize = ByteUnits.Parse(label.Value).ToString(); }); break;

                case LabelStorageLocal:                 Node.Labels.StorageLocal = label.Value.Equals("true", StringComparison.OrdinalIgnoreCase); break;

                case LabelStorageHDD:                   Node.Labels.StorageHDD = label.Value.Equals("true", StringComparison.OrdinalIgnoreCase); break;

                case LabelStorageRedundant:             Node.Labels.StorageRedundant = label.Value.Equals("true", StringComparison.OrdinalIgnoreCase); break;

                case LabelStorageEphemeral:             Node.Labels.StorageEphemeral = label.Value.Equals("true", StringComparison.OrdinalIgnoreCase); break;

                case LabelComputeCores:                 ParseCheck(label, () => { Node.Labels.ComputeCores = int.Parse(label.Value); }); break;

                case LabelComputeRamMiB:                ParseCheck(label, () => { Node.Labels.ComputeRam = int.Parse(label.Value); }); break;

                case LabelPhysicalMachine:              Node.Labels.PhysicalMachine = label.Value; break;

                case LabelPhysicalLocation:             Node.Labels.PhysicalLocation = label.Value; break;

                case LabelPhysicalAvailabilitytSet:     Node.Labels.PhysicalAvailabilitySet = label.Value; break;

                case LabelPhysicalPower:                Node.Labels.PhysicalPower = label.Value; break;

                case LabelDatacenter:
                case LabelEnvironment:

                    // These labels don't currently map to node properties so we'll ignore them.

                    break;

                default:

                    // Must be a custom label.

                    Node.Labels.Custom.Add(label.Key, label.Value);
                    break;
                }
            }
        }
Exemple #4
0
#pragma warning restore CA1416

        /// <summary>
        /// Creates a virtual machine.
        /// </summary>
        /// <param name="machineName">The machine name.</param>
        /// <param name="memorySize">
        /// A string specifying the memory size.  This can be a long byte count or a
        /// byte count or a number with units like <b>512MiB</b>, <b>0.5GiB</b>, <b>2GiB</b>,
        /// or <b>1TiB</b>.  This defaults to <b>2GiB</b>.
        /// </param>
        /// <param name="processorCount">
        /// The number of virutal processors to assign to the machine.  This defaults to <b>4</b>.
        /// </param>
        /// <param name="driveSize">
        /// A string specifying the primary disk size.  This can be a long byte count or a
        /// byte count or a number with units like <b>512MB</b>, <b>0.5GiB</b>, <b>2GiB</b>,
        /// or <b>1TiB</b>.  Pass <c>null</c> to leave the disk alone.  This defaults to <c>null</c>.
        /// </param>
        /// <param name="drivePath">
        /// Optionally specifies the path where the virtual hard drive will be located.  Pass
        /// <c>null</c> or empty to default to <b>MACHINE-NAME.vhdx</b> located in the default
        /// Hyper-V virtual machine drive folder.
        /// </param>
        /// <param name="checkpointDrives">Optionally enables drive checkpoints.  This defaults to <c>false</c>.</param>
        /// <param name="templateDrivePath">
        /// If this is specified and <paramref name="drivePath"/> is not <c>null</c> then
        /// the hard drive template at <paramref name="templateDrivePath"/> will be copied
        /// to <paramref name="drivePath"/> before creating the machine.
        /// </param>
        /// <param name="switchName">Optional name of the virtual switch.</param>
        /// <param name="extraDrives">
        /// Optionally specifies any additional virtual drives to be created and
        /// then attached to the new virtual machine.
        /// </param>
        /// <remarks>
        /// <note>
        /// The <see cref="VirtualDrive.Path"/> property of <paramref name="extraDrives"/> may be
        /// passed as <c>null</c> or empty.  In this case, the drive name will default to
        /// being located in the standard Hyper-V virtual drivers folder and will be named
        /// <b>MACHINE-NAME-#.vhdx</b>, where <b>#</b> is the one-based index of the drive
        /// in the enumeration.
        /// </note>
        /// </remarks>
        public void AddVm(
            string machineName,
            string memorySize        = "2GiB",
            int processorCount       = 4,
            string driveSize         = null,
            string drivePath         = null,
            bool checkpointDrives    = false,
            string templateDrivePath = null,
            string switchName        = null,
            IEnumerable <VirtualDrive> extraDrives = null)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(machineName), nameof(machineName));
            CheckDisposed();

            memorySize = ByteUnits.Parse(memorySize).ToString();

            if (driveSize != null)
            {
                driveSize = ByteUnits.Parse(driveSize).ToString();
            }

            var driveFolder = DefaultDriveFolder;

            if (string.IsNullOrEmpty(drivePath))
            {
                drivePath = Path.Combine(driveFolder, $"{machineName}-[0].vhdx");
            }
            else
            {
                driveFolder = Path.GetDirectoryName(Path.GetFullPath(drivePath));
            }

            if (VmExists(machineName))
            {
                throw new HyperVException($"Virtual machine [{machineName}] already exists.");
            }

            // Copy the template VHDX file.

            if (templateDrivePath != null)
            {
                File.Copy(templateDrivePath, drivePath);
            }

            // Resize the VHDX if requested.

            if (driveSize != null)
            {
                powershell.Execute($"{HyperVNamespace}Resize-VHD -Path '{drivePath}' -SizeBytes {driveSize}");
            }

            // Create the virtual machine.

            var command = $"{HyperVNamespace}New-VM -Name '{machineName}' -MemoryStartupBytes {memorySize}  -Generation 1";

            if (!string.IsNullOrEmpty(drivePath))
            {
                command += $" -VHDPath '{drivePath}'";
            }

            if (!string.IsNullOrEmpty(switchName))
            {
                command += $" -SwitchName '{switchName}'";
            }

            try
            {
                powershell.Execute(command);
            }
            catch (Exception e)
            {
                throw new HyperVException(e.Message, e);
            }

            // We need to configure the VM's processor count and min/max memory settings.

            try
            {
                powershell.Execute($"{HyperVNamespace}Set-VM -Name '{machineName}' -ProcessorCount {processorCount} -StaticMemory -MemoryStartupBytes {memorySize}");
            }
            catch (Exception e)
            {
                throw new HyperVException(e.Message, e);
            }

            // Create and attach any additional drives as required.

            if (extraDrives != null)
            {
                var diskNumber = 1;

                foreach (var drive in extraDrives)
                {
                    if (string.IsNullOrEmpty(drive.Path))
                    {
                        drive.Path = Path.Combine(driveFolder, $"{machineName}-[{diskNumber}].vhdx");
                    }

                    if (drive.Size <= 0)
                    {
                        throw new ArgumentException("Virtual drive size must be greater than 0.", nameof(drive));
                    }

                    NeonHelper.DeleteFile(drive.Path);

                    var fixedOrDynamic = drive.IsDynamic ? "-Dynamic" : "-Fixed";

                    try
                    {
                        powershell.Execute($"{HyperVNamespace}New-VHD -Path '{drive.Path}' {fixedOrDynamic} -SizeBytes {drive.Size} -BlockSizeBytes 1MB");
                        powershell.Execute($"{HyperVNamespace}Add-VMHardDiskDrive -VMName '{machineName}' -Path \"{drive.Path}\"");
                    }
                    catch (Exception e)
                    {
                        throw new HyperVException(e.Message, e);
                    }

                    diskNumber++;
                }
            }

            // Windows 10 releases since the August 2017 Creators Update enable automatic
            // virtual drive checkpointing (which is annoying).  We're going to disable this
            // by default.

            if (!checkpointDrives)
            {
                try
                {
                    powershell.Execute($"{HyperVNamespace}Set-VM -CheckpointType Disabled -Name '{machineName}'");
                }
                catch (Exception e)
                {
                    throw new HyperVException(e.Message, e);
                }
            }

            // We need to do some extra configuration for nested virtual machines:
            //
            //      https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/nested-virtualization

            if (IsNestedVirtualization)
            {
                // Enable nested virtualization for the VM.

                powershell.Execute($"{HyperVNamespace}Set-VMProcessor -VMName '{machineName}' -ExposeVirtualizationExtensions $true");

                // Enable MAC address spoofing for the VMs network adapter.

                powershell.Execute($"{HyperVNamespace}Set-VMNetworkAdapter -VMName '{machineName}' -MacAddressSpoofing On");
            }
        }
Exemple #5
0
        /// <summary>
        /// Verifies that the limit properties make sense.
        /// </summary>
        /// <returns><c>null</c> for valid properties, otherwise an error message.</returns>
        public string Validate()
        {
            decimal memory            = -1;
            decimal memorySwap        = -1;
            decimal memoryReservation = -1;
            decimal kernelMemory      = -1;

            if (Memory != null)
            {
                try
                {
                    memory = ByteUnits.Parse(Memory);

                    if (memory < 0)
                    {
                        throw new Exception();
                    }
                }
                catch
                {
                    return($"[{nameof(Memory)}={Memory}]: Value is not valid.");
                }
            }

            if (MemorySwap != null)
            {
                try
                {
                    memorySwap = ByteUnits.Parse(MemorySwap);

                    if (memorySwap < -1)
                    {
                        throw new Exception();
                    }
                }
                catch
                {
                    return($"[{nameof(MemorySwap)}={MemorySwap}]: Value is not valid.");
                }

                if (memorySwap > 0 && memory == -1)
                {
                    return($"[{nameof(Memory)}] must also be set when [{nameof(MemorySwap)}] is specified.");
                }

                if (memory != -1 && memorySwap <= memory)
                {
                    return($"[{nameof(MemorySwap)}={MemorySwap}] must be greater than [{nameof(Memory)}={Memory}].");
                }
            }

            if (MemorySwappiness != null)
            {
                if (MemorySwappiness.Value < 0 || MemorySwappiness.Value > 100)
                {
                    return($"[{nameof(MemorySwappiness)}={MemorySwappiness}]: Value is not valid.");
                }
            }

            if (MemoryReservation != null)
            {
                try
                {
                    memoryReservation = ByteUnits.Parse(MemoryReservation);

                    if (memoryReservation < 0)
                    {
                        throw new Exception();
                    }
                }
                catch
                {
                    return($"[{nameof(MemoryReservation)}={MemoryReservation}]: Value is not valid.");
                }
            }

            if (KernelMemory != null)
            {
                try
                {
                    kernelMemory = ByteUnits.Parse(KernelMemory);

                    if (memory < 4 * ByteUnits.MebiBytes)
                    {
                        return($"[{nameof(Memory)}={Memory}]: Value cannot be less than 4MiB.");
                    }
                }
                catch
                {
                    return($"[{nameof(KernelMemory)}={KernelMemory}]: Value is not valid.");
                }

                if (kernelMemory < 4 * ByteUnits.MebiBytes)
                {
                    return($"[{nameof(KernelMemory)}={KernelMemory}]: Value cannot be less than 4MiB.");
                }
            }

            if (memory != -1 && memoryReservation != -1)
            {
                if (memoryReservation >= memory)
                {
                    return($"[{nameof(MemoryReservation)}={MemoryReservation}] must be less than [{nameof(Memory)}={Memory}].");
                }
            }

            if (OomKillDisable && memory == -1)
            {
                return($"[{nameof(OomKillDisable)}={OomKillDisable}] is not allowed when [{nameof(Memory)}] is not set.");
            }

            return(null);
        }