コード例 #1
0
        public override async Task <ControllerUnpublishVolumeResponse> ControllerUnpublishVolume(ControllerUnpublishVolumeRequest request, ServerCallContext context)
        {
            var volumeName = request.VolumeId;

            //tmp fix to remove bad pvc name
            if (volumeName.StartsWith("C:\\"))
            {
                volumeName = HypervUtils.GetFileNameWithoutExtension(volumeName);
            }

            if (!Guid.TryParse(request.NodeId, out var vmId))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument, "node id invalid"));
            }

            _logger.LogInformation("unpublish volume {VolumeName} from node {VMId}", volumeName, vmId);

            var foundVolume = await _service.GetVolumesAsync(new HypervVolumeFilter
            {
                Name = volumeName
            })
                              .FirstOrDefaultAsync(context.CancellationToken);

            if (foundVolume is null)
            {
                throw new RpcException(new Status(StatusCode.NotFound, "volume not found"));
            }

            var vm = await _service.GetVirtualMachinesAsync(new HypervVirtualMachineFilter
            {
                Id = vmId
            })
                     .FirstOrDefaultAsync(context.CancellationToken);

            if (vm is null)
            {
                throw new RpcException(new Status(StatusCode.NotFound, "node not found"));
            }

            //todo maybe vm is deleted, spec: SHOULD return OK

            await _service.DetachVolumeAsync(new HypervDetachVolumeRequest
            {
                VMId       = vm.Id,
                VolumePath = foundVolume.Path,
                Host       = vm.Host
            }, context.CancellationToken);

            var rsp = new ControllerUnpublishVolumeResponse
            {
            };

            return(rsp);
        }
コード例 #2
0
        public async Task read_kvp_entries(string filePath)
        {
            var dic = new Dictionary <string, string>();

            await foreach (var entry in HypervUtils.ReadKvpPoolAsync(filePath))
            {
                dic[entry.Name] = entry.Value;
            }

            Assert.Contains("VirtualMachineId", (IDictionary <string, string>)dic);
            Assert.Contains("VirtualMachineName", (IDictionary <string, string>)dic);
            Assert.Contains("PhysicalHostNameFullyQualified", (IDictionary <string, string>)dic);
        }
コード例 #3
0
        public override async Task <NodeGetInfoResponse> NodeGetInfo(NodeGetInfoRequest request, ServerCallContext context)
        {
            //KVP daemon for KVP transfers to and from the host
            //centos: sudo yum install -y hyperv-daemons

            //read hostname from kvp values
            //strings /var/lib/hyperv/.kvp_pool_3|sed -n '/PhysicalHostNameFullyQualified/ {n;p}'

            //KVP byte array of fix sized fields key:512,value:2048 /0 filled
            //The byte array contains a UTF-8 encoded string,
            //which is padded out to the max size with null characters.
            //However, null string termination is not guaranteed (see kvp_send_key).
            //https://stackoverflow.com/questions/17530460/code-sample-for-reading-values-from-hyper-v-kvp-component-on-linux-aka-kvp-data

            var vmId = string.Empty;

            await foreach (var entry in HypervUtils.ReadKvpPoolAsync().WithCancellation(context.CancellationToken))
            {
                switch (entry.Name)
                {
                case "VirtualMachineId":
                    vmId = entry.Value;
                    break;

                //case "VirtualMachineName":
                //case "PhysicalHostNameFullyQualified":
                default:
                    break;
                }
            }

            if (string.IsNullOrEmpty(vmId))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument, string.Empty),
                                       "hyperv kvp could not be read");
            }

            var rsp = new NodeGetInfoResponse
            {
                NodeId = vmId,
                //todo MaxVolumesPerNode = 4*64 -1 //todo query by lsscsi
                //maybe AccessibleTopology from FailoverCluster query
            };

            return(rsp);
        }
コード例 #4
0
        public override async Task <DeleteVolumeResponse> DeleteVolume(DeleteVolumeRequest request, ServerCallContext context)
        {
            var volumeName = request.VolumeId;

            //tmp fix to remove bad pvc name
            if (volumeName.StartsWith("C:\\"))
            {
                volumeName = HypervUtils.GetFileNameWithoutExtension(volumeName);
            }

            _logger.LogInformation("delete volume {VolumeName}", volumeName);

            var foundVolumes = await _service.GetVolumesAsync(new HypervVolumeFilter { Name = volumeName })
                               .ToListAsync(context.CancellationToken);

            if (foundVolumes.Count > 1)
            {
                throw new RpcException(new Status(StatusCode.AlreadyExists, "volume id ambiguous"));
            }

            if (foundVolumes.Count == 1)
            {
                var volume = await _service.GetVolumeAsync(foundVolumes[0].Path, context.CancellationToken);

                if (volume.Attached)
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition, "volume attached"));
                }

                //todo snapshot/parent check

                await _service.DeleteVolumeAsync(new HypervDeleteVolumeRequest
                {
                    Id   = volume.Id,
                    Path = volume.Path
                },
                                                 context.CancellationToken);
            }

            return(new DeleteVolumeResponse());
        }
コード例 #5
0
        public async Task GetDeviceByVhdIdAsync(string hostName, Guid vhdId)
        {
            var power = Fixture.GetPower(hostName);

            Command cmd;
            var     commands = new List <Command>(2);

            var diskFilter = HypervUtils.GetDiskFilter(vhdId);

            cmd = new Command("Get-ChildItem");
            cmd.Parameters.Add("Path", diskFilter);
            commands.Add(cmd);

            cmd = new Command("Select-Object");
            cmd.Parameters.Add("Property", new [] { "Directory", "Target" });
            commands.Add(cmd);

            //hack until Join-Path in pipe
            dynamic obj = await power.InvokeAsync(commands).ThrowOnError()
                          .FirstOrDefaultAsync();

            var blockDeviceName = string.Empty;

            if (obj is not null)
            {
                commands.Clear();

                cmd = new Command("Join-Path");
                cmd.Parameters.Add("Path", obj.Directory);
                cmd.Parameters.Add("ChildPath", obj.Target);
                cmd.Parameters.Add("Resolve");
                commands.Add(cmd);

                blockDeviceName = await power.InvokeAsync(commands).ThrowOnError()
                                  .Select(n => (string)n.BaseObject)
                                  .FirstOrDefaultAsync();
            }

            Assert.StartsWith("/dev/", blockDeviceName);
        }
コード例 #6
0
        public async Task MountDeviceAsync(HypervNodeMountRequest request, CancellationToken cancellationToken = default)
        {
            using var scope = _logger.BeginScope("mounting {VolumeName}", request.Name);

            Command cmd;
            var     commands = new List <Command>(4);

            var blockDeviceName = string.Empty;

            if (request.VhdId != Guid.Empty)
            {
                var diskFilter = HypervUtils.GetDiskFilter(request.VhdId);

                cmd = new Command("Get-ChildItem");
                cmd.Parameters.Add("Path", diskFilter);
                commands.Add(cmd);

                cmd = new Command("Select-Object");
                cmd.Parameters.Add("Property", new[] { "Directory", "Target" });
                commands.Add(cmd);

                //hack until Join-Path in pipe
                dynamic obj = await _power.InvokeAsync(commands).ThrowOnError()
                              .FirstOrDefaultAsync();

                if (obj is not null)
                {
                    commands.Clear();

                    cmd = new Command("Join-Path");
                    cmd.Parameters.Add("Path", obj.Directory);
                    cmd.Parameters.Add("ChildPath", obj.Target);
                    cmd.Parameters.Add("Resolve");
                    commands.Add(cmd);

                    blockDeviceName = await _power.InvokeAsync(commands).ThrowOnError()
                                      .Select(n => (string)n.BaseObject)
                                      .FirstOrDefaultAsync();
                }
            }

            if (string.IsNullOrEmpty(blockDeviceName))
            {
                //over controller number

                commands.Clear();

                cmd = new Command("lsblk -S -o 'HCTL,NAME' -nr", true);
                commands.Add(cmd);

                var hctl       = $"{request.ControllerNumber}:0:0:{request.ControllerLocation}";
                var deviceInfo = await _power.InvokeAsync(commands).ThrowOnError()
                                 .Select((dynamic n) => (string[])n.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries))
                                 .Where(n => n.Length == 2 && n[0] == hctl)
                                 .Select(n => new
                {
                    hctl = n[0],
                    name = n[1]
                })
                                 .FirstOrDefaultAsync(cancellationToken);

                if (deviceInfo is null)
                {
                    throw new Exception($"device[{hctl}] not found");
                }

                blockDeviceName = $"/dev/{deviceInfo.name}";
            }

            if (string.IsNullOrEmpty(blockDeviceName))
            {
                throw new Exception($"block device not found");
            }

            //todo check name of blockDeviceName

            _logger.LogDebug("BlockDevice '{BlockDeviceName}' found", blockDeviceName);

            if (request.Raw)
            {
                throw new NotImplementedException("raw block device not implemented");
            }

            commands.Clear();

            var deviceName   = $"{blockDeviceName}1";
            var deviceLabel  = string.Empty;
            var deviceUUID   = string.Empty;
            var deviceFSType = string.Empty;

            cmd = new Command($"blkid -o export -c /dev/null {deviceName}", true);
            commands.Add(cmd);

            //BLKID_EXIT_NOTFOUND=2

            /*
             *  DEVNAME=/dev/sdb1
             *  LABEL=pvc-5a344250-ca2
             *  UUID=978388ae-6a2e-479a-8658-48ea495adda3
             *  TYPE=ext4
             *  PARTLABEL=primary
             *  PARTUUID=90580121-4dd5-485a-9d07-e20b73cba4bf
             */

            await foreach (var line in _power.InvokeAsync(commands).ThrowOnError()
                           .Select(n => n.BaseObject).OfType <string>()
                           .TakeWhile(n => !string.IsNullOrEmpty(n))
                           .WithCancellation(cancellationToken))
            {
                var name  = line;
                var value = string.Empty;

                int i = line.IndexOf('=');
                if (i > -1)
                {
                    name  = line.Substring(0, i);
                    value = line.Substring(i + 1);
                }

                switch (name)
                {
                case "DEVNAME" when deviceName != value:
                    throw new Exception("invalid device info");

                case "LABEL":
                    deviceLabel = value;
                    break;

                case "UUID":
                    deviceUUID = value;
                    break;

                case "TYPE":
                    deviceFSType = value;
                    break;
                }
            }

            //_logger.LogDebug("{@Device}", deviceLabel);

            //invalid device mount protection
            if (request.ValidateLabel && !string.IsNullOrEmpty(deviceLabel) && !request.Name.StartsWith(deviceLabel))
            {
                throw new Exception("device label ambiguous");
            }

            //todo select multiple partitions

            var fsType         = !string.IsNullOrEmpty(request.FSType) ? request.FSType : "ext4";
            var setPermissions = false;

            if (string.IsNullOrEmpty(deviceUUID))
            {
                _logger.LogInformation("creating partition on {BlockDeviceName} with {FSType}", blockDeviceName, deviceFSType);

                commands.Clear();

                //parted /dev/sdb --script mklabel gpt
                //parted /dev/sdb --script mkpart primary ext4 0% 100%
                var script = $"parted --align=opt {blockDeviceName} --script mklabel gpt mkpart primary {fsType} 1MiB 100%";

                cmd = new Command(script, true);
                commands.Add(cmd);

                var result = await _power.InvokeAsync(commands).ThrowOnError().ToListAsync(cancellationToken);
            }

            if (string.IsNullOrEmpty(deviceFSType))
            {
                _logger.LogInformation("creating filesystem on {deviceName} with {FSType}", deviceName, deviceFSType);

                commands.Clear();

                deviceLabel = request.Name ?? string.Empty;

                //labels are max 16-chars long (maybe for xfs max 12)
                if (deviceLabel.Length > 16)
                {
                    deviceLabel = deviceLabel.Substring(0, 16);
                }

                //mkfs -t ext4 -G 4096 -L volume-test /dev/sdb1
                //maybe add -G 4096
                cmd = new Command($"& mkfs -t {fsType} -L \"{deviceLabel}\" {deviceName} 2>&1", true);
                commands.Add(cmd);

                //cmd = new Command("Out-String");
                ////cmd.Parameters.Add("NoNewline");
                //commands.Add(cmd);

                var results = await _power.InvokeAsync(commands)
                              .ThrowOnError(error => !error.Exception.Message.StartsWith("mke2fs"))
                              .ToListAsync(cancellationToken);

                deviceFSType   = fsType;
                setPermissions = true;
            }

            if (deviceFSType != fsType)
            {
                throw new Exception("fsType ambiguous");
            }

            _logger.LogDebug("try to find mount {DeviceName} to {TargetPath}", deviceName, request.TargetPath);

            commands.Clear();

            cmd = new Command($"findmnt -fnr {deviceName} {request.TargetPath}", true);
            commands.Add(cmd);

            ///tmp/testdrive /dev/sdb1 ext4 rw,noatime,seclabel,discard
            var mountpoint = await _power.InvokeAsync(commands).ThrowOnError()
                             .Select(n => n.BaseObject).OfType <string>()
                             .FirstOrDefaultAsync(cancellationToken);

            if (string.IsNullOrEmpty(mountpoint))
            {
                _logger.LogDebug("create {TargetPath}", request.TargetPath);

                commands.Clear();

                cmd = new Command("New-Item");
                cmd.Parameters.Add("ItemType", "directory");
                cmd.Parameters.Add("Path", request.TargetPath);
                cmd.Parameters.Add("ErrorAction", "SilentlyContinue");
                commands.Add(cmd);

                _ = await _power.InvokeAsync(commands).ThrowOnError()
                    .FirstOrDefaultAsync(cancellationToken);

                _logger.LogInformation("mounting {DeviceName} to {TargetPath}", deviceName, request.TargetPath);

                commands.Clear();

                var options = new HashSet <string> {
                    "discard", "noatime"
                };

                foreach (var opt in (request.Options ?? Array.Empty <string>()))
                {
                    options.Add(opt);
                }

                if (request.Readonly)
                {
                    options.Add("ro");
                }

                //mount -o "discard,noatime,ro" /dev/sdb1 /drivetest
                cmd = new Command($"mkdir -p {request.TargetPath} && mount -o \"{string.Join(",", options)}\" {deviceName} {request.TargetPath}", true);
                commands.Add(cmd);

                //Labels are normaly only 16-chars long
                //Warning: label too long; will be truncated to 'pvc-5a344250-ca2'

                _ = await _power.InvokeAsync(commands).ThrowOnError()
                    .ToListAsync(cancellationToken);

                mountpoint = request.TargetPath;
            }

            if (!setPermissions)
            {
                _logger.LogDebug("empty check of {Mountpoint}", mountpoint);

                commands.Clear();

                cmd = new Command("Get-ChildItem");
                cmd.Parameters.Add("Path", request.TargetPath);
                commands.Add(cmd);

                try
                {
                    var hasFiles = await _power.InvokeAsync(commands).ThrowOnError()
                                   .AnyAsync((dynamic n) => n.Name switch
                    {
                        "lost+found" => false,
                        _ => true
                    }, cancellationToken);

                    setPermissions = !hasFiles;
                }
コード例 #7
0
ファイル: Startup.cs プロジェクト: Zetanova/hyperv-csi-driver
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions <HypervCsiDriverOptions>()
            .Bind(Configuration.GetSection("Driver"))
            .PostConfigure(opt =>
            {
                switch (opt.Type)
                {
                case HypervCsiDriverType.Controller:
                    //load hyperv host from kvp
                    if (string.IsNullOrEmpty(opt.HostName))
                    {
                        var(_, value) = HypervUtils.ReadKvpPoolAsync()
                                        .FirstOrDefaultAsync(n => n.Name == "PhysicalHostNameFullyQualified")
                                        .Result;

                        if (!string.IsNullOrEmpty(value))
                        {
                            opt.HostName = value;
                        }
                        if (string.IsNullOrEmpty(opt.UserName))
                        {
                            opt.UserName = "******";         //aka windows root
                        }
                    }
                    break;
                }
            })
            .Validate(opt =>
            {
                switch (opt.Type)
                {
                case HypervCsiDriverType.Controller:
                    if (string.IsNullOrEmpty(opt.HostName))
                    {
                        return(false);
                    }
                    return(true);

                case HypervCsiDriverType.Node:
                    return(true);

                default:
                    return(false);
                }
            });


            var driverType = Configuration.GetValue <HypervCsiDriverType>("Driver:Type");

            switch (driverType)
            {
            case HypervCsiDriverType.Controller:
                services.AddSingleton <IHypervVolumeService, HypervVolumeService>();
                break;

            case HypervCsiDriverType.Node:
                services.AddSingleton <IHypervNodeService, LinuxNodeService>();
                break;
            }

            services.AddGrpc();
        }
コード例 #8
0
        public async Task create_disk_filter(Guid vhdId, string diskFilter)
        {
            var r = HypervUtils.GetDiskFilter(vhdId);

            Assert.Equal(diskFilter, r);
        }
コード例 #9
0
        public async Task can_parse_storage_name_from_win_file_name(string filePath, string storageName)
        {
            var r = HypervUtils.GetStorageNameFromPath(filePath);

            Assert.Equal(storageName, r);
        }
コード例 #10
0
        public async Task can_parse_win_file_name(string filePath, string fileName)
        {
            var r = HypervUtils.GetFileNameWithoutExtension(filePath);

            Assert.Equal(fileName, r);
        }