Skip to content

Commit

Permalink
(feat): Add Volume Group management support
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed May 18, 2024
1 parent d00f32d commit af4325a
Show file tree
Hide file tree
Showing 10 changed files with 434 additions and 19 deletions.
13 changes: 13 additions & 0 deletions LVM.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,17 @@ $ sudo pvs -o pv_name,pv_size,dev_size --units B --nosuffix
PV PSize DevSize
/dev/vda3 31079792640B 31082938368B
/dev/vdb 10733223936B 10737418240B
```

# LVM Backend

# Volume Group Names are unique system-wide
# Logical Group Names are unique within the scope of the Volume Group
```
# Graph Based Data Structure
"device:xvdb" -> "pv:xvdb"
"pv:xvdb" -> "vg:ifmx-etc"
"pv:xvdc" -> "vg:ifmx-etc"
"vg:ifmx-etc" -> "lv:ifmx-etc:pv:ifmx-etc"
"vg:ifmx-etc" -> "lv:ifmx-etc-2:pv:ifmx-etc"
```
2 changes: 2 additions & 0 deletions cmd/ebs-bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func main() {
// LVM Layers
lvmLayers := []layer.Layer{
layer.NewCreatePhysicalVolumeLayer(db, lb),
layer.NewCreateVolumeGroupLayer(lb),
}
checkError(le.Execute(lvmLayers))

Expand Down Expand Up @@ -89,6 +90,7 @@ func main() {
layer.NewChangePermissionsLayer(fb),
}
checkError(le.Execute(layers))
log.Println("🟢 Passed all validation checks")
}

func checkError(err error) {
Expand Down
41 changes: 41 additions & 0 deletions internal/action/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,44 @@ func (a *CreatePhysicalVolumeAction) Refuse() string {
func (a *CreatePhysicalVolumeAction) Success() string {
return fmt.Sprintf("Successfully created physical volume %s", a.name)
}

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

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

func (a *CreateVolumeGroupAction) Execute() error {
return a.lvmService.CreateVolumeGroup(a.name, a.physicalVolume)
}

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

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

func (a *CreateVolumeGroupAction) Prompt() string {
return fmt.Sprintf("Would you like to create volume group %s on physical volume %s", a.name, a.physicalVolume)
}

func (a *CreateVolumeGroupAction) Refuse() string {
return fmt.Sprintf("Refused to create volume group %s on physical volume %s", a.name, a.physicalVolume)
}

func (a *CreateVolumeGroupAction) Success() string {
return fmt.Sprintf("Successfully created volume group %s on physical volume %s", a.name, a.physicalVolume)
}
53 changes: 52 additions & 1 deletion internal/backend/lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,79 @@ package backend
import (
"github.com/reecetech/ebs-bootstrap/internal/action"
"github.com/reecetech/ebs-bootstrap/internal/config"
datastructures "github.com/reecetech/ebs-bootstrap/internal/data_structures"
"github.com/reecetech/ebs-bootstrap/internal/model"
"github.com/reecetech/ebs-bootstrap/internal/service"
)

type LvmBackend interface {
CreatePhysicalVolume(name string) action.Action
CreateVolumeGroup(name string, physicalVolume string) action.Action
GetVolumeGroup(name string) (*model.VolumeGroup, error)
GetPhysicalVolumes(vg *model.VolumeGroup) ([]*model.PhysicalVolume, error)
From(config *config.Config) error
}

type LinuxLvmBackend struct {
lvmGraph *datastructures.LvmGraph
lvmService service.LvmService
}

func NewLinuxLvmBackend(ls service.LvmService) *LinuxLvmBackend {
return &LinuxLvmBackend{
lvmGraph: datastructures.NewLvmGraph(),
lvmService: ls,
}
}

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

func (lb *LinuxLvmBackend) GetPhysicalVolumes(vg *model.VolumeGroup) ([]*model.PhysicalVolume, error) {
vgn, err := lb.lvmGraph.GetVolumeGroup(vg.Name)
if err != nil {
return nil, err
}
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,
}
}
return physicalVolumes, nil
}

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

// TODO: Establish Graph Network
func (lb *LinuxLvmBackend) CreateVolumeGroup(name string, physicalVolume string) action.Action {
return action.NewCreateVolumeGroupAction(name, physicalVolume, lb.lvmService)
}

func (db *LinuxLvmBackend) From(config *config.Config) error {
pvs, err := db.lvmService.GetPhysicalVolumes()
if err != nil {
return err
}
for _, pv := range pvs {
db.lvmGraph.AddBlockDevice(pv.Name)
db.lvmGraph.AddPhysicalVolume(pv.Name)
}
vgs, err := db.lvmService.GetVolumeGroups()
if err != nil {
return err
}
for _, vg := range vgs {
db.lvmGraph.AddVolumeGroup(vg.Name, vg.PhysicalVolume)
}
return nil
}
190 changes: 190 additions & 0 deletions internal/data_structures/lvm_graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package datastructures

import (
"fmt"
)

type LvmNodeType int

const (
BlockDevice LvmNodeType = 0
PhysicalVolume LvmNodeType = 1
VolumeGroup LvmNodeType = 2
LogicalVolume LvmNodeType = 3
)

type LvmNode struct {
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,
nodeType: BlockDevice,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

func NewPhysicalVolume(name string) *LvmNode {
return &LvmNode{
id: fmt.Sprintf("pv:%s", name),
Name: name,
Active: true,
nodeType: PhysicalVolume,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

func NewVolumeGroup(name string) *LvmNode {
return &LvmNode{
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 {
return &LvmNode{
id: fmt.Sprintf("lv:%s:vg:%s", name, vg),
Name: name,
Active: active,
nodeType: LogicalVolume,
children: []*LvmNode{},
parents: []*LvmNode{},
}
}

type LvmGraph struct {
nodes map[string]*LvmNode // A map that stores all the nodes by their Id
}

func NewLvmGraph() *LvmGraph {
return &LvmGraph{
nodes: map[string]*LvmNode{},
}
}

func (lg *LvmGraph) AddBlockDevice(name string) error {
bd := NewBlockDevice(name)

_, found := lg.nodes[bd.id]
if found {
return fmt.Errorf("block device %s already exists", name)
}

lg.nodes[bd.id] = bd
return nil
}

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

_, found := lg.nodes[pv.id]
if found {
return fmt.Errorf("physical volume %s already exists", name)
}

bdId := fmt.Sprintf("device:%s", name)
bdn, found := lg.nodes[bdId]
if !found {
return fmt.Errorf("block device %s does not exist", name)
}

lg.nodes[pv.id] = pv
bdn.children = append(bdn.children, pv)
pv.parents = append(pv.parents, bdn)
return nil
}

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

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

pvId := fmt.Sprintf("pv:%s", pv)
pvn, found := lg.nodes[pvId]
if !found {
return fmt.Errorf("physical volume %s does not exist", pv)
}

if len(pvn.children) > 0 {
return fmt.Errorf("%s is already assigned to volume group %s", pv, pvn.children[0].Name)
}

lg.nodes[vg.id] = vg
pvn.children = append(pvn.children, vg)
vg.parents = append(vg.parents, pvn)
return nil
}

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

_, found := lg.nodes[lv.id]
if found {
return fmt.Errorf("logical volume %s already exists", name)
}

vgId := fmt.Sprintf("vg:%s", vg)
vgn, found := lg.nodes[vgId]
if !found {
return fmt.Errorf("volume group %s does not exist", vg)
}

lg.nodes[lv.id] = lv
vgn.children = append(vgn.children, lv)
lv.parents = append(lv.parents, vgn)

// 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
}
}
return nil
}

func (lg *LvmGraph) GetVolumeGroup(name string) (*LvmNode, error) {
id := fmt.Sprintf("vg:%s", name)
node, found := lg.nodes[id]
if !found {
return nil, fmt.Errorf("volume group %s does not exist", name)
}
return node, nil
}

func (lg *LvmGraph) GetLogicalVolume(name string, vg string) (*LvmNode, error) {
id := fmt.Sprintf("lv:%s:vg:%s", name, vg)
node, found := lg.nodes[id]
if !found {
return nil, fmt.Errorf("logical volume %s/%s does not exist", vg, name)
}
return node, nil
}

func (lg *LvmGraph) GetParents(node *LvmNode, nodeType LvmNodeType) []*LvmNode {
parents := []*LvmNode{}
for _, p := range node.parents {
if p.nodeType == nodeType {
parents = append(parents, p)
}
}
return parents
}
1 change: 0 additions & 1 deletion internal/layer/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ func (le *ExponentialBackoffLayerExecutor) Execute(layers []Layer) error {
return err
}
}
log.Println("🟢 Passed all validation checks")
return nil
}

Expand Down
Loading

0 comments on commit af4325a

Please sign in to comment.