github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/graphdriver/lcow/lcow_svm.go (about) 1 // +build windows 2 3 package lcow // import "github.com/demonoid81/moby/daemon/graphdriver/lcow" 4 5 import ( 6 "bytes" 7 "fmt" 8 "io" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/Microsoft/hcsshim" 14 "github.com/Microsoft/opengcs/client" 15 "github.com/pkg/errors" 16 "github.com/sirupsen/logrus" 17 ) 18 19 // Code for all the service VM management for the LCOW graphdriver 20 21 var errVMisTerminating = errors.New("service VM is shutting down") 22 var errVMUnknown = errors.New("service vm id is unknown") 23 var errVMStillHasReference = errors.New("Attemping to delete a VM that is still being used") 24 25 // serviceVMMap is the struct representing the id -> service VM mapping. 26 type serviceVMMap struct { 27 sync.Mutex 28 svms map[string]*serviceVMMapItem 29 } 30 31 // serviceVMMapItem is our internal structure representing an item in our 32 // map of service VMs we are maintaining. 33 type serviceVMMapItem struct { 34 svm *serviceVM // actual service vm object 35 refCount int // refcount for VM 36 } 37 38 // attachedVHD is for reference counting SCSI disks attached to a service VM, 39 // and for a counter used to generate a short path name for the container path. 40 type attachedVHD struct { 41 refCount int 42 attachCounter uint64 43 } 44 45 type serviceVM struct { 46 sync.Mutex // Serialises operations being performed in this service VM. 47 scratchAttached bool // Has a scratch been attached? 48 config *client.Config // Represents the service VM item. 49 50 // Indicates that the vm is started 51 startStatus chan interface{} 52 startError error 53 54 // Indicates that the vm is stopped 55 stopStatus chan interface{} 56 stopError error 57 58 attachCounter uint64 // Increasing counter for each add 59 attachedVHDs map[string]*attachedVHD // Map ref counting all the VHDS we've hot-added/hot-removed. 60 unionMounts map[string]int // Map ref counting all the union filesystems we mounted. 61 } 62 63 // add will add an id to the service vm map. There are three cases: 64 // - entry doesn't exist: 65 // - add id to map and return a new vm that the caller can manually configure+start 66 // - entry does exist 67 // - return vm in map and increment ref count 68 // - entry does exist but the ref count is 0 69 // - return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop 70 func (svmMap *serviceVMMap) add(id string) (svm *serviceVM, alreadyExists bool, err error) { 71 svmMap.Lock() 72 defer svmMap.Unlock() 73 if svm, ok := svmMap.svms[id]; ok { 74 if svm.refCount == 0 { 75 return svm.svm, true, errVMisTerminating 76 } 77 svm.refCount++ 78 return svm.svm, true, nil 79 } 80 81 // Doesn't exist, so create an empty svm to put into map and return 82 newSVM := &serviceVM{ 83 startStatus: make(chan interface{}), 84 stopStatus: make(chan interface{}), 85 attachedVHDs: make(map[string]*attachedVHD), 86 unionMounts: make(map[string]int), 87 config: &client.Config{}, 88 } 89 svmMap.svms[id] = &serviceVMMapItem{ 90 svm: newSVM, 91 refCount: 1, 92 } 93 return newSVM, false, nil 94 } 95 96 // get will get the service vm from the map. There are three cases: 97 // - entry doesn't exist: 98 // - return errVMUnknown 99 // - entry does exist 100 // - return vm with no error 101 // - entry does exist but the ref count is 0 102 // - return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop 103 func (svmMap *serviceVMMap) get(id string) (*serviceVM, error) { 104 svmMap.Lock() 105 defer svmMap.Unlock() 106 svm, ok := svmMap.svms[id] 107 if !ok { 108 return nil, errVMUnknown 109 } 110 if svm.refCount == 0 { 111 return svm.svm, errVMisTerminating 112 } 113 return svm.svm, nil 114 } 115 116 // decrementRefCount decrements the ref count of the given ID from the map. There are four cases: 117 // - entry doesn't exist: 118 // - return errVMUnknown 119 // - entry does exist but the ref count is 0 120 // - return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop 121 // - entry does exist but ref count is 1 122 // - return vm and set lastRef to true. The caller can then stop the vm, delete the id from this map 123 // - and execute svm.signalStopFinished to signal the threads that the svm has been terminated. 124 // - entry does exist and ref count > 1 125 // - just reduce ref count and return svm 126 func (svmMap *serviceVMMap) decrementRefCount(id string) (_ *serviceVM, lastRef bool, _ error) { 127 svmMap.Lock() 128 defer svmMap.Unlock() 129 130 svm, ok := svmMap.svms[id] 131 if !ok { 132 return nil, false, errVMUnknown 133 } 134 if svm.refCount == 0 { 135 return svm.svm, false, errVMisTerminating 136 } 137 svm.refCount-- 138 return svm.svm, svm.refCount == 0, nil 139 } 140 141 // setRefCountZero works the same way as decrementRefCount, but sets ref count to 0 instead of decrementing it. 142 func (svmMap *serviceVMMap) setRefCountZero(id string) (*serviceVM, error) { 143 svmMap.Lock() 144 defer svmMap.Unlock() 145 146 svm, ok := svmMap.svms[id] 147 if !ok { 148 return nil, errVMUnknown 149 } 150 if svm.refCount == 0 { 151 return svm.svm, errVMisTerminating 152 } 153 svm.refCount = 0 154 return svm.svm, nil 155 } 156 157 // deleteID deletes the given ID from the map. If the refcount is not 0 or the 158 // VM does not exist, then this function returns an error. 159 func (svmMap *serviceVMMap) deleteID(id string) error { 160 svmMap.Lock() 161 defer svmMap.Unlock() 162 svm, ok := svmMap.svms[id] 163 if !ok { 164 return errVMUnknown 165 } 166 if svm.refCount != 0 { 167 return errVMStillHasReference 168 } 169 delete(svmMap.svms, id) 170 return nil 171 } 172 173 func (svm *serviceVM) signalStartFinished(err error) { 174 svm.Lock() 175 svm.startError = err 176 svm.Unlock() 177 close(svm.startStatus) 178 } 179 180 func (svm *serviceVM) getStartError() error { 181 <-svm.startStatus 182 svm.Lock() 183 defer svm.Unlock() 184 return svm.startError 185 } 186 187 func (svm *serviceVM) signalStopFinished(err error) { 188 svm.Lock() 189 svm.stopError = err 190 svm.Unlock() 191 close(svm.stopStatus) 192 } 193 194 func (svm *serviceVM) getStopError() error { 195 <-svm.stopStatus 196 svm.Lock() 197 defer svm.Unlock() 198 return svm.stopError 199 } 200 201 // hotAddVHDs waits for the service vm to start and then attaches the vhds. 202 func (svm *serviceVM) hotAddVHDs(mvds ...hcsshim.MappedVirtualDisk) error { 203 if err := svm.getStartError(); err != nil { 204 return err 205 } 206 return svm.hotAddVHDsAtStart(mvds...) 207 } 208 209 // hotAddVHDsAtStart works the same way as hotAddVHDs but does not wait for the VM to start. 210 func (svm *serviceVM) hotAddVHDsAtStart(mvds ...hcsshim.MappedVirtualDisk) error { 211 svm.Lock() 212 defer svm.Unlock() 213 for i, mvd := range mvds { 214 if _, ok := svm.attachedVHDs[mvd.HostPath]; ok { 215 svm.attachedVHDs[mvd.HostPath].refCount++ 216 logrus.Debugf("lcowdriver: UVM %s: %s already present, refCount now %d", svm.config.Name, mvd.HostPath, svm.attachedVHDs[mvd.HostPath].refCount) 217 continue 218 } 219 220 svm.attachCounter++ 221 shortContainerPath := remapLongToShortContainerPath(mvd.ContainerPath, svm.attachCounter, svm.config.Name) 222 if err := svm.config.HotAddVhd(mvd.HostPath, shortContainerPath, mvd.ReadOnly, !mvd.AttachOnly); err != nil { 223 svm.hotRemoveVHDsNoLock(mvds[:i]...) 224 return err 225 } 226 svm.attachedVHDs[mvd.HostPath] = &attachedVHD{refCount: 1, attachCounter: svm.attachCounter} 227 } 228 return nil 229 } 230 231 // hotRemoveVHDs waits for the service vm to start and then removes the vhds. 232 // The service VM must not be locked when calling this function. 233 func (svm *serviceVM) hotRemoveVHDs(mvds ...hcsshim.MappedVirtualDisk) error { 234 if err := svm.getStartError(); err != nil { 235 return err 236 } 237 svm.Lock() 238 defer svm.Unlock() 239 return svm.hotRemoveVHDsNoLock(mvds...) 240 } 241 242 // hotRemoveVHDsNoLock removes VHDs from a service VM. When calling this function, 243 // the contract is the service VM lock must be held. 244 func (svm *serviceVM) hotRemoveVHDsNoLock(mvds ...hcsshim.MappedVirtualDisk) error { 245 var retErr error 246 for _, mvd := range mvds { 247 if _, ok := svm.attachedVHDs[mvd.HostPath]; !ok { 248 // We continue instead of returning an error if we try to hot remove a non-existent VHD. 249 // This is because one of the callers of the function is graphdriver.Put(). Since graphdriver.Get() 250 // defers the VM start to the first operation, it's possible that nothing have been hot-added 251 // when Put() is called. To avoid Put returning an error in that case, we simply continue if we 252 // don't find the vhd attached. 253 logrus.Debugf("lcowdriver: UVM %s: %s is not attached, not doing anything", svm.config.Name, mvd.HostPath) 254 continue 255 } 256 257 if svm.attachedVHDs[mvd.HostPath].refCount > 1 { 258 svm.attachedVHDs[mvd.HostPath].refCount-- 259 logrus.Debugf("lcowdriver: UVM %s: %s refCount dropped to %d. not removing from UVM", svm.config.Name, mvd.HostPath, svm.attachedVHDs[mvd.HostPath].refCount) 260 continue 261 } 262 263 // last reference to VHD, so remove from VM and map 264 if err := svm.config.HotRemoveVhd(mvd.HostPath); err == nil { 265 delete(svm.attachedVHDs, mvd.HostPath) 266 } else { 267 // Take note of the error, but still continue to remove the other VHDs 268 logrus.Warnf("Failed to hot remove %s: %s", mvd.HostPath, err) 269 if retErr == nil { 270 retErr = err 271 } 272 } 273 } 274 return retErr 275 } 276 277 func (svm *serviceVM) createExt4VHDX(destFile string, sizeGB uint32, cacheFile string) error { 278 if err := svm.getStartError(); err != nil { 279 return err 280 } 281 282 svm.Lock() 283 defer svm.Unlock() 284 return svm.config.CreateExt4Vhdx(destFile, sizeGB, cacheFile) 285 } 286 287 // getShortContainerPath looks up where a SCSI disk was actually mounted 288 // in a service VM when we remapped a long path name to a short name. 289 func (svm *serviceVM) getShortContainerPath(mvd *hcsshim.MappedVirtualDisk) string { 290 if mvd.ContainerPath == "" { 291 return "" 292 } 293 avhd, ok := svm.attachedVHDs[mvd.HostPath] 294 if !ok { 295 return "" 296 } 297 return fmt.Sprintf("/tmp/d%d", avhd.attachCounter) 298 } 299 300 func (svm *serviceVM) createUnionMount(mountName string, mvds ...hcsshim.MappedVirtualDisk) (err error) { 301 if len(mvds) == 0 { 302 return fmt.Errorf("createUnionMount: error must have at least 1 layer") 303 } 304 305 if err = svm.getStartError(); err != nil { 306 return err 307 } 308 309 svm.Lock() 310 defer svm.Unlock() 311 if _, ok := svm.unionMounts[mountName]; ok { 312 svm.unionMounts[mountName]++ 313 return nil 314 } 315 316 var lowerLayers []string 317 if mvds[0].ReadOnly { 318 lowerLayers = append(lowerLayers, svm.getShortContainerPath(&mvds[0])) 319 } 320 321 for i := 1; i < len(mvds); i++ { 322 lowerLayers = append(lowerLayers, svm.getShortContainerPath(&mvds[i])) 323 } 324 325 logrus.Debugf("Doing the overlay mount with union directory=%s", mountName) 326 errOut := &bytes.Buffer{} 327 if err = svm.runProcess(fmt.Sprintf("mkdir -p %s", mountName), nil, nil, errOut); err != nil { 328 return errors.Wrapf(err, "mkdir -p %s failed (%s)", mountName, errOut.String()) 329 } 330 331 var cmd string 332 if len(mvds) == 1 { 333 // `FROM SCRATCH` case and the only layer. No overlay required. 334 cmd = fmt.Sprintf("mount %s %s", svm.getShortContainerPath(&mvds[0]), mountName) 335 } else if mvds[0].ReadOnly { 336 // Readonly overlay 337 cmd = fmt.Sprintf("mount -t overlay overlay -olowerdir=%s %s", 338 strings.Join(lowerLayers, ","), 339 mountName) 340 } else { 341 upper := fmt.Sprintf("%s/upper", svm.getShortContainerPath(&mvds[0])) 342 work := fmt.Sprintf("%s/work", svm.getShortContainerPath(&mvds[0])) 343 344 errOut := &bytes.Buffer{} 345 if err = svm.runProcess(fmt.Sprintf("mkdir -p %s %s", upper, work), nil, nil, errOut); err != nil { 346 return errors.Wrapf(err, "mkdir -p %s failed (%s)", mountName, errOut.String()) 347 } 348 349 cmd = fmt.Sprintf("mount -t overlay overlay -olowerdir=%s,upperdir=%s,workdir=%s %s", 350 strings.Join(lowerLayers, ":"), 351 upper, 352 work, 353 mountName) 354 } 355 356 logrus.Debugf("createUnionMount: Executing mount=%s", cmd) 357 errOut = &bytes.Buffer{} 358 if err = svm.runProcess(cmd, nil, nil, errOut); err != nil { 359 return errors.Wrapf(err, "%s failed (%s)", cmd, errOut.String()) 360 } 361 362 svm.unionMounts[mountName] = 1 363 return nil 364 } 365 366 func (svm *serviceVM) deleteUnionMount(mountName string, disks ...hcsshim.MappedVirtualDisk) error { 367 if err := svm.getStartError(); err != nil { 368 return err 369 } 370 371 svm.Lock() 372 defer svm.Unlock() 373 if _, ok := svm.unionMounts[mountName]; !ok { 374 return nil 375 } 376 377 if svm.unionMounts[mountName] > 1 { 378 svm.unionMounts[mountName]-- 379 return nil 380 } 381 382 logrus.Debugf("Removing union mount %s", mountName) 383 if err := svm.runProcess(fmt.Sprintf("umount %s", mountName), nil, nil, nil); err != nil { 384 return err 385 } 386 387 delete(svm.unionMounts, mountName) 388 return nil 389 } 390 391 func (svm *serviceVM) runProcess(command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 392 var process hcsshim.Process 393 var err error 394 errOut := &bytes.Buffer{} 395 396 if stderr != nil { 397 process, err = svm.config.RunProcess(command, stdin, stdout, stderr) 398 } else { 399 process, err = svm.config.RunProcess(command, stdin, stdout, errOut) 400 } 401 if err != nil { 402 return err 403 } 404 defer process.Close() 405 406 process.WaitTimeout(time.Duration(int(time.Second) * svm.config.UvmTimeoutSeconds)) 407 exitCode, err := process.ExitCode() 408 if err != nil { 409 return err 410 } 411 412 if exitCode != 0 { 413 // If the caller isn't explicitly capturing stderr output, then capture it here instead. 414 e := fmt.Sprintf("svm.runProcess: command %s failed with exit code %d", command, exitCode) 415 if stderr == nil { 416 e = fmt.Sprintf("%s. (%s)", e, errOut.String()) 417 } 418 return fmt.Errorf(e) 419 } 420 return nil 421 }