Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat): Resize Physical Volume #12

Merged
merged 2 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/ebs-bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func main() {
// LVM Layers
lvmLayers := []layer.Layer{
layer.NewCreatePhysicalVolumeLayer(db, lb),
layer.NewResizePhysicalVolumeLayer(lb),
layer.NewCreateVolumeGroupLayer(lb),
layer.NewCreateLogicalVolumeLayer(lb),
layer.NewActivateLogicalVolumeLayer(lb),
Expand Down
2 changes: 2 additions & 0 deletions configs/ubuntu.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defaults:
lvmConsumption: 100
resizeFs: true
resizeThreshold: 99
devices:
/dev/vdb:
fs: xfs
Expand Down
39 changes: 39 additions & 0 deletions internal/action/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,42 @@ func (a *ActivateLogicalVolumeAction) Refuse() string {
func (a *ActivateLogicalVolumeAction) Success() string {
return fmt.Sprintf("Successfully activated logical volume %s in volume group %s", a.name, a.volumeGroup)
}

type ResizePhysicalVolumeAction struct {
name string
mode model.Mode
lvmService service.LvmService
}

func NewResizePhysicalVolumeAction(name string, ls service.LvmService) *ResizePhysicalVolumeAction {
return &ResizePhysicalVolumeAction{
name: name,
mode: model.Empty,
lvmService: ls,
}
}

func (a *ResizePhysicalVolumeAction) Execute() error {
return a.lvmService.ResizePhysicalVolume(a.name)
}

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

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

func (a *ResizePhysicalVolumeAction) Prompt() string {
return fmt.Sprintf("Would you like to resize physical volume %s", a.name)
}

func (a *ResizePhysicalVolumeAction) Refuse() string {
return fmt.Sprintf("Refused to resize physical volume %s", a.name)
}

func (a *ResizePhysicalVolumeAction) Success() string {
return fmt.Sprintf("Successfully resized physical volume %s", a.name)
}
34 changes: 30 additions & 4 deletions internal/backend/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type LvmBackend interface {
GetLogicalVolume(name string, volumeGroup string) (*model.LogicalVolume, error)
SearchLogicalVolumes(volumeGroup string) []*model.LogicalVolume
SearchVolumeGroup(physicalVolume string) *model.VolumeGroup
ShouldResizePhysicalVolume(name string, threshold float64) bool
ResizePhysicalVolume(name string) action.Action
From(config *config.Config) error
}

Expand Down Expand Up @@ -78,6 +80,7 @@ func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) []*model.Log
Name: lv.Name,
VolumeGroup: node.Name,
State: model.LogicalVolumeState(lv.State),
Size: lv.Size,
})
}
return lvs
Expand All @@ -95,6 +98,7 @@ func (lb *LinuxLvmBackend) SearchVolumeGroup(physicalVolume string) *model.Volum
return &model.VolumeGroup{
Name: vgn[0].Name,
PhysicalVolume: node.Name,
Size: vgn[0].Size,
}
}

Expand All @@ -114,28 +118,50 @@ func (lb *LinuxLvmBackend) ActivateLogicalVolume(name string, volumeGroup string
return action.NewActivateLogicalVolumeAction(name, volumeGroup, lb.lvmService)
}

func (lb *LinuxLvmBackend) ShouldResizePhysicalVolume(name string, threshold float64) bool {
node, err := lb.lvmGraph.GetPhysicalVolume(name)
if err != nil {
return false
}
dvn := lb.lvmGraph.GetParents(node, datastructures.Device)
if len(dvn) == 0 {
return false
}
return (float64(node.Size) / float64(dvn[0].Size) * 100) < threshold
}

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

func (db *LinuxLvmBackend) From(config *config.Config) error {
ds, err := db.lvmService.GetDevices()
if err != nil {
return err
}
for _, d := range ds {
db.lvmGraph.AddDevice(d.Name, d.Size)
}
pvs, err := db.lvmService.GetPhysicalVolumes()
if err != nil {
return err
}
for _, pv := range pvs {
db.lvmGraph.AddBlockDevice(pv.Name)
db.lvmGraph.AddPhysicalVolume(pv.Name)
db.lvmGraph.AddPhysicalVolume(pv.Name, pv.Size)
}
vgs, err := db.lvmService.GetVolumeGroups()
if err != nil {
return err
}
for _, vg := range vgs {
db.lvmGraph.AddVolumeGroup(vg.Name, vg.PhysicalVolume)
db.lvmGraph.AddVolumeGroup(vg.Name, vg.PhysicalVolume, vg.Size)
}
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))
db.lvmGraph.AddLogicalVolume(lv.Name, lv.VolumeGroup, datastructures.LvmNodeState(lv.State), lv.Size)
}
return nil
}
35 changes: 20 additions & 15 deletions internal/datastructures/lvm_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type LvmNodeState int32
type LvmNodeCategory int32

const (
BlockDeviceActive LvmNodeState = 0b0000001
DeviceActive LvmNodeState = 0b0000001
PhysicalVolumeActive LvmNodeState = 0b0000010
VolumeGroupInactive LvmNodeState = 0b0000100
VolumeGroupActive LvmNodeState = 0b0001100
Expand All @@ -18,7 +18,7 @@ const (
)

const (
BlockDevice LvmNodeCategory = 0b0000001
Device LvmNodeCategory = 0b0000001
PhysicalVolume LvmNodeCategory = 0b0000010
VolumeGroup LvmNodeCategory = 0b0000100
LogicalVolume LvmNodeCategory = 0b0010000
Expand All @@ -28,45 +28,50 @@ type LvmNode struct {
id string
Name string
State LvmNodeState
Size uint64
children []*LvmNode
parents []*LvmNode
}

func NewBlockDevice(name string) *LvmNode {
func NewDevice(name string, size uint64) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("device:%s", name),
Name: name,
State: BlockDeviceActive,
State: DeviceActive,
Size: size,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

func NewPhysicalVolume(name string) *LvmNode {
func NewPhysicalVolume(name string, size uint64) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("pv:%s", name),
Name: name,
State: PhysicalVolumeActive,
Size: size,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

func NewVolumeGroup(name string) *LvmNode {
func NewVolumeGroup(name string, size uint64) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("vg:%s", name),
Name: name,
State: VolumeGroupInactive,
Size: size,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

func NewLogicalVolume(name string, vg string, State LvmNodeState) *LvmNode {
func NewLogicalVolume(name string, vg string, State LvmNodeState, size uint64) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("lv:%s:vg:%s", name, vg),
Name: name,
State: State,
Size: size,
children: []*LvmNode{},
parents: []*LvmNode{},
}
Expand All @@ -82,8 +87,8 @@ func NewLvmGraph() *LvmGraph {
}
}

func (lg *LvmGraph) AddBlockDevice(name string) error {
bd := NewBlockDevice(name)
func (lg *LvmGraph) AddDevice(name string, size uint64) error {
bd := NewDevice(name, size)

_, found := lg.nodes[bd.id]
if found {
Expand All @@ -94,8 +99,8 @@ func (lg *LvmGraph) AddBlockDevice(name string) error {
return nil
}

func (lg *LvmGraph) AddPhysicalVolume(name string) error {
pv := NewPhysicalVolume(name)
func (lg *LvmGraph) AddPhysicalVolume(name string, size uint64) error {
pv := NewPhysicalVolume(name, size)

_, found := lg.nodes[pv.id]
if found {
Expand All @@ -114,12 +119,12 @@ func (lg *LvmGraph) AddPhysicalVolume(name string) error {
return nil
}

func (lg *LvmGraph) AddVolumeGroup(name string, pv string) error {
func (lg *LvmGraph) AddVolumeGroup(name string, pv string, size uint64) error {
id := fmt.Sprintf("vg:%s", name)

vg, found := lg.nodes[id]
if !found {
vg = NewVolumeGroup(name)
vg = NewVolumeGroup(name, size)
}

pvId := fmt.Sprintf("pv:%s", pv)
Expand All @@ -138,8 +143,8 @@ func (lg *LvmGraph) AddVolumeGroup(name string, pv string) error {
return nil
}

func (lg *LvmGraph) AddLogicalVolume(name string, vg string, state LvmNodeState) error {
lv := NewLogicalVolume(name, vg, state)
func (lg *LvmGraph) AddLogicalVolume(name string, vg string, state LvmNodeState, size uint64) error {
lv := NewLogicalVolume(name, vg, state, size)

_, found := lg.nodes[lv.id]
if found {
Expand Down
90 changes: 90 additions & 0 deletions internal/layer/pv_resize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package layer

import (
"fmt"

"github.com/reecetech/ebs-bootstrap/internal/action"
"github.com/reecetech/ebs-bootstrap/internal/backend"
"github.com/reecetech/ebs-bootstrap/internal/config"
)

const (
// 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
ResizeThreshold = float64(99.6)
)

type ResizePhysicalVolumeLayer struct {
lvmBackend backend.LvmBackend
}

func NewResizePhysicalVolumeLayer(lb backend.LvmBackend) *ResizePhysicalVolumeLayer {
return &ResizePhysicalVolumeLayer{
lvmBackend: lb,
}
}

func (rpvl *ResizePhysicalVolumeLayer) Modify(c *config.Config) ([]action.Action, error) {
actions := make([]action.Action, 0)
for name, cd := range c.Devices {
if len(cd.Lvm) == 0 {
continue
}
if !c.GetResizeFs(name) {
continue
}
if !rpvl.lvmBackend.ShouldResizePhysicalVolume(name, ResizeThreshold) {
continue
}
mode := c.GetMode(name)
a := rpvl.lvmBackend.ResizePhysicalVolume(name)
actions = append(actions, a.SetMode(mode))
}
return actions, nil
}

func (rpvl *ResizePhysicalVolumeLayer) Validate(c *config.Config) error {
for name, cd := range c.Devices {
if len(cd.Lvm) == 0 {
continue
}
if !c.GetResizeFs(name) {
continue
}
if rpvl.lvmBackend.ShouldResizePhysicalVolume(name, ResizeThreshold) {
return fmt.Errorf("🔴 %s: Failed to resize validation checks. Physical volume %s still needs to be resized", name, name)
}
}
return nil
}

func (rpvl *ResizePhysicalVolumeLayer) Warning() string {
return DisabledWarning
}

func (rpvl *ResizePhysicalVolumeLayer) From(c *config.Config) error {
return rpvl.lvmBackend.From(c)
}

func (rpvl *ResizePhysicalVolumeLayer) ShouldProcess(c *config.Config) bool {
for name, cd := range c.Devices {
if len(cd.Lvm) > 0 && c.GetResizeFs(name) {
return true
}
}
return false
}
8 changes: 8 additions & 0 deletions internal/model/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ package model

import "github.com/reecetech/ebs-bootstrap/internal/datastructures"

type Device struct {
Name string
Size uint64
}

type PhysicalVolume struct {
Name string
Size uint64
}

type VolumeGroup struct {
Name string
PhysicalVolume string
Size uint64
}

type LogicalVolumeState int32
Expand All @@ -23,4 +30,5 @@ type LogicalVolume struct {
Name string
VolumeGroup string
State LogicalVolumeState
Size uint64
}
Loading
Loading