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