Skip to content

Commit

Permalink
(feat): Add Logical Volume resizing capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed May 23, 2024
1 parent 09cb4f2 commit 24592ed
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 52 deletions.
1 change: 1 addition & 0 deletions cmd/ebs-bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func main() {
layer.NewCreateVolumeGroupLayer(lb),
layer.NewCreateLogicalVolumeLayer(lb),
layer.NewActivateLogicalVolumeLayer(lb),
layer.NewResizeLogicalVolumeLayer(lb),
}
checkError(le.Execute(lvmLayers))

Expand Down
3 changes: 2 additions & 1 deletion configs/ubuntu.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
defaults:
lvmConsumption: 100
resizeFs: true
resizeThreshold: 99
devices:
Expand All @@ -10,10 +9,12 @@ devices:
user: ubuntu
group: ubuntu
permissions: 755
lvmConsumption: 100
/dev/vdc:
fs: xfs
lvm: ifmx-var
mountPoint: /mnt/bar
user: ubuntu
group: ubuntu
permissions: 755
lvmConsumption: 20
73 changes: 58 additions & 15 deletions internal/action/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,25 @@ func (a *CreateVolumeGroupAction) Success() string {
}

type CreateLogicalVolumeAction struct {
name string
freeSpacePercent int
volumeGroup string
mode model.Mode
lvmService service.LvmService
name string
volumeGroupPercent int
volumeGroup string
mode model.Mode
lvmService service.LvmService
}

func NewCreateLogicalVolumeAction(name string, freeSpacePercent int, volumeGroup string, ls service.LvmService) *CreateLogicalVolumeAction {
func NewCreateLogicalVolumeAction(name string, volumeGroupPercent int, volumeGroup string, ls service.LvmService) *CreateLogicalVolumeAction {
return &CreateLogicalVolumeAction{
name: name,
freeSpacePercent: freeSpacePercent,
volumeGroup: volumeGroup,
mode: model.Empty,
lvmService: ls,
name: name,
volumeGroupPercent: volumeGroupPercent,
volumeGroup: volumeGroup,
mode: model.Empty,
lvmService: ls,
}
}

func (a *CreateLogicalVolumeAction) Execute() error {
return a.lvmService.CreateLogicalVolume(a.name, a.volumeGroup, a.freeSpacePercent)
return a.lvmService.CreateLogicalVolume(a.name, a.volumeGroup, a.volumeGroupPercent)
}

func (a *CreateLogicalVolumeAction) GetMode() model.Mode {
Expand All @@ -119,15 +119,15 @@ func (a *CreateLogicalVolumeAction) SetMode(mode model.Mode) Action {
}

func (a *CreateLogicalVolumeAction) Prompt() string {
return fmt.Sprintf("Would you like to create logical volume %s that consumes %d%% free space of volume group %s", a.name, a.freeSpacePercent, a.volumeGroup)
return fmt.Sprintf("Would you like to create logical volume %s that consumes %d%% of the space of volume group %s", a.name, a.volumeGroupPercent, a.volumeGroup)
}

func (a *CreateLogicalVolumeAction) Refuse() string {
return fmt.Sprintf("Refused to create logical volume %s that consumes %d%% free space of volume group %s", a.name, a.freeSpacePercent, a.volumeGroup)
return fmt.Sprintf("Refused to create logical volume %s that consumes %d%% of the space of volume group %s", a.name, a.volumeGroupPercent, a.volumeGroup)
}

func (a *CreateLogicalVolumeAction) Success() string {
return fmt.Sprintf("Successfully created logical volume %s that consumes %d%% free space of volume group %s", a.name, a.freeSpacePercent, a.volumeGroup)
return fmt.Sprintf("Successfully created logical volume %s that consumes %d%% of the space of volume group %s", a.name, a.volumeGroupPercent, a.volumeGroup)
}

type ActivateLogicalVolumeAction struct {
Expand Down Expand Up @@ -209,3 +209,46 @@ func (a *ResizePhysicalVolumeAction) Refuse() string {
func (a *ResizePhysicalVolumeAction) Success() string {
return fmt.Sprintf("Successfully resized physical volume %s", a.name)
}

type ResizeLogicalVolumeAction struct {
name string
volumeGroupPercent int
volumeGroup string
mode model.Mode
lvmService service.LvmService
}

func NewResizeLogicalVolumeAction(name string, volumeGroupPercent int, volumeGroup string, ls service.LvmService) *ResizeLogicalVolumeAction {
return &ResizeLogicalVolumeAction{
name: name,
volumeGroupPercent: volumeGroupPercent,
volumeGroup: volumeGroup,
mode: model.Empty,
lvmService: ls,
}
}

func (a *ResizeLogicalVolumeAction) Execute() error {
return a.lvmService.ResizeLogicalVolume(a.name, a.volumeGroup, a.volumeGroupPercent)
}

func (a *ResizeLogicalVolumeAction) GetMode() model.Mode {
return a.mode
}

func (a *ResizeLogicalVolumeAction) SetMode(mode model.Mode) Action {
a.mode = mode
return a
}

func (a *ResizeLogicalVolumeAction) Prompt() string {
return fmt.Sprintf("Would you like to resize logical volume %s to consume %d%% of the space of volume group %s", a.name, a.volumeGroupPercent, a.volumeGroup)
}

func (a *ResizeLogicalVolumeAction) Refuse() string {
return fmt.Sprintf("Refused to resize logical volume %s to consume %d%% of the space of volume group %s", a.name, a.volumeGroupPercent, a.volumeGroup)
}

func (a *ResizeLogicalVolumeAction) Success() string {
return fmt.Sprintf("Successfully resized logical volume %s to consume %d%% of the space of volume group %s", a.name, a.volumeGroupPercent, a.volumeGroup)
}
3 changes: 1 addition & 2 deletions internal/backend/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,9 @@ func (lfb *LinuxFileBackend) IsMount(p string) bool {
}

func (lfb *LinuxFileBackend) From(config *config.Config) error {
// Clear representation of files
lfb.files = nil

files := map[string]*model.File{}

for _, cd := range config.Devices {
if len(cd.MountPoint) == 0 {
continue
Expand Down
86 changes: 65 additions & 21 deletions internal/backend/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ import (
type LvmBackend interface {
CreatePhysicalVolume(name string) action.Action
CreateVolumeGroup(name string, physicalVolume string) action.Action
CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent int) action.Action
CreateLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) action.Action
ActivateLogicalVolume(name string, volumeGroup string) action.Action
GetVolumeGroups(name string) []*model.VolumeGroup
GetLogicalVolume(name string, volumeGroup string) (*model.LogicalVolume, error)
SearchLogicalVolumes(volumeGroup string) []*model.LogicalVolume
SearchVolumeGroup(physicalVolume string) *model.VolumeGroup
ShouldResizePhysicalVolume(name string, threshold float64) bool
SearchLogicalVolumes(volumeGroup string) ([]*model.LogicalVolume, error)
SearchVolumeGroup(physicalVolume string) (*model.VolumeGroup, error)
ShouldResizePhysicalVolume(name string, threshold float64) (bool, error)
ResizePhysicalVolume(name string) action.Action
ShouldResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int, tolerance float64) (bool, error)
ResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) action.Action
From(config *config.Config) error
}

Expand Down Expand Up @@ -68,11 +70,11 @@ func (lb *LinuxLvmBackend) GetLogicalVolume(name string, volumeGroup string) (*m
}, nil
}

func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) []*model.LogicalVolume {
func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) ([]*model.LogicalVolume, error) {
lvs := []*model.LogicalVolume{}
node, err := lb.lvmGraph.GetVolumeGroup(volumeGroup)
if err != nil {
return lvs
return nil, err
}
lvn := lb.lvmGraph.GetChildren(node, datastructures.LogicalVolume)
for _, lv := range lvn {
Expand All @@ -83,23 +85,23 @@ func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) []*model.Log
Size: lv.Size,
})
}
return lvs
return lvs, nil
}

func (lb *LinuxLvmBackend) SearchVolumeGroup(physicalVolume string) *model.VolumeGroup {
func (lb *LinuxLvmBackend) SearchVolumeGroup(physicalVolume string) (*model.VolumeGroup, error) {
node, err := lb.lvmGraph.GetPhysicalVolume(physicalVolume)
if err != nil {
return nil
return nil, err
}
vgn := lb.lvmGraph.GetChildren(node, datastructures.VolumeGroup)
if len(vgn) == 0 {
return nil
return nil, fmt.Errorf("🔴 %s: Physical volume has no volume group", physicalVolume)
}
return &model.VolumeGroup{
Name: vgn[0].Name,
PhysicalVolume: node.Name,
Size: vgn[0].Size,
}
}, nil
}

func (lb *LinuxLvmBackend) CreatePhysicalVolume(name string) action.Action {
Expand All @@ -110,58 +112,100 @@ func (lb *LinuxLvmBackend) CreateVolumeGroup(name string, physicalVolume string)
return action.NewCreateVolumeGroupAction(name, physicalVolume, lb.lvmService)
}

func (lb *LinuxLvmBackend) CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent int) action.Action {
return action.NewCreateLogicalVolumeAction(name, freeSpacePercent, volumeGroup, lb.lvmService)
func (lb *LinuxLvmBackend) CreateLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) action.Action {
return action.NewCreateLogicalVolumeAction(name, volumeGroupPercent, volumeGroup, lb.lvmService)
}

func (lb *LinuxLvmBackend) ActivateLogicalVolume(name string, volumeGroup string) action.Action {
return action.NewActivateLogicalVolumeAction(name, volumeGroup, lb.lvmService)
}

func (lb *LinuxLvmBackend) ShouldResizePhysicalVolume(name string, threshold float64) bool {
func (lb *LinuxLvmBackend) ShouldResizePhysicalVolume(name string, threshold float64) (bool, error) {
node, err := lb.lvmGraph.GetPhysicalVolume(name)
if err != nil {
return false
return false, nil
}
dvn := lb.lvmGraph.GetParents(node, datastructures.Device)
if len(dvn) == 0 {
return false
return false, nil
}
return (float64(node.Size) / float64(dvn[0].Size) * 100) < threshold
return (float64(node.Size) / float64(dvn[0].Size) * 100) < threshold, 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
node, err := lb.lvmGraph.GetLogicalVolume(name, volumeGroup)
if err != nil {
return false, err
}
vgn := lb.lvmGraph.GetParents(node, datastructures.VolumeGroup)
if len(vgn) == 0 {
return false, fmt.Errorf("🔴 %s: Logical volume has no volume group", name)
}
usedPerecent := (float64(node.Size) / float64(vgn[0].Size)) * 100
if usedPerecent > right {
return false, fmt.Errorf("🔴 %s: Logical volume %s is using %.0f%% of volume group %s, which exceeds the expected usage of %d%%", volumeGroup, name, usedPerecent, volumeGroup, volumeGroupPercent)
}
return usedPerecent < left, nil
}

func (lb *LinuxLvmBackend) ResizeLogicalVolume(name string, volumeGroup string, volumeGroupPercent int) action.Action {
return action.NewResizeLogicalVolumeAction(name, volumeGroupPercent, volumeGroup, lb.lvmService)
}

func (db *LinuxLvmBackend) From(config *config.Config) error {
// We populate a temporary lvmGraph and then assign it to the backend
// after all objects have been successfully added. This avoids a partial
// state in the event of failure during one of intermediate steps.
db.lvmGraph = nil
lvmGraph := datastructures.NewLvmGraph()

ds, err := db.lvmService.GetDevices()
if err != nil {
return err
}
for _, d := range ds {
db.lvmGraph.AddDevice(d.Name, d.Size)
err := lvmGraph.AddDevice(d.Name, d.Size)
if err != nil {
return err
}
}
pvs, err := db.lvmService.GetPhysicalVolumes()
if err != nil {
return err
}
for _, pv := range pvs {
db.lvmGraph.AddPhysicalVolume(pv.Name, pv.Size)
err := lvmGraph.AddPhysicalVolume(pv.Name, pv.Size)
if err != nil {
return err
}
}
vgs, err := db.lvmService.GetVolumeGroups()
if err != nil {
return err
}
for _, vg := range vgs {
db.lvmGraph.AddVolumeGroup(vg.Name, vg.PhysicalVolume, vg.Size)
err := lvmGraph.AddVolumeGroup(vg.Name, vg.PhysicalVolume, vg.Size)
if err != nil {
return err
}
}
lvs, err := db.lvmService.GetLogicalVolumes()
if err != nil {
return err
}
for _, lv := range lvs {
db.lvmGraph.AddLogicalVolume(lv.Name, lv.VolumeGroup, datastructures.LvmNodeState(lv.State), lv.Size)
err := lvmGraph.AddLogicalVolume(lv.Name, lv.VolumeGroup, datastructures.LvmNodeState(lv.State), lv.Size)
if err != nil {
return err
}
}

db.lvmGraph = lvmGraph
return nil
}
3 changes: 1 addition & 2 deletions internal/backend/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ func (dmb *LinuxDeviceMetricsBackend) GetBlockDeviceMetrics(name string) (*model
}

func (dmb *LinuxDeviceMetricsBackend) From(config *config.Config) error {
// Clear representation of metrics
dmb.blockDeviceMetrics = nil

blockDeviceMetrics := map[string]*model.BlockDeviceMetrics{}

for name := range config.Devices {
bd, err := dmb.deviceService.GetBlockDevice(name)
if err != nil {
Expand Down
3 changes: 1 addition & 2 deletions internal/backend/owner.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,11 @@ func (lfb *LinuxOwnerBackend) GetGroup(group string) (*model.Group, error) {
}

func (lfb *LinuxOwnerBackend) From(config *config.Config) error {
// Clear representation of users and groups
lfb.users = nil
lfb.groups = nil

users := map[string]*model.User{}
groups := map[string]*model.Group{}

for _, cd := range config.Devices {
if len(cd.User) > 0 {
o, err := lfb.ownerService.GetUser(cd.User)
Expand Down
4 changes: 4 additions & 0 deletions internal/datastructures/lvm_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,7 @@ func (lg *LvmGraph) GetChildren(node *LvmNode, state LvmNodeCategory) []*LvmNode
}
return children
}

func (lg *LvmGraph) Clear() {
lg.nodes = map[string]*LvmNode{}
}
10 changes: 8 additions & 2 deletions internal/layer/lv.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ func (cvgl *CreateLogicalVolumeLayer) Modify(c *config.Config) ([]action.Action,
continue
}

lvs := cvgl.lvmBackend.SearchLogicalVolumes(cd.Lvm)
lvs, err := cvgl.lvmBackend.SearchLogicalVolumes(cd.Lvm)
if err != nil {
return nil, err
}
if len(lvs) == 1 {
if lvs[0].Name == cd.Lvm {
continue
Expand All @@ -48,7 +51,10 @@ func (cvgl *CreateLogicalVolumeLayer) Validate(c *config.Config) error {
if len(cd.Lvm) == 0 {
continue
}
lvs := cvgl.lvmBackend.SearchLogicalVolumes(cd.Lvm)
lvs, err := cvgl.lvmBackend.SearchLogicalVolumes(cd.Lvm)
if err != nil {
return err
}
if len(lvs) == 1 {
if lvs[0].Name == cd.Lvm {
continue
Expand Down
Loading

0 comments on commit 24592ed

Please sign in to comment.