Browse code

devmapper: Add a new option dm.min_free_space

Once thin pool gets full, bad things can happen. Especially in case of xfs
it is possible that xfs keeps on retrying IO infinitely (for certain kind
of IO) and container hangs.

One way to mitigate the problem is that once thin pool is about to get full,
start failing some of the docker operations like pulling new images or
creation of new containers. That way user will get warning ahead of time
and can try to rectify it by creating more free space in thin pool. This
can be done either by deleting existing images/containers or by adding more
free space to thin pool.

This patch adds a new option dm.min_free_space to devicemapper graph
driver. Say one specifies dm.min_free_space=10%. This means atleast
10% of data and metadata blocks should be free in pool before new device
creation is allowed, otherwise operation will fail.

By default min_free_space is 10%. User can change it by specifying
dm.min_free_space=X% on command line. A value of 0% will disable the
check.

Signed-off-by: Vivek Goyal <vgoyal@redhat.com>

Vivek Goyal authored on 2016/03/08 05:27:39
Showing 3 changed files
... ...
@@ -43,11 +43,12 @@ var (
43 43
 	// We retry device removal so many a times that even error messages
44 44
 	// will fill up console during normal operation. So only log Fatal
45 45
 	// messages by default.
46
-	logLevel                     = devicemapper.LogLevelFatal
47
-	driverDeferredRemovalSupport = false
48
-	enableDeferredRemoval        = false
49
-	enableDeferredDeletion       = false
50
-	userBaseSize                 = false
46
+	logLevel                            = devicemapper.LogLevelFatal
47
+	driverDeferredRemovalSupport        = false
48
+	enableDeferredRemoval               = false
49
+	enableDeferredDeletion              = false
50
+	userBaseSize                        = false
51
+	defaultMinFreeSpacePercent   uint32 = 10
51 52
 )
52 53
 
53 54
 const deviceSetMetaFile string = "deviceset-metadata"
... ...
@@ -122,6 +123,7 @@ type DeviceSet struct {
122 122
 	deletionWorkerTicker  *time.Ticker
123 123
 	uidMaps               []idtools.IDMap
124 124
 	gidMaps               []idtools.IDMap
125
+	minFreeSpacePercent   uint32 //min free space percentage in thinpool
125 126
 }
126 127
 
127 128
 // DiskUsage contains information about disk usage and is used when reporting Status of a device.
... ...
@@ -753,6 +755,38 @@ func (devices *DeviceSet) getNextFreeDeviceID() (int, error) {
753 753
 	return 0, fmt.Errorf("devmapper: Unable to find a free device ID")
754 754
 }
755 755
 
756
+func (devices *DeviceSet) poolHasFreeSpace() error {
757
+	if devices.minFreeSpacePercent == 0 {
758
+		return nil
759
+	}
760
+
761
+	_, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus()
762
+	if err != nil {
763
+		return err
764
+	}
765
+
766
+	minFreeData := (dataTotal * uint64(devices.minFreeSpacePercent)) / 100
767
+	if minFreeData < 1 {
768
+		minFreeData = 1
769
+	}
770
+	dataFree := dataTotal - dataUsed
771
+	if dataFree < minFreeData {
772
+		return fmt.Errorf("devmapper: Thin Pool has %v free data blocks which is less than minimum required %v free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior", (dataTotal - dataUsed), minFreeData)
773
+	}
774
+
775
+	minFreeMetadata := (metadataTotal * uint64(devices.minFreeSpacePercent)) / 100
776
+	if minFreeMetadata < 1 {
777
+		minFreeData = 1
778
+	}
779
+
780
+	metadataFree := metadataTotal - metadataUsed
781
+	if metadataFree < minFreeMetadata {
782
+		return fmt.Errorf("devmapper: Thin Pool has %v free metadata blocks which is less than minimum required %v free metadata blocks. Create more free metadata space in thin pool or use dm.min_free_space option to change behavior", (metadataTotal - metadataUsed), minFreeMetadata)
783
+	}
784
+
785
+	return nil
786
+}
787
+
756 788
 func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) {
757 789
 	devices.Lock()
758 790
 	defer devices.Unlock()
... ...
@@ -809,6 +843,10 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) {
809 809
 }
810 810
 
811 811
 func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInfo) error {
812
+	if err := devices.poolHasFreeSpace(); err != nil {
813
+		return err
814
+	}
815
+
812 816
 	deviceID, err := devices.getNextFreeDeviceID()
813 817
 	if err != nil {
814 818
 		return err
... ...
@@ -2437,6 +2475,7 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [
2437 2437
 		deletionWorkerTicker:  time.NewTicker(time.Second * 30),
2438 2438
 		uidMaps:               uidMaps,
2439 2439
 		gidMaps:               gidMaps,
2440
+		minFreeSpacePercent:   defaultMinFreeSpacePercent,
2440 2441
 	}
2441 2442
 
2442 2443
 	foundBlkDiscard := false
... ...
@@ -2512,6 +2551,22 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [
2512 2512
 				return nil, err
2513 2513
 			}
2514 2514
 
2515
+		case "dm.min_free_space":
2516
+			if !strings.HasSuffix(val, "%") {
2517
+				return nil, fmt.Errorf("devmapper: Option dm.min_free_space requires %% suffix")
2518
+			}
2519
+
2520
+			valstring := strings.TrimSuffix(val, "%")
2521
+			minFreeSpacePercent, err := strconv.ParseUint(valstring, 10, 32)
2522
+			if err != nil {
2523
+				return nil, err
2524
+			}
2525
+
2526
+			if minFreeSpacePercent >= 100 {
2527
+				return nil, fmt.Errorf("devmapper: Invalid value %v for option dm.min_free_space", val)
2528
+			}
2529
+
2530
+			devices.minFreeSpacePercent = uint32(minFreeSpacePercent)
2515 2531
 		default:
2516 2532
 			return nil, fmt.Errorf("devmapper: Unknown option %s\n", key)
2517 2533
 		}
... ...
@@ -438,6 +438,32 @@ options for `zfs` start with `zfs`.
438 438
     when unintentional leaking of mount point happens across multiple mount
439 439
     namespaces.
440 440
 
441
+*  `dm.min_free_space`
442
+
443
+    Specifies the min free space percent in thin pool require for new device
444
+    creation to succeed. This check applies to both free data space as well
445
+    as free metadata space. Valid values are from 0% - 99%. Value 0% disables
446
+    free space checking logic. If user does not specify a value for this optoin,
447
+    then default value for this option is 10%.
448
+
449
+    Whenever a new thin pool device is created (during docker pull or
450
+    during container creation), docker will check minimum free space is
451
+    available as specified by this parameter. If that is not the case, then
452
+    device creation will fail and docker operation will fail.
453
+
454
+    One will have to create more free space in thin pool to recover from the
455
+    error. Either delete some of the images and containers from thin pool and
456
+    create free space or add more storage to thin pool.
457
+
458
+    For lvm thin pool, one can add more storage to volume group container thin
459
+    pool and that should automatically resolve it. If loop devices are being
460
+    used, then stop docker, grow the size of loop files and restart docker and
461
+    that should resolve the issue.
462
+
463
+    Example use:
464
+
465
+        $ docker daemon --storage-opt dm.min_free_space_percent=10%
466
+
441 467
 Currently supported options of `zfs`:
442 468
 
443 469
 * `zfs.fsname`
... ...
@@ -475,6 +475,30 @@ By default docker will pick up the zfs filesystem where docker graph
475 475
 
476 476
 Example use: `docker daemon -s zfs --storage-opt zfs.fsname=zroot/docker`
477 477
 
478
+#### dm.min_free_space
479
+
480
+Specifies the min free space percent in thin pool require for new device
481
+creation to succeed. This check applies to both free data space as well
482
+as free metadata space. Valid values are from 0% - 99%. Value 0% disables
483
+free space checking logic. If user does not specify a value for this optoin,
484
+then default value for this option is 10%.
485
+
486
+Whenever a new thin pool device is created (during docker pull or
487
+during container creation), docker will check minimum free space is
488
+available as specified by this parameter. If that is not the case, then
489
+device creation will fail and docker operation will fail.
490
+
491
+One will have to create more free space in thin pool to recover from the
492
+error. Either delete some of the images and containers from thin pool and
493
+create free space or add more storage to thin pool.
494
+
495
+For lvm thin pool, one can add more storage to volume group container thin
496
+pool and that should automatically resolve it. If loop devices are being
497
+used, then stop docker, grow the size of loop files and restart docker and
498
+that should resolve the issue.
499
+
500
+Example use: `docker daemon --storage-opt dm.min_free_space_percent=10%`
501
+
478 502
 # CLUSTER STORE OPTIONS
479 503
 
480 504
 The daemon uses libkv to advertise