Skip to content

Commit 57337af

Browse files
author
Ivan Shvedunov
committed
Add devicemapper GC for persistent rootfs
Upon GC at the Virtlet startup, the virtual block devices that are created by Virtlet are recognized by name and the sector 0 of underlying block device (Virtlet magic header). If they're not related to any active domain, they're removed (note that this doesn't mean the data on the device is affected in any way).
1 parent 219c0ea commit 57337af

10 files changed

+724
-222
lines changed

pkg/blockdev/blockdev.go

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
Copyright 2018 Mirantis
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package blockdev
18+
19+
import (
20+
"crypto/sha256"
21+
"encoding/binary"
22+
"fmt"
23+
"io/ioutil"
24+
"os"
25+
"path/filepath"
26+
"strconv"
27+
"strings"
28+
29+
"github.com/Mirantis/virtlet/pkg/utils"
30+
"github.com/golang/glog"
31+
)
32+
33+
const (
34+
// VirtletLogicalDevicePrefix denotes the required prefix for
35+
// the virtual block devices created by Virtlet.
36+
VirtletLogicalDevicePrefix = "virtlet-dm-"
37+
virtletRootfsMagic = 0x263dbe52ba576702
38+
virtletRootfsMetadataVersion = 1
39+
sectorSize = 512
40+
devnameUeventVar = "DEVNAME="
41+
)
42+
43+
type virtletRootfsHeader struct {
44+
Magic uint64
45+
MetadataVersion uint16
46+
ImageHash [sha256.Size]byte
47+
}
48+
49+
// LogicalDeviceHandler makes it possible to store metadata in the
50+
// first sector of a block device, making the rest of the device
51+
// available as another logical device managed by the device mapper.
52+
type LogicalDeviceHandler struct {
53+
commander utils.Commander
54+
devPath string
55+
sysfsPath string
56+
}
57+
58+
// NewLogicalDeviceHandler creates a new LogicalDeviceHandler using
59+
// the specified commander and paths that should be used in place of
60+
// /dev and /sys directories (empty string to use /dev and /sys,
61+
// respectively)
62+
func NewLogicalDeviceHandler(commander utils.Commander, devPath, sysfsPath string) *LogicalDeviceHandler {
63+
if devPath == "" {
64+
devPath = "/dev"
65+
}
66+
if sysfsPath == "" {
67+
sysfsPath = "/sys"
68+
}
69+
return &LogicalDeviceHandler{commander, devPath, sysfsPath}
70+
}
71+
72+
// EnsureDevHeaderMatches returns true if the specified block device
73+
// has proper Virtlet header that matches the specified image hash
74+
func (ldh *LogicalDeviceHandler) EnsureDevHeaderMatches(devPath string, imageHash [sha256.Size]byte) (bool, error) {
75+
f, err := os.OpenFile(devPath, os.O_RDWR|os.O_SYNC, 0)
76+
if err != nil {
77+
return false, fmt.Errorf("open %q: %v", devPath, err)
78+
}
79+
defer func() {
80+
if f != nil {
81+
f.Close()
82+
}
83+
}()
84+
85+
var hdr virtletRootfsHeader
86+
if err := binary.Read(f, binary.BigEndian, &hdr); err != nil {
87+
return false, fmt.Errorf("reading rootfs header: %v", err)
88+
}
89+
90+
headerMatch := true
91+
switch {
92+
case hdr.Magic != virtletRootfsMagic || hdr.ImageHash != imageHash:
93+
headerMatch = false
94+
if _, err := f.Seek(0, os.SEEK_SET); err != nil {
95+
return false, fmt.Errorf("seek: %v", err)
96+
}
97+
if err := binary.Write(f, binary.BigEndian, virtletRootfsHeader{
98+
Magic: virtletRootfsMagic,
99+
MetadataVersion: virtletRootfsMetadataVersion,
100+
ImageHash: imageHash,
101+
}); err != nil {
102+
return false, fmt.Errorf("writing rootfs header: %v", err)
103+
}
104+
case hdr.MetadataVersion != virtletRootfsMetadataVersion:
105+
// NOTE: we should handle earlier metadata versions
106+
// after we introduce new ones. But we can't handle
107+
// future metadata versions and any non-matching
108+
// metadata versions are future ones currently, so we
109+
// don't want to lose any data here.
110+
return false, fmt.Errorf("unsupported virtlet root device metadata version %v", hdr.MetadataVersion)
111+
}
112+
113+
if err := f.Close(); err != nil {
114+
return false, fmt.Errorf("error closing rootfs device: %v", err)
115+
}
116+
f = nil
117+
return headerMatch, nil
118+
}
119+
120+
// blockDevSizeInSectors returns the size of the block device in sectors
121+
func (ldh *LogicalDeviceHandler) blockDevSizeInSectors(devPath string) (uint64, error) {
122+
// NOTE: this is also doable via ioctl but this way it's
123+
// shorter (no need for fake non-linux impl, extra interface,
124+
// extra fake impl for it). Some links that may help if we
125+
// decide to use the ioctl later on:
126+
// https://github.com/karelzak/util-linux/blob/master/disk-utils/blockdev.c
127+
// https://github.com/aicodix/smr/blob/24aa589f378827a69a07d220f114c169693dacec/smr.go#L29
128+
out, err := ldh.commander.Command("blockdev", "--getsz", devPath).Run(nil)
129+
if err != nil {
130+
return 0, err
131+
}
132+
nSectors, err := strconv.ParseUint(strings.TrimSpace(string(out)), 10, 64)
133+
if err != nil {
134+
return 0, fmt.Errorf("bad size value returned by blockdev: %q: %v", out, err)
135+
}
136+
return nSectors, nil
137+
}
138+
139+
// Map maps the device sectors starting from 1 to a new virtual block
140+
// device. dmName specifies the name of the new device.
141+
func (ldh *LogicalDeviceHandler) Map(devPath, dmName string, imageSize uint64) error {
142+
if !strings.HasPrefix(dmName, VirtletLogicalDevicePrefix) {
143+
return fmt.Errorf("bad logical device name %q: must have prefix %q", dmName, VirtletLogicalDevicePrefix)
144+
}
145+
146+
nSectors, err := ldh.blockDevSizeInSectors(devPath)
147+
if err != nil {
148+
return err
149+
}
150+
151+
// sector 0 is reserved for the Virtlet metadata
152+
minSectors := (imageSize+sectorSize-1)/sectorSize + 1
153+
if nSectors < minSectors {
154+
return fmt.Errorf("block device too small for the image: need at least %d bytes (%d sectors) but got %d bytes (%d sectors)",
155+
minSectors*sectorSize,
156+
minSectors,
157+
nSectors*sectorSize,
158+
nSectors)
159+
}
160+
161+
hostPath, err := filepath.EvalSymlinks(devPath)
162+
if err != nil {
163+
return err
164+
}
165+
166+
dmTable := fmt.Sprintf("0 %d linear %s 1\n", nSectors-1, hostPath)
167+
_, err = ldh.commander.Command("dmsetup", "create", dmName).Run([]byte(dmTable))
168+
return err
169+
}
170+
171+
// Unmap unmaps the virtual block device
172+
func (ldh *LogicalDeviceHandler) Unmap(dmName string) error {
173+
_, err := ldh.commander.Command("dmsetup", "remove", dmName).Run(nil)
174+
return err
175+
}
176+
177+
// ListVirtletLogicalDevices returns a list of logical devices managed
178+
// by Virtlet
179+
func (ldh *LogicalDeviceHandler) ListVirtletLogicalDevices() ([]string, error) {
180+
table, err := ldh.commander.Command("dmsetup", "table").Run(nil)
181+
if err != nil {
182+
return nil, fmt.Errorf("dmsetup table: %v", err)
183+
}
184+
var r []string
185+
for _, l := range strings.Split(string(table), "\n") {
186+
if l == "" {
187+
continue
188+
}
189+
fields := strings.Fields(l)
190+
if len(fields) != 6 || fields[3] != "linear" {
191+
continue
192+
}
193+
virtDevName := fields[0]
194+
if strings.HasSuffix(virtDevName, ":") {
195+
virtDevName = virtDevName[:len(virtDevName)-1]
196+
}
197+
198+
devID := fields[4]
199+
ueventFile := filepath.Join(ldh.sysfsPath, "dev/block", devID, "uevent")
200+
ueventContent, err := ioutil.ReadFile(ueventFile)
201+
if err != nil {
202+
glog.Warningf("error reading %q: %v", ueventFile, err)
203+
continue
204+
}
205+
devName := ""
206+
for _, ul := range strings.Split(string(ueventContent), "\n") {
207+
ul = strings.TrimSpace(ul)
208+
if strings.HasPrefix(ul, devnameUeventVar) {
209+
devName = ul[len(devnameUeventVar):]
210+
break
211+
}
212+
}
213+
if devName == "" {
214+
glog.Warningf("bad uevent file %q: no DEVNAME", ueventFile)
215+
continue
216+
}
217+
218+
isVbd, err := ldh.deviceHasVirtletHeader(devName)
219+
if err != nil {
220+
glog.Warningf("checking device file %q: %v", devName, err)
221+
continue
222+
}
223+
224+
if isVbd {
225+
r = append(r, virtDevName)
226+
}
227+
}
228+
229+
return r, nil
230+
}
231+
232+
func (ldh *LogicalDeviceHandler) deviceHasVirtletHeader(devName string) (bool, error) {
233+
f, err := os.Open(filepath.Join(ldh.devPath, devName))
234+
if err != nil {
235+
return false, err
236+
}
237+
defer f.Close()
238+
239+
var hdr virtletRootfsHeader
240+
if err := binary.Read(f, binary.BigEndian, &hdr); err != nil {
241+
return false, err
242+
}
243+
244+
return hdr.Magic == virtletRootfsMagic, nil
245+
}

0 commit comments

Comments
 (0)