Skip to content

Commit

Permalink
Merge pull request #31 from threefoldtech/main_disk_partitioning
Browse files Browse the repository at this point in the history
add the ability to partition used disks
  • Loading branch information
Omarabdul3ziz authored Dec 30, 2024
2 parents 4c2dc2b + e37beeb commit 84f60f5
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 20 deletions.
2 changes: 2 additions & 0 deletions pkg/storage/filesystem/btrfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func (m *TestDeviceManager) Seektime(ctx context.Context, device string) (zos.De
return zos.DeviceType(args.String(0)), args.Error(1)
}

func (m *TestDeviceManager) ClearCache() {}

func TestBtrfsCreatePoolExists(t *testing.T) {
require := require.New(t)
exe := &TestExecuter{}
Expand Down
97 changes: 92 additions & 5 deletions pkg/storage/filesystem/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"

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

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

var (
subvolFindmntOption = regexp.MustCompile(`(^|,)subvol=/($|,)`)
)
var subvolFindmntOption = regexp.MustCompile(`(^|,)subvol=/($|,)`)

// blockDevices lsblk output
type blockDevices struct {
Expand All @@ -61,6 +63,14 @@ type DeviceInfo struct {
Children []DeviceInfo `json:"children,omitempty"`
}

type DiskSpace struct {
Number int `json:"number"`
Start string `json:"start"`
End string `json:"end"`
Type string `json:"type"`
Size string `json:"size"`
}

func (i *DeviceInfo) Name() string {
return filepath.Base(i.Path)
}
Expand All @@ -83,6 +93,81 @@ func (d *DeviceInfo) IsPXEPartition() bool {
return d.Label == "ZOSPXE"
}

func (d *DeviceInfo) IsPartitioned() bool {
return len(d.Children) != 0
}

func (d *DeviceInfo) GetUnallocatedSpaces(ctx context.Context) ([]DiskSpace, error) {
args := []string{
"--json", d.Path, "unit", "B", "print", "free",
}
output, err := exec.CommandContext(ctx, "parted", args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("parted command error: %w", err)
}

var diskData struct {
Disk struct {
Partitions []DiskSpace `json:"partitions"`
} `json:"disk"`
}
if err := json.Unmarshal(output, &diskData); err != nil {
return nil, fmt.Errorf("failed to parse parted output: %v", err)
}

validSpaces := []DiskSpace{}
for _, part := range diskData.Disk.Partitions {
if isValidAsDevice(part) {
validSpaces = append(validSpaces, part)
}
}

return validSpaces, nil
}

func (d *DeviceInfo) AllocateEmptySpace(ctx context.Context, space DiskSpace) error {
args := []string{
d.Path, "mkpart", "primary", string(BtrfsFSType), space.Start, space.End,
}

output, err := exec.CommandContext(ctx, "parted", args...).CombinedOutput()
if err != nil {
return fmt.Errorf("parted command error: %w", err)
}
log.Debug().Str("output", string(output)).Msg("allocate empty space parted command")

return nil
}

func (d *DeviceInfo) RefreshDeviceInfo(ctx context.Context) (DeviceInfo, error) {
// notify the kernel with the changed
if err := Partprobe(ctx); err != nil {
return DeviceInfo{}, err
}

// remove the cache
d.mgr.ClearCache()

return d.mgr.Device(ctx, d.Path)
}

func isValidAsDevice(space DiskSpace) bool {
// minimum acceptable device size could be used by zos
const minDeviceSizeBytes = 5 * 1024 * 1024 * 1024 // 5 GiB

spaceSize, err := strconv.ParseUint(strings.TrimSuffix(space.Size, "B"), 10, 64)
if err != nil {
log.Debug().Err(err).Msg("failed converting space size")
return false
}

if space.Type == "free" &&
spaceSize >= minDeviceSizeBytes {
return true
}
return false
}

// lsblkDeviceManager uses the lsblk utility to scann the disk for devices, and
// caches the result.
//
Expand All @@ -106,6 +191,10 @@ func defaultDeviceManager(exec executer) DeviceManager {
return m
}

func (l *lsblkDeviceManager) ClearCache() {
l.cache = nil
}

// Devices gets available block devices
func (l *lsblkDeviceManager) Seektime(ctx context.Context, device string) (zos.DeviceType, error) {
log.Debug().Str("device", device).Msg("checking seektim for device")
Expand Down Expand Up @@ -160,7 +249,6 @@ func (l *lsblkDeviceManager) Device(ctx context.Context, path string) (device De
}

return device, fmt.Errorf("device not found")

}

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

return "", nil

}

func (l *lsblkDeviceManager) raw(ctx context.Context) ([]DeviceInfo, error) {
Expand Down
48 changes: 33 additions & 15 deletions pkg/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ const (
cacheCheckDuration = 5 * time.Minute
)

var (
_ pkg.StorageModule = (*Module)(nil)
)
var _ pkg.StorageModule = (*Module)(nil)

// Module implements functionality for pkg.StorageModule
type Module struct {
Expand Down Expand Up @@ -148,7 +146,6 @@ func (s *Module) dump() {
device := pool.Device()
log.Debug().Str("path", device.Path).Str("label", pool.Name()).Str("type", string(zos.HDDDevice)).Send()
}

}

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

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

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

for _, device := range devices {
log.Debug().Msgf("device: %+v", device)
candidateDevices := []filesystem.DeviceInfo{}
for _, dev := range devices {
log.Debug().Any("device", dev).Msg("processing device")

if !dev.IsPartitioned() { // use it as a full disk
candidateDevices = append(candidateDevices, dev)
continue
}

if len(device.Children) != 0 {
for _, part := range device.Children {
s.mountPool(part, vm)
spaces, err := dev.GetUnallocatedSpaces(ctx)
if err != nil { // couldn't get unallocated for any reason, use its partitions as is
candidateDevices = append(candidateDevices, dev.Children...)
continue
}

for _, space := range spaces {
if err := dev.AllocateEmptySpace(ctx, space); err != nil {
log.Error().Err(err).Str("device", dev.Path).Msg("could not allocate empty space")
continue
}
}

newDevice, err := dev.RefreshDeviceInfo(ctx)
if err != nil {
log.Error().Err(err).Str("device", dev.Path).Msg("failed to refresh device info")
continue
}
s.mountPool(device, vm)

candidateDevices = append(candidateDevices, newDevice.Children...)
}

log.Debug().Any("candidate devices", candidateDevices).Send()
for _, dev := range candidateDevices {
s.mountPool(dev, vm)
}

log.Info().
Expand Down Expand Up @@ -340,7 +361,6 @@ func (s *Module) Metrics() ([]pkg.PoolMetrics, error) {
for _, pool := range pools {
size := pool.Device().Size
used, err := s.poolUsage(pool)

if err != nil {
log.Error().Err(err).Msg("failed to check pool usage")
continue
Expand Down Expand Up @@ -759,7 +779,6 @@ type candidate struct {
}

func (s *Module) findCandidates(size gridtypes.Unit, policy Policy) ([]candidate, error) {

// Look for candidates in mounted pools first
candidates, err := s.checkForCandidates(size, policy)
if err != nil {
Expand Down Expand Up @@ -902,7 +921,6 @@ func (s *Module) Monitor(ctx context.Context) <-chan pkg.PoolsStats {
case ch <- values:
}
}

}()

return ch
Expand Down

0 comments on commit 84f60f5

Please sign in to comment.