Skip to content

Commit

Permalink
(feat): Resize Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed May 23, 2024
1 parent 01a4764 commit b2b8d87
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 413 deletions.
1 change: 0 additions & 1 deletion cmd/ebs-bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func main() {
validators := []config.Validator{
config.NewFileSystemValidator(),
config.NewModeValidator(),
config.NewResizeThresholdValidator(),
config.NewMountPointValidator(),
config.NewMountOptionsValidator(),
config.NewOwnerValidator(uos),
Expand Down
5 changes: 2 additions & 3 deletions configs/ubuntu.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defaults:
resizeFs: true
resizeThreshold: 99
resize: true
devices:
/dev/vdb:
fs: xfs
Expand All @@ -17,4 +16,4 @@ devices:
user: ubuntu
group: ubuntu
permissions: 755
lvmConsumption: 30
lvmConsumption: 100
52 changes: 45 additions & 7 deletions internal/backend/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,44 @@ import (
"github.com/reecetech/ebs-bootstrap/internal/service"
)

const (
// The % tolerance to expect the logical volume size to be within
// -------------------------------------------------------
// If the (logical volume / volume group size) * 100 is less than
// (lvmConsumption% - tolerance%) then we perform a resize operation
// -------------------------------------------------------
// If the (logical volume / volume group size) * 100 is greater than
// (lvmConsumption% + tolerance%) then the user is attempting a downsize
// operation. We outright deny this as downsizing can be a destructive
// operation
// -------------------------------------------------------
// Why implement a tolernace-based policy for resizing?
// - When creating a Logical Volume, `ebs-bootstrap` issues a command like
// `lvcreate -l 20%VG -n lv_name vg_name`
// - When we calculate how much percentage of the volume group has been
// consumed by the logical volume, the value would look like 20.0052096...
// - A tolerance establishes a window of acceptable values for avoiding a
// resizing operation
LogicalVolumeResizeTolerance = float64(0.1)
// The % threshold at which to resize a physical volume
// -------------------------------------------------------
// If the (physical volume / device size) * 100 falls
// under this threshold then we perform a resize operation
// -------------------------------------------------------
// The smallest gp3 EBS volume you can create is 1GiB (1073741824 bytes).
// The default size of the extent of a PV is 4 MiB (4194304 bytes).
// Typically, the first extent of a PV is reserved for metadata. This
// produces a PV of size 1069547520 bytes (Usage=99.6093%). We ensure
// that we set the resize threshold to 99.6% to ensure that a 1 GiB EBS
// volume won't be always resized
// -------------------------------------------------------
// Why not just look for a difference of 4194304 bytes?
// - The size of the extent can be changed by the user
// - Therefore we may not always see a difference of 4194304 bytes between
// the block device and physical volume size
PhysicalVolumeResizeThreshold = float64(99.6)
)

type LvmBackend interface {
CreatePhysicalVolume(name string) action.Action
CreateVolumeGroup(name string, physicalVolume string) action.Action
Expand All @@ -19,9 +57,9 @@ type LvmBackend interface {
GetLogicalVolume(name string, volumeGroup string) (*model.LogicalVolume, error)
SearchLogicalVolumes(volumeGroup string) ([]*model.LogicalVolume, error)
SearchVolumeGroup(physicalVolume string) (*model.VolumeGroup, error)
ShouldResizePhysicalVolume(name string, threshold float64) (bool, error)
ShouldResizePhysicalVolume(name string) (bool, error)
ResizePhysicalVolume(name string) action.Action
ShouldResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int, tolerance float64) (bool, error)
ShouldResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) (bool, error)
ResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) action.Action
From(config *config.Config) error
}
Expand Down Expand Up @@ -124,7 +162,7 @@ func (lb *LinuxLvmBackend) ActivateLogicalVolume(name string, volumeGroup string
return action.NewActivateLogicalVolumeAction(name, volumeGroup, lb.lvmService)
}

func (lb *LinuxLvmBackend) ShouldResizePhysicalVolume(name string, threshold float64) (bool, error) {
func (lb *LinuxLvmBackend) ShouldResizePhysicalVolume(name string) (bool, error) {
pvn, err := lb.lvmGraph.GetPhysicalVolume(name)
if err != nil {
return false, nil
Expand All @@ -133,16 +171,16 @@ func (lb *LinuxLvmBackend) ShouldResizePhysicalVolume(name string, threshold flo
if len(dn) == 0 {
return false, nil
}
return (float64(pvn.Size) / float64(dn[0].Size) * 100) < threshold, nil
return (float64(pvn.Size) / float64(dn[0].Size) * 100) < PhysicalVolumeResizeThreshold, nil
}

func (lb *LinuxLvmBackend) ResizePhysicalVolume(name string) action.Action {
return action.NewResizePhysicalVolumeAction(name, lb.lvmService)
}

func (lb *LinuxLvmBackend) ShouldResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int, tolerance float64) (bool, error) {
left := float64(volumeGroupPercent) - tolerance
right := float64(volumeGroupPercent) + tolerance
func (lb *LinuxLvmBackend) ShouldResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) (bool, error) {
left := float64(volumeGroupPercent) - LogicalVolumeResizeTolerance
right := float64(volumeGroupPercent) + LogicalVolumeResizeTolerance
lvn, err := lb.lvmGraph.GetLogicalVolume(name, volumeGroup)
if err != nil {
return false, err
Expand Down
21 changes: 21 additions & 0 deletions internal/backend/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,25 @@ import (
"github.com/reecetech/ebs-bootstrap/internal/service"
)

const (
// The % threshold at which to resize a file system
// -------------------------------------------------------
// If the (file system size / device size) * 100 falls
// under this threshold then we perform a resize operation
// -------------------------------------------------------
// Why is the threshold not set to 100%?
// - A completely extended file system may be size that is
// slightly less than that of the underlying block device
// - This is likely due to reserved sections that store
// file system metadata
// - Therefore we set the threshold to 99.9% to avoid
// unnecessary resize operations
FileSystemResizeThreshold = float64(99.9)
)

type DeviceMetricsBackend interface {
GetBlockDeviceMetrics(name string) (*model.BlockDeviceMetrics, error)
ShouldResize(bdm *model.BlockDeviceMetrics) bool
From(config *config.Config) error
}

Expand Down Expand Up @@ -43,6 +60,10 @@ func (dmb *LinuxDeviceMetricsBackend) GetBlockDeviceMetrics(name string) (*model
return metrics, nil
}

func (dmb *LinuxDeviceMetricsBackend) ShouldResize(bdm *model.BlockDeviceMetrics) bool {
return (float64(bdm.FileSystemSize) / float64(bdm.BlockDeviceSize) * 100) < FileSystemResizeThreshold
}

func (dmb *LinuxDeviceMetricsBackend) From(config *config.Config) error {
dmb.blockDeviceMetrics = nil
blockDeviceMetrics := map[string]*model.BlockDeviceMetrics{}
Expand Down
36 changes: 36 additions & 0 deletions internal/backend/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,42 @@ func TestGetBlockDeviceMetrics(t *testing.T) {
}
}

func TestLinuxDeviceMetricsBackendShouldResize(t *testing.T) {
subtests := []struct {
Name string
BlockDeviceMetrics *model.BlockDeviceMetrics
ExpectedOutput bool
}{
// FileSystemThreshold = 99.9%
// 9989 / 10000 → 99.89% < 99.9% → true
{
Name: "Should Resize",
BlockDeviceMetrics: &model.BlockDeviceMetrics{
FileSystemSize: 9989,
BlockDeviceSize: 10000,
},
ExpectedOutput: true,
},
// FileSystemThreshold = 99.9%
// 9999 / 10000 → 99.9% < 99.9% → false
{
Name: "Should Not Resize",
BlockDeviceMetrics: &model.BlockDeviceMetrics{
FileSystemSize: 9990,
BlockDeviceSize: 10000,
},
ExpectedOutput: false,
},
}
for _, subtest := range subtests {
t.Run(subtest.Name, func(t *testing.T) {
dmb := NewMockLinuxDeviceMetricsBackend(nil)
shouldResize := dmb.ShouldResize(subtest.BlockDeviceMetrics)
utils.CheckOutput("dmb.ShouldResize()", t, subtest.ExpectedOutput, shouldResize)
})
}
}

func TestLinuxDeviceMetricsBackendFrom(t *testing.T) {
fssf := service.NewLinuxFileSystemServiceFactory(nil)

Expand Down
49 changes: 16 additions & 33 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ const (
)

type Flag struct {
Config string
Mode string
Remount bool
MountOptions string
ResizeFs bool
ResizeThreshold float64
LvmConsumption int
Config string
Mode string
Remount bool
MountOptions string
Resize bool
LvmConsumption int
}

type Device struct {
Expand All @@ -38,12 +37,11 @@ type Device struct {
}

type Options struct {
Mode model.Mode `yaml:"mode"`
Remount bool `yaml:"remount"`
MountOptions model.MountOptions `yaml:"mountOptions"`
ResizeFs bool `yaml:"resizeFs"`
ResizeThreshold float64 `yaml:"resizeThreshold"`
LvmConsumption int `yaml:"lvmConsumption"`
Mode model.Mode `yaml:"mode"`
Remount bool `yaml:"remount"`
MountOptions model.MountOptions `yaml:"mountOptions"`
Resize bool `yaml:"resize"`
LvmConsumption int `yaml:"lvmConsumption"`
}

// We don't export "overrides" as this is an attribute that is used
Expand Down Expand Up @@ -100,8 +98,7 @@ func parseFlags(program string, args []string) (*Flag, error) {
flags.StringVar(&f.Mode, "mode", "", "override for mode")
flags.BoolVar(&f.Remount, "remount", false, "override for remount")
flags.StringVar(&f.MountOptions, "mount-options", "", "override for mount options")
flags.BoolVar(&f.ResizeFs, "resize-fs", false, "override for resize filesystem")
flags.Float64Var(&f.ResizeThreshold, "resize-threshold", 0, "override for resize threshold")
flags.BoolVar(&f.Resize, "resize", false, "override for resize filesystem")
flags.IntVar(&f.LvmConsumption, "lvm-consumption", 0, "override for lvm consumption")

// Actually parse the flag
Expand All @@ -117,8 +114,8 @@ func (c *Config) setOverrides(f *Flag) *Config {
c.overrides.Mode = model.Mode(f.Mode)
c.overrides.Remount = f.Remount
c.overrides.MountOptions = model.MountOptions(f.MountOptions)
c.overrides.ResizeFs = f.ResizeFs
c.overrides.ResizeThreshold = f.ResizeThreshold
c.overrides.Resize = f.Resize
c.overrides.LvmConsumption = f.LvmConsumption
return c
}

Expand Down Expand Up @@ -164,26 +161,12 @@ func (c *Config) GetMountOptions(name string) model.MountOptions {
return DefaultMountOptions
}

func (c *Config) GetResizeFs(name string) bool {
func (c *Config) GetResize(name string) bool {
cd, found := c.Devices[name]
if !found {
return false
}
return c.overrides.ResizeFs || c.Defaults.ResizeFs || cd.ResizeFs
}

func (c *Config) GetResizeThreshold(name string) float64 {
cd, found := c.Devices[name]
if !found {
return 0
}
if c.overrides.ResizeThreshold > 0 {
return c.overrides.ResizeThreshold
}
if cd.ResizeThreshold > 0 {
return cd.ResizeThreshold
}
return c.Defaults.ResizeThreshold
return c.overrides.Resize || c.Defaults.Resize || cd.Resize
}

func (c *Config) GetLvmConsumption(name string) int {
Expand Down
Loading

0 comments on commit b2b8d87

Please sign in to comment.