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 }