Skip to content

Commit 84f60f5

Browse files
Merge pull request #31 from threefoldtech/main_disk_partitioning
add the ability to partition used disks
2 parents 4c2dc2b + e37beeb commit 84f60f5

File tree

3 files changed

+127
-20
lines changed

3 files changed

+127
-20
lines changed

pkg/storage/filesystem/btrfs_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ func (m *TestDeviceManager) Seektime(ctx context.Context, device string) (zos.De
4242
return zos.DeviceType(args.String(0)), args.Error(1)
4343
}
4444

45+
func (m *TestDeviceManager) ClearCache() {}
46+
4547
func TestBtrfsCreatePoolExists(t *testing.T) {
4648
require := require.New(t)
4749
exe := &TestExecuter{}

pkg/storage/filesystem/device.go

+92-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"os/exec"
78
"path/filepath"
89
"regexp"
10+
"strconv"
911
"strings"
1012

1113
"github.com/pkg/errors"
@@ -25,6 +27,8 @@ type DeviceManager interface {
2527
Mountpoint(ctx context.Context, device string) (string, error)
2628
// Seektime checks device seektime
2729
Seektime(ctx context.Context, device string) (zos.DeviceType, error)
30+
// ClearCache clears the cached devices to refresh
31+
ClearCache()
2832
}
2933

3034
// Devices represents a list of cached in memory devices
@@ -38,9 +42,7 @@ const (
3842
BtrfsFSType FSType = "btrfs"
3943
)
4044

41-
var (
42-
subvolFindmntOption = regexp.MustCompile(`(^|,)subvol=/($|,)`)
43-
)
45+
var subvolFindmntOption = regexp.MustCompile(`(^|,)subvol=/($|,)`)
4446

4547
// blockDevices lsblk output
4648
type blockDevices struct {
@@ -61,6 +63,14 @@ type DeviceInfo struct {
6163
Children []DeviceInfo `json:"children,omitempty"`
6264
}
6365

66+
type DiskSpace struct {
67+
Number int `json:"number"`
68+
Start string `json:"start"`
69+
End string `json:"end"`
70+
Type string `json:"type"`
71+
Size string `json:"size"`
72+
}
73+
6474
func (i *DeviceInfo) Name() string {
6575
return filepath.Base(i.Path)
6676
}
@@ -83,6 +93,81 @@ func (d *DeviceInfo) IsPXEPartition() bool {
8393
return d.Label == "ZOSPXE"
8494
}
8595

96+
func (d *DeviceInfo) IsPartitioned() bool {
97+
return len(d.Children) != 0
98+
}
99+
100+
func (d *DeviceInfo) GetUnallocatedSpaces(ctx context.Context) ([]DiskSpace, error) {
101+
args := []string{
102+
"--json", d.Path, "unit", "B", "print", "free",
103+
}
104+
output, err := exec.CommandContext(ctx, "parted", args...).CombinedOutput()
105+
if err != nil {
106+
return nil, fmt.Errorf("parted command error: %w", err)
107+
}
108+
109+
var diskData struct {
110+
Disk struct {
111+
Partitions []DiskSpace `json:"partitions"`
112+
} `json:"disk"`
113+
}
114+
if err := json.Unmarshal(output, &diskData); err != nil {
115+
return nil, fmt.Errorf("failed to parse parted output: %v", err)
116+
}
117+
118+
validSpaces := []DiskSpace{}
119+
for _, part := range diskData.Disk.Partitions {
120+
if isValidAsDevice(part) {
121+
validSpaces = append(validSpaces, part)
122+
}
123+
}
124+
125+
return validSpaces, nil
126+
}
127+
128+
func (d *DeviceInfo) AllocateEmptySpace(ctx context.Context, space DiskSpace) error {
129+
args := []string{
130+
d.Path, "mkpart", "primary", string(BtrfsFSType), space.Start, space.End,
131+
}
132+
133+
output, err := exec.CommandContext(ctx, "parted", args...).CombinedOutput()
134+
if err != nil {
135+
return fmt.Errorf("parted command error: %w", err)
136+
}
137+
log.Debug().Str("output", string(output)).Msg("allocate empty space parted command")
138+
139+
return nil
140+
}
141+
142+
func (d *DeviceInfo) RefreshDeviceInfo(ctx context.Context) (DeviceInfo, error) {
143+
// notify the kernel with the changed
144+
if err := Partprobe(ctx); err != nil {
145+
return DeviceInfo{}, err
146+
}
147+
148+
// remove the cache
149+
d.mgr.ClearCache()
150+
151+
return d.mgr.Device(ctx, d.Path)
152+
}
153+
154+
func isValidAsDevice(space DiskSpace) bool {
155+
// minimum acceptable device size could be used by zos
156+
const minDeviceSizeBytes = 5 * 1024 * 1024 * 1024 // 5 GiB
157+
158+
spaceSize, err := strconv.ParseUint(strings.TrimSuffix(space.Size, "B"), 10, 64)
159+
if err != nil {
160+
log.Debug().Err(err).Msg("failed converting space size")
161+
return false
162+
}
163+
164+
if space.Type == "free" &&
165+
spaceSize >= minDeviceSizeBytes {
166+
return true
167+
}
168+
return false
169+
}
170+
86171
// lsblkDeviceManager uses the lsblk utility to scann the disk for devices, and
87172
// caches the result.
88173
//
@@ -106,6 +191,10 @@ func defaultDeviceManager(exec executer) DeviceManager {
106191
return m
107192
}
108193

194+
func (l *lsblkDeviceManager) ClearCache() {
195+
l.cache = nil
196+
}
197+
109198
// Devices gets available block devices
110199
func (l *lsblkDeviceManager) Seektime(ctx context.Context, device string) (zos.DeviceType, error) {
111200
log.Debug().Str("device", device).Msg("checking seektim for device")
@@ -160,7 +249,6 @@ func (l *lsblkDeviceManager) Device(ctx context.Context, path string) (device De
160249
}
161250

162251
return device, fmt.Errorf("device not found")
163-
164252
}
165253

166254
func (l *lsblkDeviceManager) lsblk(ctx context.Context) ([]DeviceInfo, error) {
@@ -233,7 +321,6 @@ func (l *lsblkDeviceManager) Mountpoint(ctx context.Context, device string) (str
233321
}
234322

235323
return "", nil
236-
237324
}
238325

239326
func (l *lsblkDeviceManager) raw(ctx context.Context) ([]DeviceInfo, error) {

pkg/storage/storage.go

+33-15
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ const (
3838
cacheCheckDuration = 5 * time.Minute
3939
)
4040

41-
var (
42-
_ pkg.StorageModule = (*Module)(nil)
43-
)
41+
var _ pkg.StorageModule = (*Module)(nil)
4442

4543
// Module implements functionality for pkg.StorageModule
4644
type Module struct {
@@ -148,7 +146,6 @@ func (s *Module) dump() {
148146
device := pool.Device()
149147
log.Debug().Str("path", device.Path).Str("label", pool.Name()).Str("type", string(zos.HDDDevice)).Send()
150148
}
151-
152149
}
153150

154151
// poolType gets the device type of a disk
@@ -196,10 +193,10 @@ func (s *Module) poolType(pool filesystem.Pool, vm bool) (zos.DeviceType, error)
196193
}
197194

198195
func (s *Module) mountPool(device filesystem.DeviceInfo, vm bool) {
199-
log.Debug().Str("path", device.Path).Msg("mounting device")
196+
log.Debug().Any("device", device).Msg("mounting device")
200197

201198
if device.IsPXEPartition() {
202-
log.Info().Str("device", device.Path).Msg("device has 'ZOSPXE' label")
199+
log.Debug().Str("device", device.Path).Msg("skip device has 'ZOSPXE' label")
203200
s.brokenDevices = append(s.brokenDevices, pkg.BrokenDevice{Path: device.Path, Err: fmt.Errorf("device is a PXE partition")})
204201
return
205202
}
@@ -271,16 +268,40 @@ func (s *Module) initialize(ctx context.Context) error {
271268
return err
272269
}
273270

274-
for _, device := range devices {
275-
log.Debug().Msgf("device: %+v", device)
271+
candidateDevices := []filesystem.DeviceInfo{}
272+
for _, dev := range devices {
273+
log.Debug().Any("device", dev).Msg("processing device")
274+
275+
if !dev.IsPartitioned() { // use it as a full disk
276+
candidateDevices = append(candidateDevices, dev)
277+
continue
278+
}
276279

277-
if len(device.Children) != 0 {
278-
for _, part := range device.Children {
279-
s.mountPool(part, vm)
280+
spaces, err := dev.GetUnallocatedSpaces(ctx)
281+
if err != nil { // couldn't get unallocated for any reason, use its partitions as is
282+
candidateDevices = append(candidateDevices, dev.Children...)
283+
continue
284+
}
285+
286+
for _, space := range spaces {
287+
if err := dev.AllocateEmptySpace(ctx, space); err != nil {
288+
log.Error().Err(err).Str("device", dev.Path).Msg("could not allocate empty space")
289+
continue
280290
}
291+
}
292+
293+
newDevice, err := dev.RefreshDeviceInfo(ctx)
294+
if err != nil {
295+
log.Error().Err(err).Str("device", dev.Path).Msg("failed to refresh device info")
281296
continue
282297
}
283-
s.mountPool(device, vm)
298+
299+
candidateDevices = append(candidateDevices, newDevice.Children...)
300+
}
301+
302+
log.Debug().Any("candidate devices", candidateDevices).Send()
303+
for _, dev := range candidateDevices {
304+
s.mountPool(dev, vm)
284305
}
285306

286307
log.Info().
@@ -340,7 +361,6 @@ func (s *Module) Metrics() ([]pkg.PoolMetrics, error) {
340361
for _, pool := range pools {
341362
size := pool.Device().Size
342363
used, err := s.poolUsage(pool)
343-
344364
if err != nil {
345365
log.Error().Err(err).Msg("failed to check pool usage")
346366
continue
@@ -759,7 +779,6 @@ type candidate struct {
759779
}
760780

761781
func (s *Module) findCandidates(size gridtypes.Unit, policy Policy) ([]candidate, error) {
762-
763782
// Look for candidates in mounted pools first
764783
candidates, err := s.checkForCandidates(size, policy)
765784
if err != nil {
@@ -902,7 +921,6 @@ func (s *Module) Monitor(ctx context.Context) <-chan pkg.PoolsStats {
902921
case ch <- values:
903922
}
904923
}
905-
906924
}()
907925

908926
return ch

0 commit comments

Comments
 (0)