github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/libvirttools/diskdriver.go (about)

     1  /*
     2  Copyright 2016-2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package libvirttools
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sort"
    23  
    24  	libvirtxml "github.com/libvirt/libvirt-go-xml"
    25  
    26  	"github.com/Mirantis/virtlet/pkg/metadata/types"
    27  )
    28  
    29  const (
    30  	minBlockDevChar = 'a'
    31  	// https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/5/html/Virtualization/sect-Virtualization-Virtualization_limitations-KVM_limitations.html
    32  	// Actually there can be more than 21 block devices (including
    33  	// the root volume), but we want to be on the safe side here
    34  	maxVirtioBlockDevChar = 'u'
    35  	maxScsiBlockDevChar   = 'z'
    36  )
    37  
    38  type diskDriver interface {
    39  	diskPath(domainDef *libvirtxml.Domain) (*diskPath, error)
    40  	target() *libvirtxml.DomainDiskTarget
    41  	address() *libvirtxml.DomainAddress
    42  }
    43  
    44  type diskDriverFactory func(n int) (diskDriver, error)
    45  
    46  var diskDriverMap = map[types.DiskDriverName]diskDriverFactory{
    47  	types.DiskDriverVirtio: virtioBlkDriverFactory,
    48  	types.DiskDriverScsi:   scsiDriverFactory,
    49  }
    50  
    51  type virtioBlkDriver struct {
    52  	n        int
    53  	diskChar int
    54  }
    55  
    56  func virtioBlkDriverFactory(n int) (diskDriver, error) {
    57  	diskChar := minBlockDevChar + n
    58  	if diskChar > maxVirtioBlockDevChar {
    59  		return nil, errors.New("too many virtio block devices")
    60  	}
    61  	return &virtioBlkDriver{n, diskChar}, nil
    62  }
    63  
    64  func (d *virtioBlkDriver) diskPath(domainDef *libvirtxml.Domain) (*diskPath, error) {
    65  	disk, err := findDisk(domainDef, d.devName())
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	devPath, sysfsPath, err := pciPath(domainDef, disk.Address)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return &diskPath{devPath, sysfsPath + "/virtio*/block/"}, nil
    74  }
    75  
    76  func (d *virtioBlkDriver) devName() string {
    77  	return fmt.Sprintf("vd%c", d.diskChar)
    78  }
    79  
    80  func (d *virtioBlkDriver) target() *libvirtxml.DomainDiskTarget {
    81  	return &libvirtxml.DomainDiskTarget{
    82  		Dev: d.devName(),
    83  		Bus: "virtio",
    84  	}
    85  }
    86  
    87  func (d *virtioBlkDriver) address() *libvirtxml.DomainAddress {
    88  	// FIXME: we can let libvirt auto-assign the addresses.
    89  	// We'll have to add auto-assignment logic to fake_domain.go though
    90  	domain := uint(0)
    91  	// use bus1 to have more predictable addressing for virtio devs
    92  	bus := uint(1)
    93  	slot := uint(d.n + 1)
    94  	function := uint(0)
    95  	return &libvirtxml.DomainAddress{
    96  		PCI: &libvirtxml.DomainAddressPCI{
    97  			Domain:   &domain,
    98  			Bus:      &bus,
    99  			Slot:     &slot,
   100  			Function: &function,
   101  		},
   102  	}
   103  }
   104  
   105  type scsiDriver struct {
   106  	n        int
   107  	diskChar int
   108  }
   109  
   110  func scsiDriverFactory(n int) (diskDriver, error) {
   111  	diskChar := minBlockDevChar + n
   112  	if diskChar > maxScsiBlockDevChar {
   113  		return nil, errors.New("too many scsi block devices")
   114  	}
   115  	return &scsiDriver{n, diskChar}, nil
   116  }
   117  
   118  func (d *scsiDriver) diskPath(domainDef *libvirtxml.Domain) (*diskPath, error) {
   119  	scsiControllers := findControllers(domainDef, "scsi")
   120  	switch {
   121  	case len(scsiControllers) == 0:
   122  		return nil, errors.New("no scsi controllers found")
   123  	case len(scsiControllers) > 1:
   124  		// linux kernel reports wrong host number in sysfs for some reason
   125  		return nil, errors.New("more than one scsi controller is not supported")
   126  	}
   127  
   128  	disk, err := findDisk(domainDef, d.devName())
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	if disk.Address.Drive == nil || disk.Address.Drive.Controller == nil || disk.Address.Drive.Bus == nil || disk.Address.Drive.Target == nil || disk.Address.Drive.Unit == nil {
   133  		return nil, fmt.Errorf("bad disk address for scsi disk %q", d.devName())
   134  	}
   135  	if *disk.Address.Drive.Controller != 0 {
   136  		return nil, fmt.Errorf("bad controller index for scsi disk %q", d.devName())
   137  	}
   138  
   139  	devPath, sysfsPath, err := pciPath(domainDef, scsiControllers[0].Address)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	return &diskPath{
   144  		fmt.Sprintf("%s-scsi-0:%d:%d:%d", devPath, *disk.Address.Drive.Bus, *disk.Address.Drive.Target, *disk.Address.Drive.Unit),
   145  		// host number are wrong in sysfs for some reason
   146  		fmt.Sprintf("%s/virtio*/host*/target*:%d:%d/*:%d:%d:%d/block/",
   147  			sysfsPath,
   148  			*disk.Address.Drive.Bus,
   149  			*disk.Address.Drive.Target,
   150  			*disk.Address.Drive.Bus,
   151  			*disk.Address.Drive.Target,
   152  			*disk.Address.Drive.Unit),
   153  	}, nil
   154  }
   155  
   156  func (d *scsiDriver) devName() string {
   157  	return fmt.Sprintf("sd%c", d.diskChar)
   158  }
   159  
   160  func (d *scsiDriver) target() *libvirtxml.DomainDiskTarget {
   161  	return &libvirtxml.DomainDiskTarget{
   162  		Dev: d.devName(),
   163  		Bus: "scsi",
   164  	}
   165  }
   166  
   167  func (d *scsiDriver) address() *libvirtxml.DomainAddress {
   168  	// FIXME: we can let libvirt auto-assign the addresses.
   169  	// We'll have to add auto-assignment logic to fake_domain.go though
   170  	controller := uint(0)
   171  	bus := uint(0)
   172  	target := uint(0)
   173  	unit := uint(d.n)
   174  	return &libvirtxml.DomainAddress{
   175  		Drive: &libvirtxml.DomainAddressDrive{
   176  			Controller: &controller,
   177  			Bus:        &bus,
   178  			Target:     &target,
   179  			Unit:       &unit,
   180  		},
   181  	}
   182  }
   183  
   184  func getDiskDriverFactory(name types.DiskDriverName) (diskDriverFactory, error) {
   185  	if f, found := diskDriverMap[name]; found {
   186  		return f, nil
   187  	}
   188  	return nil, fmt.Errorf("bad disk driver name: %q", name)
   189  }
   190  
   191  func findDisk(domainDef *libvirtxml.Domain, dev string) (*libvirtxml.DomainDisk, error) {
   192  	if domainDef.Devices != nil {
   193  		for _, d := range domainDef.Devices.Disks {
   194  			if d.Target != nil && d.Target.Dev == dev {
   195  				return &d, nil
   196  			}
   197  		}
   198  	}
   199  	return nil, fmt.Errorf("disk %q not found in the domain", dev)
   200  }
   201  
   202  func findControllers(domainDef *libvirtxml.Domain, controllerType string) []libvirtxml.DomainController {
   203  	if domainDef.Devices == nil {
   204  		return nil
   205  	}
   206  	// make an empty slice instead of nil because the effects of
   207  	// calling sort.SliceStable() on nil slice are unspecified
   208  	r := []libvirtxml.DomainController{}
   209  	for _, c := range domainDef.Devices.Controllers {
   210  		if c.Type == controllerType {
   211  			r = append(r, c)
   212  		}
   213  	}
   214  	sort.SliceStable(r, func(i, j int) bool {
   215  		var a, b uint
   216  		if r[i].Index != nil {
   217  			a = *r[i].Index
   218  		}
   219  		if r[j].Index != nil {
   220  			b = *r[j].Index
   221  		}
   222  		return a < b
   223  	})
   224  
   225  	return r
   226  }
   227  
   228  func pciPath(domainDef *libvirtxml.Domain, address *libvirtxml.DomainAddress) (string, string, error) {
   229  	pciControllers := findControllers(domainDef, "pci")
   230  	devPath := "/dev/disk/by-path/"
   231  	sysfsPath := "/sys/devices"
   232  	var recurse func(*libvirtxml.DomainAddress, string, int) error
   233  	recurse = func(address *libvirtxml.DomainAddress, pathPrefix string, depth int) error {
   234  		if depth > 256 { // 256 is big enough here, we're not expecting that many hops
   235  			return fmt.Errorf("can't make path for device address %#v: loop detected", address)
   236  		}
   237  		if address == nil || address.PCI == nil || address.PCI.Domain == nil || address.PCI.Bus == nil || address.PCI.Slot == nil || address.PCI.Function == nil {
   238  			return fmt.Errorf("can't make path for device address %#v", address)
   239  		}
   240  		if *address.PCI.Bus > uint(len(pciControllers)) {
   241  			return fmt.Errorf("bad PCI bus number: %#v", address)
   242  		}
   243  		ctl := pciControllers[*address.PCI.Bus]
   244  		if ctl.Address != nil && ctl.Address.PCI != nil {
   245  			if err := recurse(ctl.Address, "pci", depth+1); err != nil {
   246  				return err
   247  			}
   248  		} else {
   249  			// pci-root is not mentioned in devPath, but is present in sysfsPath
   250  			sysfsPath += fmt.Sprintf("/pci%04x:%02x", *address.PCI.Domain, *address.PCI.Bus)
   251  		}
   252  		addressStr := fmt.Sprintf("%04x:%02x:%02x.%01x", *address.PCI.Domain, *address.PCI.Bus, *address.PCI.Slot, *address.PCI.Function)
   253  		if devPath[len(devPath)-1] != '/' {
   254  			devPath += "-"
   255  		}
   256  		devPath += pathPrefix + "-" + addressStr
   257  		sysfsPath += "/" + addressStr
   258  		return nil
   259  	}
   260  	if err := recurse(address, "virtio-pci", 0); err != nil {
   261  		return "", "", fmt.Errorf("pciPath for %#v: %v", address, err)
   262  	}
   263  	return devPath, sysfsPath, nil
   264  }