Skip to content

Commit

Permalink
(feat): Add Logical Volume support
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed May 19, 2024
1 parent adca477 commit ab9e553
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 94 deletions.
1 change: 1 addition & 0 deletions cmd/ebs-bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func main() {
lvmLayers := []layer.Layer{
layer.NewCreatePhysicalVolumeLayer(db, lb),
layer.NewCreateVolumeGroupLayer(lb),
layer.NewCreateLogicalVolumeLayer(lb),
}
checkError(le.Execute(lvmLayers))

Expand Down
5 changes: 5 additions & 0 deletions configs/ubuntu.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defaults:
lvmConsumption: 80
devices:
/dev/vdb:
fs: xfs
lvm: ifmx-etc
/dev/vdc:
fs: xfs
lvm: ifmx-var
43 changes: 43 additions & 0 deletions internal/action/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,46 @@ func (a *CreateVolumeGroupAction) Refuse() string {
func (a *CreateVolumeGroupAction) Success() string {
return fmt.Sprintf("Successfully created volume group %s on physical volume %s", a.name, a.physicalVolume)
}

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

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

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

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

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

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)
}

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)
}

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)
}
86 changes: 46 additions & 40 deletions internal/backend/lvm.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package backend

import (
"fmt"

"github.com/reecetech/ebs-bootstrap/internal/action"
"github.com/reecetech/ebs-bootstrap/internal/config"
datastructures "github.com/reecetech/ebs-bootstrap/internal/data_structures"
Expand All @@ -13,10 +11,10 @@ import (
type LvmBackend interface {
CreatePhysicalVolume(name string) action.Action
CreateVolumeGroup(name string, physicalVolume string) action.Action
GetVolumeGroup(name string) (*model.VolumeGroup, error)
SearchVolumeGroup(pv *model.PhysicalVolume) (*model.VolumeGroup, error)
GetPhysicalVolume(name string) (*model.PhysicalVolume, error)
SearchPhysicalVolumes(vg *model.VolumeGroup) ([]*model.PhysicalVolume, error)
CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent int) action.Action
GetVolumeGroups(name string) []*model.VolumeGroup
SearchLogicalVolumes(volumeGroup string) []*model.LogicalVolume
SearchVolumeGroup(physicalVolume string) *model.VolumeGroup
From(config *config.Config) error
}

Expand All @@ -32,54 +30,51 @@ func NewLinuxLvmBackend(ls service.LvmService) *LinuxLvmBackend {
}
}

func (lb *LinuxLvmBackend) GetVolumeGroup(name string) (*model.VolumeGroup, error) {
func (lb *LinuxLvmBackend) GetVolumeGroups(name string) []*model.VolumeGroup {
vgs := []*model.VolumeGroup{}
node, err := lb.lvmGraph.GetVolumeGroup(name)
if err != nil {
return nil, err
return vgs
}
return &model.VolumeGroup{
Name: node.Name,
}, nil
}

func (lb *LinuxLvmBackend) SearchVolumeGroup(pv *model.PhysicalVolume) (*model.VolumeGroup, error) {
pvn, err := lb.lvmGraph.GetPhysicalVolume(pv.Name)
if err != nil {
return nil, err
}
vg := lb.lvmGraph.GetChildren(pvn, datastructures.VolumeGroup)
if len(vg) == 0 {
return nil, nil
pvn := lb.lvmGraph.GetParents(node, datastructures.PhysicalVolume)
for _, pv := range pvn {
vgs = append(vgs, &model.VolumeGroup{
Name: node.Name,
PhysicalVolume: pv.Name,
})
}
return &model.VolumeGroup{Name: vg[0].Name}, nil
return vgs
}

func (lb *LinuxLvmBackend) GetPhysicalVolume(name string) (*model.PhysicalVolume, error) {
node, err := lb.lvmGraph.GetPhysicalVolume(name)
func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) []*model.LogicalVolume {
lvs := []*model.LogicalVolume{}
node, err := lb.lvmGraph.GetVolumeGroup(volumeGroup)
if err != nil {
return nil, err
return lvs
}
lvn := lb.lvmGraph.GetChildren(node, datastructures.LogicalVolume)
for _, lv := range lvn {
lvs = append(lvs, &model.LogicalVolume{
Name: lv.Name,
VolumeGroup: node.Name,
})
}
return &model.PhysicalVolume{
Name: node.Name,
}, nil
return lvs
}

func (lb *LinuxLvmBackend) SearchPhysicalVolumes(vg *model.VolumeGroup) ([]*model.PhysicalVolume, error) {
vgn, err := lb.lvmGraph.GetVolumeGroup(vg.Name)
func (lb *LinuxLvmBackend) SearchVolumeGroup(physicalVolume string) *model.VolumeGroup {
node, err := lb.lvmGraph.GetPhysicalVolume(physicalVolume)
if err != nil {
return nil, err
return nil
}
pvs := lb.lvmGraph.GetParents(vgn, datastructures.PhysicalVolume)
physicalVolumes := make([]*model.PhysicalVolume, len(pvs))
for i, pv := range pvs {
physicalVolumes[i] = &model.PhysicalVolume{
Name: pv.Name,
}
vgn := lb.lvmGraph.GetChildren(node, datastructures.VolumeGroup)
if len(vgn) == 0 {
return nil
}
if len(physicalVolumes) == 0 {
return nil, fmt.Errorf("🔴 %s: No physical volumes found", vg.Name)
return &model.VolumeGroup{
Name: vgn[0].Name,
PhysicalVolume: node.Name,
}
return physicalVolumes, nil
}

func (lb *LinuxLvmBackend) CreatePhysicalVolume(name string) action.Action {
Expand All @@ -90,6 +85,10 @@ 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 (db *LinuxLvmBackend) From(config *config.Config) error {
pvs, err := db.lvmService.GetPhysicalVolumes()
if err != nil {
Expand All @@ -106,5 +105,12 @@ func (db *LinuxLvmBackend) From(config *config.Config) error {
for _, vg := range vgs {
db.lvmGraph.AddVolumeGroup(vg.Name, vg.PhysicalVolume)
}
lvs, err := db.lvmService.GetLogicalVolumes()
if err != nil {
return err
}
for _, lv := range lvs {
db.lvmGraph.AddLogicalVolume(lv.Name, lv.VolumeGroup)
}
return nil
}
48 changes: 24 additions & 24 deletions internal/data_structures/lvm_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ const (
)

type LvmNode struct {
id string
Name string
Active bool
id string
Name string
// Active bool
nodeType LvmNodeType
children []*LvmNode
parents []*LvmNode
}

func NewBlockDevice(name string) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("device:%s", name),
Name: name,
Active: true,
id: fmt.Sprintf("device:%s", name),
Name: name,
// Active: true,
nodeType: BlockDevice,
children: []*LvmNode{},
parents: []*LvmNode{},
Expand All @@ -35,9 +35,9 @@ func NewBlockDevice(name string) *LvmNode {

func NewPhysicalVolume(name string) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("pv:%s", name),
Name: name,
Active: true,
id: fmt.Sprintf("pv:%s", name),
Name: name,
// Active: true,
nodeType: PhysicalVolume,
children: []*LvmNode{},
parents: []*LvmNode{},
Expand All @@ -46,20 +46,20 @@ func NewPhysicalVolume(name string) *LvmNode {

func NewVolumeGroup(name string) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("vg:%s", name),
Name: name,
Active: false,
id: fmt.Sprintf("vg:%s", name),
Name: name,
// Active: false,
nodeType: VolumeGroup,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

func NewLogicalVolume(name string, vg string, active bool) *LvmNode {
func NewLogicalVolume(name string, vg string) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("lv:%s:vg:%s", name, vg),
Name: name,
Active: active,
id: fmt.Sprintf("lv:%s:vg:%s", name, vg),
Name: name,
// Active: active,
nodeType: LogicalVolume,
children: []*LvmNode{},
parents: []*LvmNode{},
Expand Down Expand Up @@ -132,8 +132,8 @@ func (lg *LvmGraph) AddVolumeGroup(name string, pv string) error {
return nil
}

func (lg *LvmGraph) AddLogicalVolume(name string, vg string, active bool) error {
lv := NewLogicalVolume(name, vg, active)
func (lg *LvmGraph) AddLogicalVolume(name string, vg string) error {
lv := NewLogicalVolume(name, vg)

_, found := lg.nodes[lv.id]
if found {
Expand All @@ -152,12 +152,12 @@ func (lg *LvmGraph) AddLogicalVolume(name string, vg string, active bool) error

// If at least one of the logical volumes are active, the
// volume group is considered active
for _, lvn := range vgn.children {
if lvn.Active {
vgn.Active = true
break
}
}
// for _, lvn := range vgn.children {
// if lvn.Active {
// vgn.Active = true
// break
// }
// }
return nil
}

Expand Down
71 changes: 71 additions & 0 deletions internal/layer/lv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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"
)

type CreateLogicalVolumeLayer struct {
lvmBackend backend.LvmBackend
}

func NewCreateLogicalVolumeLayer(lb backend.LvmBackend) *CreateLogicalVolumeLayer {
return &CreateLogicalVolumeLayer{
lvmBackend: lb,
}
}

func (cvgl *CreateLogicalVolumeLayer) Modify(c *config.Config) ([]action.Action, error) {
actions := make([]action.Action, 0)
for name, cd := range c.Devices {
if len(cd.Lvm) == 0 {
continue
}

lvs := cvgl.lvmBackend.SearchLogicalVolumes(cd.Lvm)
if len(lvs) == 1 {
if lvs[0].Name == cd.Lvm {
continue
}
return nil, fmt.Errorf("🔴 %s: Volume group %s already has logical volume %s associated", name, cd.Lvm, lvs[0].Name)
}
if len(lvs) > 1 {
return nil, fmt.Errorf("🔴 %s: Cannot manage volume group %s with more than one logical volume associated", name, cd.Lvm)
}

mode := c.GetMode(name)
a := cvgl.lvmBackend.CreateLogicalVolume(cd.Lvm, cd.Lvm, c.GetLvmConsumption(name))
actions = append(actions, a.SetMode(mode))
}
return actions, nil
}

func (cvgl *CreateLogicalVolumeLayer) Validate(c *config.Config) error {
for name, cd := range c.Devices {
if len(cd.Lvm) == 0 {
continue
}
lvs := cvgl.lvmBackend.SearchLogicalVolumes(cd.Lvm)
if len(lvs) == 1 {
if lvs[0].Name == cd.Lvm {
continue
}
return fmt.Errorf("🔴 %s: Failed to validate logical volume. Expected=%s, Actual=%s", name, cd.Lvm, lvs[0].Name)
}
if len(lvs) > 1 {
return fmt.Errorf("🔴 %s: ailed to validate logical volume. #(Logical Volume) Expected=%d, Actual=%d", name, 1, len(lvs))
}
}
return nil
}

func (cvgl *CreateLogicalVolumeLayer) Warning() string {
return DisabledWarning
}

func (cvgl *CreateLogicalVolumeLayer) From(c *config.Config) error {
return cvgl.lvmBackend.From(c)
}
Loading

0 comments on commit ab9e553

Please sign in to comment.