Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 45 additions & 37 deletions storage/drivers/btrfs/btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,20 @@ func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idt
// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
return d.create(id, parent, opts, true)
return d.create(id, parent, opts, false)
}

// Create the filesystem with given id.
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
return d.create(id, parent, opts, false)
return d.create(id, parent, opts, true)
}

func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, applyDriverDefaultQuota bool) error {
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnly bool) error {
quota, err := d.parseStorageOpt(opts, readOnly)
if err != nil {
return err
}

quotas := d.quotasDir()
subvolumes := d.subvolumesDir()
if err := os.MkdirAll(subvolumes, 0o700); err != nil {
Expand All @@ -518,34 +523,14 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, applyDr
}
}

var storageOpt map[string]string
if opts != nil {
storageOpt = opts.StorageOpt
}

var quotaSize uint64
var needQuota bool
if _, ok := storageOpt["size"]; ok {
driver := &Driver{}
if err := d.parseStorageOpt(storageOpt, driver); err != nil {
return err
}
quotaSize = driver.options.size
needQuota = true
}
if !needQuota && applyDriverDefaultQuota && d.options.size > 0 {
quotaSize = d.options.size
needQuota = true
}
if needQuota {
layerDriver := &Driver{options: btrfsOptions{size: quotaSize}}
if err := d.setStorageSize(path.Join(subvolumes, id), layerDriver); err != nil {
if quota != nil {
if err := d.setStorageSize(path.Join(subvolumes, id), *quota); err != nil {
return err
}
if err := os.MkdirAll(quotas, 0o700); err != nil {
return err
}
if err := os.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(quotaSize)), 0o644); err != nil {
if err := os.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(quota.size)), 0o644); err != nil {
return err
}
}
Expand All @@ -558,40 +543,63 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, applyDr
return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
}

// Parse btrfs storage options
func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
// layerQuota contains per-layer quota settings.
type layerQuota struct {
size uint64
}

// parseStorageOpt parses CreateOpts.StorageOpt.
// Returns a *layerQuota if a quota should be applied, nil otherwise.
func (d *Driver) parseStorageOpt(opts *graphdriver.CreateOpts, readOnly bool) (*layerQuota, error) {
var storageOpt map[string]string = nil // Iterating over a nil map is safe
if opts != nil {
storageOpt = opts.StorageOpt
}

res := layerQuota{}
needQuota := false

if !readOnly && d.options.size > 0 {
res.size = d.options.size
needQuota = true
}

// Read size to change the subvolume disk quota per container
for key, val := range storageOpt {
key := strings.ToLower(key)
switch key {
case "size":
size, err := units.RAMInBytes(val)
if err != nil {
return err
return nil, err
}
driver.options.size = uint64(size)
res.size = uint64(size)
needQuota = true
default:
return fmt.Errorf("unknown option %s (%q)", key, storageOpt)
return nil, fmt.Errorf("unknown option %s (%q)", key, storageOpt)
}
}

return nil
if needQuota {
return &res, nil
}
return nil, nil
}

// Set btrfs storage size
func (d *Driver) setStorageSize(dir string, driver *Driver) error {
if driver.options.size <= 0 {
return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size)))
func (d *Driver) setStorageSize(dir string, quota layerQuota) error {
if quota.size <= 0 {
return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(quota.size)))
}
if d.options.minSpace > 0 && driver.options.size < d.options.minSpace {
if d.options.minSpace > 0 && quota.size < d.options.minSpace {
return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace)))
}

if err := d.enableQuota(); err != nil {
return err
}

if err := subvolLimitQgroup(dir, driver.options.size); err != nil {
if err := subvolLimitQgroup(dir, quota.size); err != nil {
return err
}

Expand Down
80 changes: 30 additions & 50 deletions storage/drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -976,46 +976,16 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
return fmt.Errorf("--storage-opt is supported only for overlay over xfs with 'pquota' mount option")
}

if opts == nil {
opts = &graphdriver.CreateOpts{
StorageOpt: map[string]string{},
}
}

if d.options.forceMask != nil && d.options.mountProgram == "" {
return fmt.Errorf("overlay: force_mask option for writeable layers is only supported with a mount_program")
}

if _, ok := opts.StorageOpt["size"]; !ok {
if opts.StorageOpt == nil {
opts.StorageOpt = map[string]string{}
}
opts.StorageOpt["size"] = strconv.FormatUint(d.options.quota.Size, 10)
}

if _, ok := opts.StorageOpt["inodes"]; !ok {
if opts.StorageOpt == nil {
opts.StorageOpt = map[string]string{}
}
opts.StorageOpt["inodes"] = strconv.FormatUint(d.options.quota.Inodes, 10)
}

return d.create(id, parent, opts, false)
}

// Create is used to create the upper, lower, and merge directories required for overlay fs for a given id.
// The parent filesystem is used to configure these directories for the overlay.
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {
if opts != nil && len(opts.StorageOpt) != 0 {
if _, ok := opts.StorageOpt["size"]; ok {
return fmt.Errorf("--storage-opt size is only supported for ReadWrite Layers")
}

if _, ok := opts.StorageOpt["inodes"]; ok {
return fmt.Errorf("--storage-opt inodes is only supported for ReadWrite Layers")
}
}

return d.create(id, parent, opts, true)
}

Expand Down Expand Up @@ -1063,6 +1033,11 @@ func (d *Driver) getLayerPermissions(parent string, uidMaps, gidMaps []idtools.I
}

func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnly bool) (retErr error) {
quota, err := d.parseStorageOpt(opts, readOnly) // Do this even for read-only layers, to allow rejecting quota options
if err != nil {
return err
}

dir, homedir, _ := d.dir2(id, readOnly)

disableQuota := readOnly
Expand Down Expand Up @@ -1112,19 +1087,6 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
}()

if d.quotaCtl != nil && !disableQuota {
quota := quota.Quota{}
if opts != nil && len(opts.StorageOpt) > 0 {
driver := &Driver{}
if err := d.parseStorageOpt(opts.StorageOpt, driver); err != nil {
return err
}
if driver.options.quota.Size > 0 {
quota.Size = driver.options.quota.Size
}
if driver.options.quota.Inodes > 0 {
quota.Inodes = driver.options.quota.Inodes
}
}
// Set container disk quota limit
// If it is set to 0, we will track the disk usage, but not enforce a limit
if err := d.quotaCtl.SetQuota(dir, quota); err != nil {
Expand Down Expand Up @@ -1182,29 +1144,47 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
}

// Parse overlay storage options
func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
func (d *Driver) parseStorageOpt(opts *graphdriver.CreateOpts, readOnly bool) (quota.Quota, error) {
var storageOpt map[string]string = nil // Iterating over a nil map is safe
if opts != nil {
storageOpt = opts.StorageOpt
}

res := quota.Quota{}

if !readOnly {
res.Size = d.options.quota.Size
res.Inodes = d.options.quota.Inodes
}

// Read size to set the disk project quota per container
for key, val := range storageOpt {
key := strings.ToLower(key)
switch key {
case "size":
if readOnly {
return quota.Quota{}, fmt.Errorf("--storage-opt size is only supported for ReadWrite Layers")
}
size, err := units.RAMInBytes(val)
if err != nil {
return err
return quota.Quota{}, err
}
driver.options.quota.Size = uint64(size)
res.Size = uint64(size)
case "inodes":
if readOnly {
return quota.Quota{}, fmt.Errorf("--storage-opt inodes is only supported for ReadWrite Layers")
}
inodes, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return err
return quota.Quota{}, err
}
driver.options.quota.Inodes = inodes
res.Inodes = inodes
default:
return fmt.Errorf("unknown option %s", key)
return quota.Quota{}, fmt.Errorf("unknown option %s", key)
}
}

return nil
return res, nil
}

func (d *Driver) getLower(parent string) (string, error) {
Expand Down
Loading