forked from moby/moby
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
devicemapper: Add trim-pool driver command
This command suspends the pool, extracts all metadata from the metadata pool and then manually discards all regions not in use on the data device. This will re-sparsify the underlying loopback file and regain space on the host operating system. This is required in some cases because the discards we do when deleting images and containers isn't enought to fully free all space unless you have a very new kernel. See: moby#3182 (comment) Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)
- Loading branch information
1 parent
4a4a814
commit 1ec7116
Showing
5 changed files
with
420 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// +build linux,amd64 | ||
|
||
package devmapper | ||
|
||
import ( | ||
"encoding/xml" | ||
"fmt" | ||
"io" | ||
"os/exec" | ||
"strconv" | ||
) | ||
|
||
type MetadataDecoder struct { | ||
d *xml.Decoder | ||
ranges *Ranges | ||
} | ||
|
||
func NewMetadataDecoder(reader io.Reader) *MetadataDecoder { | ||
m := &MetadataDecoder{ | ||
d: xml.NewDecoder(reader), | ||
ranges: NewRanges(), | ||
} | ||
|
||
return m | ||
} | ||
|
||
func (m *MetadataDecoder) parseRange(start *xml.StartElement) error { | ||
var begin, length uint64 | ||
var err error | ||
for _, attr := range start.Attr { | ||
switch attr.Name.Local { | ||
case "data_begin": | ||
begin, err = strconv.ParseUint(attr.Value, 10, 64) | ||
if err != nil { | ||
return err | ||
} | ||
case "length": | ||
length, err = strconv.ParseUint(attr.Value, 10, 64) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
m.ranges.Add(begin, begin+length) | ||
|
||
m.d.Skip() | ||
return nil | ||
} | ||
|
||
func (m *MetadataDecoder) parseSingle(start *xml.StartElement) error { | ||
for _, attr := range start.Attr { | ||
switch attr.Name.Local { | ||
case "data_block": | ||
block, err := strconv.ParseUint(attr.Value, 10, 64) | ||
if err != nil { | ||
return err | ||
} | ||
m.ranges.Add(block, block+1) | ||
} | ||
} | ||
|
||
m.d.Skip() | ||
|
||
return nil | ||
} | ||
|
||
func (m *MetadataDecoder) parseDevice(start *xml.StartElement) error { | ||
for { | ||
tok, err := m.d.Token() | ||
if err != nil { | ||
return err | ||
} | ||
switch tok := tok.(type) { | ||
case xml.StartElement: | ||
switch tok.Name.Local { | ||
case "range_mapping": | ||
if err := m.parseRange(&tok); err != nil { | ||
return err | ||
} | ||
case "single_mapping": | ||
if err := m.parseSingle(&tok); err != nil { | ||
return err | ||
} | ||
default: | ||
return fmt.Errorf("Unknown tag type %s\n", tok.Name) | ||
} | ||
case xml.EndElement: | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func (m *MetadataDecoder) readStart() (*xml.StartElement, error) { | ||
for { | ||
tok, err := m.d.Token() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch tok := tok.(type) { | ||
case xml.StartElement: | ||
return &tok, nil | ||
|
||
case xml.EndElement: | ||
return nil, fmt.Errorf("Unbalanced tags") | ||
} | ||
} | ||
} | ||
|
||
func (m *MetadataDecoder) parseMetadata() error { | ||
start, err := m.readStart() | ||
if err != nil { | ||
return err | ||
} | ||
if start.Name.Local != "superblock" { | ||
return fmt.Errorf("Unexpected tag type %s", start.Name) | ||
} | ||
|
||
for { | ||
tok, err := m.d.Token() | ||
if err != nil { | ||
return err | ||
} | ||
switch tok := tok.(type) { | ||
case xml.StartElement: | ||
switch tok.Name.Local { | ||
case "device": | ||
m.parseDevice(&tok) | ||
default: | ||
return fmt.Errorf("Unknown tag type %s\n", tok.Name) | ||
} | ||
case xml.EndElement: | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func readMetadataRanges(file string) (*Ranges, error) { | ||
cmd := exec.Command("thin_dump", file) | ||
|
||
stdout, err := cmd.StdoutPipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
m := NewMetadataDecoder(stdout) | ||
|
||
errChan := make(chan error) | ||
|
||
go func() { | ||
err = m.parseMetadata() | ||
errChan <- err | ||
}() | ||
|
||
if err := cmd.Run(); err != nil { | ||
return nil, err | ||
} | ||
|
||
err = <-errChan | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return m.ranges, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// +build linux,amd64 | ||
|
||
package devmapper | ||
|
||
import ( | ||
"container/list" | ||
"fmt" | ||
) | ||
|
||
type Range struct { | ||
begin uint64 | ||
end uint64 | ||
} | ||
|
||
type Ranges struct { | ||
*list.List | ||
} | ||
|
||
func NewRanges() *Ranges { | ||
return &Ranges{list.New()} | ||
} | ||
|
||
func (r *Ranges) ToString() string { | ||
s := "" | ||
for e := r.Front(); e != nil; e = e.Next() { | ||
r := e.Value.(*Range) | ||
if s != "" { | ||
s = s + "," | ||
} | ||
s = fmt.Sprintf("%s%d-%d", s, r.begin, r.end) | ||
} | ||
return s | ||
} | ||
|
||
func (r *Ranges) Clear() { | ||
r.Init() | ||
} | ||
|
||
func (r *Ranges) Add(begin, end uint64) { | ||
var next *list.Element | ||
for e := r.Front(); e != nil; e = next { | ||
next = e.Next() | ||
|
||
existing := e.Value.(*Range) | ||
|
||
// If existing range is fully to the left, skip | ||
if existing.end < begin { | ||
continue | ||
} | ||
|
||
// If new range is fully to the left, just insert | ||
if end < existing.begin { | ||
r.InsertBefore(&Range{begin, end}, e) | ||
return | ||
} | ||
|
||
// Now we know the two ranges somehow intersect (or at least touch) | ||
|
||
// Extend existing range with the new range | ||
if begin < existing.begin { | ||
existing.begin = begin | ||
} | ||
|
||
// If the new range is completely covered by existing range, we're done | ||
if end <= existing.end { | ||
return | ||
} | ||
|
||
// Otherwise strip r from new range | ||
begin = existing.end | ||
|
||
// We're now touching r at the end, and so we need to either extend r | ||
// or merge with next | ||
|
||
if next == nil { | ||
// Nothing after, extend | ||
existing.end = end | ||
return | ||
} | ||
|
||
nextR := next.Value.(*Range) | ||
if end < nextR.begin { | ||
// Fits, Just extend | ||
existing.end = end | ||
return | ||
} | ||
|
||
// The new region overlaps the next, merge the two | ||
nextR.begin = existing.begin | ||
r.Remove(e) | ||
} | ||
|
||
// nothing in list or everything to the left, just append the rest | ||
if begin < end { | ||
r.PushBack(&Range{begin, end}) | ||
return | ||
} | ||
} |
Oops, something went wrong.