From d00f32d515a3bc6d8ac1809324f20b35b0f56fad Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Sat, 18 May 2024 03:16:53 +0000 Subject: [PATCH 1/9] (feat): Add Physical Volume Creation --- LVM.md | 41 ++++++++++++++++++++ cmd/ebs-bootstrap.go | 20 +++++++++- configs/ubuntu.yml | 7 +--- go.mod | 9 ++--- go.sum | 5 ++- internal/action/lvm.go | 47 +++++++++++++++++++++++ internal/backend/lvm.go | 30 +++++++++++++++ internal/config/config.go | 26 ++++++++++++- internal/config/modifier.go | 26 +++++++++++++ internal/layer/pv.go | 70 ++++++++++++++++++++++++++++++++++ internal/model/filesystem.go | 3 +- internal/model/lvm.go | 24 ++++++++++++ internal/service/device.go | 14 +++---- internal/service/filesystem.go | 3 ++ internal/service/lvm.go | 33 ++++++++++++++++ internal/utils/exec.go | 1 + 16 files changed, 334 insertions(+), 25 deletions(-) create mode 100644 LVM.md create mode 100644 internal/action/lvm.go create mode 100644 internal/backend/lvm.go create mode 100644 internal/layer/pv.go create mode 100644 internal/model/lvm.go create mode 100644 internal/service/lvm.go diff --git a/LVM.md b/LVM.md new file mode 100644 index 0000000..d2381d1 --- /dev/null +++ b/LVM.md @@ -0,0 +1,41 @@ +``` +$ sudo blockdev --getsize64 /dev/vdb +10737418240 + +$ sudo pvdisplay /dev/vdb --units B +... + PV Size 10737418240 B / not usable 4194304 B + +$ sudo vgdisplay vgdemo --units B +... + VG Size 10733223936 B + +# 10737418240 - 10733223936 = 4194304 Bytes (4 KB) + +$ sudo lvdisplay vgdemo/lvdemo --units B +... +LV Size 10733223936 B +``` + +``` +LV -> VG +VG -> PV +PV -> Block Device +``` + +``` +$ sudo lvs -o lv_name,vg_name,lv_size,vg_size,lv_attr --units B --nosuffix + LV VG LSize VSize Attr + ubuntu-lv ubuntu-vg 31079792640B 31079792640B -wi-ao---- + lvdemo vgdemo 10733223936B 10733223936B -wi-a----- + +$ sudo vgs -o vg_name,pv_name --units B --nosuffix + VG PV + ubuntu-vg /dev/vda3 + vgdemo /dev/vdb + +$ sudo pvs -o pv_name,pv_size,dev_size --units B --nosuffix + PV PSize DevSize + /dev/vda3 31079792640B 31082938368B + /dev/vdb 10733223936B 10737418240B +``` \ No newline at end of file diff --git a/cmd/ebs-bootstrap.go b/cmd/ebs-bootstrap.go index c8740ff..23c1013 100644 --- a/cmd/ebs-bootstrap.go +++ b/cmd/ebs-bootstrap.go @@ -21,6 +21,7 @@ func main() { lds := service.NewLinuxDeviceService(erf) uos := service.NewUnixOwnerService() ans := service.NewAwsNitroNVMeService() + ls := service.NewLinuxLvmService(erf) fssf := service.NewLinuxFileSystemServiceFactory(erf) // Warnings @@ -35,7 +36,11 @@ func main() { fb := backend.NewLinuxFileBackend(ufs) ub := backend.NewLinuxOwnerBackend(uos) dmb := backend.NewLinuxDeviceMetricsBackend(lds, fssf) + lb := backend.NewLinuxLvmBackend(ls) + + // Executors dae := action.NewDefaultActionExecutor() + le := layer.NewExponentialBackoffLayerExecutor(c, dae, layer.DefaultExponentialBackoffParameters()) // Modify Config modifiers := []config.Modifier{ @@ -45,6 +50,20 @@ func main() { checkError(m.Modify(c)) } + // LVM Layers + lvmLayers := []layer.Layer{ + layer.NewCreatePhysicalVolumeLayer(db, lb), + } + checkError(le.Execute(lvmLayers)) + + // LVM Modifiers + lvmModifiers := []config.Modifier{ + config.NewLvmModifier(), + } + for _, m := range lvmModifiers { + checkError(m.Modify(c)) + } + // Validate Config validators := []config.Validator{ config.NewFileSystemValidator(), @@ -60,7 +79,6 @@ func main() { } // Layers - le := layer.NewExponentialBackoffLayerExecutor(c, dae, layer.DefaultExponentialBackoffParameters()) layers := []layer.Layer{ layer.NewFormatDeviceLayer(db), layer.NewLabelDeviceLayer(db), diff --git a/configs/ubuntu.yml b/configs/ubuntu.yml index 7095386..c2d32e7 100644 --- a/configs/ubuntu.yml +++ b/configs/ubuntu.yml @@ -1,9 +1,4 @@ devices: /dev/vdb: fs: xfs - label: external-vol - mountPoint: /mnt/app - group: ubuntu - user: ubuntu - permissions: 0644 - resizeFs: true + lvm: ifmx-etc diff --git a/go.mod b/go.mod index 8b7bf8a..1bec01e 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,9 @@ module github.com/reecetech/ebs-bootstrap go 1.21 -require gopkg.in/yaml.v2 v2.4.0 - require ( - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/google/go-cmp v0.6.0 + github.com/ryanuber/go-glob v1.0.0 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 868ecc5..e221fff 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,10 @@ -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/action/lvm.go b/internal/action/lvm.go new file mode 100644 index 0000000..e7a67b7 --- /dev/null +++ b/internal/action/lvm.go @@ -0,0 +1,47 @@ +package action + +import ( + "fmt" + + "github.com/reecetech/ebs-bootstrap/internal/model" + "github.com/reecetech/ebs-bootstrap/internal/service" +) + +type CreatePhysicalVolumeAction struct { + name string + mode model.Mode + lvmService service.LvmService +} + +func NewCreatePhysicalVolumeAction(name string, ls service.LvmService) *CreatePhysicalVolumeAction { + return &CreatePhysicalVolumeAction{ + name: name, + mode: model.Empty, + lvmService: ls, + } +} + +func (a *CreatePhysicalVolumeAction) Execute() error { + return a.lvmService.CreatePhysicalVolume(a.name) +} + +func (a *CreatePhysicalVolumeAction) GetMode() model.Mode { + return a.mode +} + +func (a *CreatePhysicalVolumeAction) SetMode(mode model.Mode) Action { + a.mode = mode + return a +} + +func (a *CreatePhysicalVolumeAction) Prompt() string { + return fmt.Sprintf("Would you like to create physical volume %s", a.name) +} + +func (a *CreatePhysicalVolumeAction) Refuse() string { + return fmt.Sprintf("Refused to create physical volume %s", a.name) +} + +func (a *CreatePhysicalVolumeAction) Success() string { + return fmt.Sprintf("Successfully created physical volume %s", a.name) +} diff --git a/internal/backend/lvm.go b/internal/backend/lvm.go new file mode 100644 index 0000000..6945dbf --- /dev/null +++ b/internal/backend/lvm.go @@ -0,0 +1,30 @@ +package backend + +import ( + "github.com/reecetech/ebs-bootstrap/internal/action" + "github.com/reecetech/ebs-bootstrap/internal/config" + "github.com/reecetech/ebs-bootstrap/internal/service" +) + +type LvmBackend interface { + CreatePhysicalVolume(name string) action.Action +} + +type LinuxLvmBackend struct { + lvmService service.LvmService +} + +func NewLinuxLvmBackend(ls service.LvmService) *LinuxLvmBackend { + return &LinuxLvmBackend{ + lvmService: ls, + } +} + +func (lb *LinuxLvmBackend) CreatePhysicalVolume(name string) action.Action { + return action.NewCreatePhysicalVolumeAction(name, lb.lvmService) +} + +// TODO: Establish Graph Network +func (db *LinuxLvmBackend) From(config *config.Config) error { + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go index bd61bbe..87ac001 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,8 +11,9 @@ import ( ) const ( - DefaultMode = model.Healthcheck - DefaultMountOptions = model.MountOptions("defaults") + DefaultMode = model.Healthcheck + DefaultMountOptions = model.MountOptions("defaults") + DefaultLvmConsumption = 100 ) type Flag struct { @@ -22,6 +23,7 @@ type Flag struct { MountOptions string ResizeFs bool ResizeThreshold float64 + LvmConsumption int } type Device struct { @@ -31,6 +33,7 @@ type Device struct { Group string `yaml:"group"` Label string `yaml:"label"` Permissions model.FilePermissions `yaml:"permissions"` + Lvm string `yaml:"lvm"` Options `yaml:",inline"` } @@ -40,6 +43,7 @@ type Options struct { MountOptions model.MountOptions `yaml:"mountOptions"` ResizeFs bool `yaml:"resizeFs"` ResizeThreshold float64 `yaml:"resizeThreshold"` + LvmConsumption int `yaml:"lvmConsumption"` } // We don't export "overrides" as this is an attribute that is used @@ -98,6 +102,7 @@ func parseFlags(program string, args []string) (*Flag, error) { 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.IntVar(&f.LvmConsumption, "lvm-consumption", 0, "override for lvm consumption") // Actually parse the flag err := flags.Parse(args) @@ -180,3 +185,20 @@ func (c *Config) GetResizeThreshold(name string) float64 { } return c.Defaults.ResizeThreshold } + +func (c *Config) GetLvmConsumption(name string) int { + cd, found := c.Devices[name] + if !found { + return DefaultLvmConsumption + } + if c.overrides.LvmConsumption > 0 { + return c.overrides.LvmConsumption + } + if cd.LvmConsumption > 0 { + return cd.LvmConsumption + } + if c.Defaults.LvmConsumption > 0 { + return c.Defaults.LvmConsumption + } + return DefaultLvmConsumption +} diff --git a/internal/config/modifier.go b/internal/config/modifier.go index 38db1c6..09f7fcc 100644 --- a/internal/config/modifier.go +++ b/internal/config/modifier.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "log" "strings" @@ -61,3 +62,28 @@ func (andm *AwsNitroNVMeModifier) Modify(c *Config) error { } return nil } + +type LvmModifier struct{} + +func NewLvmModifier() *LvmModifier { + return &LvmModifier{} +} + +func (lm *LvmModifier) Modify(c *Config) error { + // Fetch a copy of the original keys as we are updating + // the config in-place and it is unsafe to iterate over it + // directly + keys := make([]string, len(c.Devices)) + for name := range c.Devices { + keys = append(keys, name) + } + for _, key := range keys { + device := c.Devices[key] + if len(device.Lvm) > 0 { + ldn := fmt.Sprintf("/dev/%s/%s", device.Lvm, device.Lvm) + c.Devices[ldn] = device + delete(c.Devices, key) + } + } + return nil +} diff --git a/internal/layer/pv.go b/internal/layer/pv.go new file mode 100644 index 0000000..2b3a87b --- /dev/null +++ b/internal/layer/pv.go @@ -0,0 +1,70 @@ +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" + "github.com/reecetech/ebs-bootstrap/internal/model" +) + +type CreatePhysicalVolumeLayer struct { + deviceBackend backend.DeviceBackend + lvmBackend backend.LvmBackend +} + +func NewCreatePhysicalVolumeLayer(db backend.DeviceBackend, lb backend.LvmBackend) *CreatePhysicalVolumeLayer { + return &CreatePhysicalVolumeLayer{ + deviceBackend: db, + lvmBackend: lb, + } +} + +func (cpvl *CreatePhysicalVolumeLayer) Modify(c *config.Config) ([]action.Action, error) { + actions := make([]action.Action, 0) + for name, cd := range c.Devices { + if len(cd.Lvm) == 0 { + continue + } + bd, err := cpvl.deviceBackend.GetBlockDevice(name) + if err != nil { + return nil, err + } + if bd.FileSystem == model.Lvm { + continue + } + if bd.FileSystem != model.Unformatted { + return nil, fmt.Errorf("🔴 %s: Can not create a physical volume on a device with an existing %s file system", bd.Name, bd.FileSystem.String()) + } + mode := c.GetMode(name) + a := cpvl.lvmBackend.CreatePhysicalVolume(bd.Name) + actions = append(actions, a.SetMode(mode)) + } + return actions, nil +} + +func (cpvl *CreatePhysicalVolumeLayer) Validate(c *config.Config) error { + for name, cd := range c.Devices { + if len(cd.Lvm) == 0 { + continue + } + bd, err := cpvl.deviceBackend.GetBlockDevice(name) + if err != nil { + return err + } + if bd.FileSystem != model.Lvm { + return fmt.Errorf("🔴 %s: Failed physical volume validation checks. Expected=%s, Actual=%s", bd.Name, model.Lvm, bd.FileSystem) + } + } + return nil +} + +func (cpvl *CreatePhysicalVolumeLayer) Warning() string { + return DisabledWarning +} + +func (cpvl *CreatePhysicalVolumeLayer) From(c *config.Config) error { + // Lvmackend does not have to be initialised as we are only creating a Physical Volume + return cpvl.deviceBackend.From(c) +} diff --git a/internal/model/filesystem.go b/internal/model/filesystem.go index 059b195..10d86f5 100644 --- a/internal/model/filesystem.go +++ b/internal/model/filesystem.go @@ -8,6 +8,7 @@ const ( Unformatted FileSystem = "" Ext4 FileSystem = "ext4" Xfs FileSystem = "xfs" + Lvm FileSystem = "LVM2_member" ) func (fs FileSystem) String() string { @@ -20,7 +21,7 @@ func (fs FileSystem) String() string { func ParseFileSystem(s string) (FileSystem, error) { fst := FileSystem(s) switch fst { - case Unformatted, Ext4, Xfs: + case Unformatted, Ext4, Xfs, Lvm: return fst, nil default: return fst, fmt.Errorf("File system '%s' is not supported", fst.String()) diff --git a/internal/model/lvm.go b/internal/model/lvm.go new file mode 100644 index 0000000..4fe4243 --- /dev/null +++ b/internal/model/lvm.go @@ -0,0 +1,24 @@ +package model + +type LvmDevice struct { + Name string + Child *PhysicalVolume +} + +type PhysicalVolume struct { + Name string + Parent *LvmDevice + Child *VolumeGroup +} + +type VolumeGroup struct { + Name string + Parent []*PhysicalVolume + Children []*LogicalVolume +} + +type LogicalVolume struct { + Name string + Parent *VolumeGroup + Active bool +} diff --git a/internal/service/device.go b/internal/service/device.go index 919ec2f..962abbf 100644 --- a/internal/service/device.go +++ b/internal/service/device.go @@ -22,14 +22,12 @@ type LinuxDeviceService struct { } type LsblkBlockDeviceResponse struct { - BlockDevices []LsblkBlockDevice `json:"blockdevices"` -} - -type LsblkBlockDevice struct { - Name *string `json:"name"` - Label *string `json:"label"` - FsType *string `json:"fstype"` - MountPoint *string `json:"mountpoint"` + BlockDevices []struct { + Name *string `json:"name"` + Label *string `json:"label"` + FsType *string `json:"fstype"` + MountPoint *string `json:"mountpoint"` + } `json:"blockdevices"` } func NewLinuxDeviceService(rc utils.RunnerFactory) *LinuxDeviceService { diff --git a/internal/service/filesystem.go b/internal/service/filesystem.go index 54e107a..d68e6e0 100644 --- a/internal/service/filesystem.go +++ b/internal/service/filesystem.go @@ -40,6 +40,9 @@ func (fsf *LinuxFileSystemServiceFactory) Select(fs model.FileSystem) (FileSyste return NewExt4Service(fsf.RunnerFactory), nil case model.Xfs: return NewXfsService(fsf.RunnerFactory), nil + case model.Lvm: + //lint:ignore ST1005 This Error Message Is Supposed to Be Prepended With A Device Name + return nil, fmt.Errorf("Refer to https://github.com/reecetech/ebs-bootstrap/wiki/LVM on how to manage LVM file systems") case model.Unformatted: //lint:ignore ST1005 This Error Message Is Supposed to Be Prepended With A Device Name return nil, fmt.Errorf("An unformatted file system can not be queried/modified") diff --git a/internal/service/lvm.go b/internal/service/lvm.go new file mode 100644 index 0000000..58e82df --- /dev/null +++ b/internal/service/lvm.go @@ -0,0 +1,33 @@ +package service + +import ( + "github.com/reecetech/ebs-bootstrap/internal/utils" +) + +type LvmService interface { + CreatePhysicalVolume(name string) error +} + +type LinuxLvmService struct { + runnerFactory utils.RunnerFactory +} + +type PvsResponse struct { + Report []struct { + PhysicalVolume []struct { + Name string `json:"pv_name"` + } `json:"pv"` + } `json:"report"` +} + +func NewLinuxLvmService(rf utils.RunnerFactory) *LinuxLvmService { + return &LinuxLvmService{ + runnerFactory: rf, + } +} + +func (ls *LinuxLvmService) CreatePhysicalVolume(name string) error { + r := ls.runnerFactory.Select(utils.PvCreate) + _, err := r.Command(name) + return err +} diff --git a/internal/utils/exec.go b/internal/utils/exec.go index a1c433b..e5b3982 100644 --- a/internal/utils/exec.go +++ b/internal/utils/exec.go @@ -22,6 +22,7 @@ const ( XfsInfo Binary = "xfs_info" Resize2fs Binary = "resize2fs" XfsGrowfs Binary = "xfs_growfs" + PvCreate Binary = "pvcreate" ) type RunnerFactory interface { From adca4771298c48b902c0d27d05629f0416dba6e2 Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Sat, 18 May 2024 12:16:48 +0000 Subject: [PATCH 2/9] (feat): Add Volume Group management support --- LVM.md | 13 ++ cmd/ebs-bootstrap.go | 2 + internal/action/lvm.go | 41 +++++ internal/backend/lvm.go | 82 +++++++++- internal/data_structures/lvm_graph.go | 209 ++++++++++++++++++++++++++ internal/layer/layer.go | 1 - internal/layer/vg.go | 84 +++++++++++ internal/model/lvm.go | 20 +-- internal/service/lvm.go | 63 ++++++++ internal/utils/exec.go | 3 + 10 files changed, 499 insertions(+), 19 deletions(-) create mode 100644 internal/data_structures/lvm_graph.go create mode 100644 internal/layer/vg.go diff --git a/LVM.md b/LVM.md index d2381d1..cd1f82d 100644 --- a/LVM.md +++ b/LVM.md @@ -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" ``` \ No newline at end of file diff --git a/cmd/ebs-bootstrap.go b/cmd/ebs-bootstrap.go index 23c1013..9ec2efb 100644 --- a/cmd/ebs-bootstrap.go +++ b/cmd/ebs-bootstrap.go @@ -53,6 +53,7 @@ func main() { // LVM Layers lvmLayers := []layer.Layer{ layer.NewCreatePhysicalVolumeLayer(db, lb), + layer.NewCreateVolumeGroupLayer(lb), } checkError(le.Execute(lvmLayers)) @@ -89,6 +90,7 @@ func main() { layer.NewChangePermissionsLayer(fb), } checkError(le.Execute(layers)) + log.Println("🟢 Passed all validation checks") } func checkError(err error) { diff --git a/internal/action/lvm.go b/internal/action/lvm.go index e7a67b7..b13d500 100644 --- a/internal/action/lvm.go +++ b/internal/action/lvm.go @@ -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) +} diff --git a/internal/backend/lvm.go b/internal/backend/lvm.go index 6945dbf..b160cfd 100644 --- a/internal/backend/lvm.go +++ b/internal/backend/lvm.go @@ -1,30 +1,110 @@ 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" + "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) + SearchVolumeGroup(pv *model.PhysicalVolume) (*model.VolumeGroup, error) + GetPhysicalVolume(name string) (*model.PhysicalVolume, error) + SearchPhysicalVolumes(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) 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 + } + return &model.VolumeGroup{Name: vg[0].Name}, nil +} + +func (lb *LinuxLvmBackend) GetPhysicalVolume(name string) (*model.PhysicalVolume, error) { + node, err := lb.lvmGraph.GetPhysicalVolume(name) + if err != nil { + return nil, err + } + return &model.PhysicalVolume{ + Name: node.Name, + }, nil +} + +func (lb *LinuxLvmBackend) SearchPhysicalVolumes(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, + } + } + if len(physicalVolumes) == 0 { + return nil, fmt.Errorf("🔴 %s: No physical volumes found", vg.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 } diff --git a/internal/data_structures/lvm_graph.go b/internal/data_structures/lvm_graph.go new file mode 100644 index 0000000..e351177 --- /dev/null +++ b/internal/data_structures/lvm_graph.go @@ -0,0 +1,209 @@ +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) GetPhysicalVolume(name string) (*LvmNode, error) { + id := fmt.Sprintf("pv:%s", name) + node, found := lg.nodes[id] + if !found { + return nil, fmt.Errorf("physical volume %s does not exist", name) + } + return node, 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 +} + +func (lg *LvmGraph) GetChildren(node *LvmNode, nodeType LvmNodeType) []*LvmNode { + children := []*LvmNode{} + for _, c := range node.children { + if c.nodeType == nodeType { + children = append(children, c) + } + } + return children +} diff --git a/internal/layer/layer.go b/internal/layer/layer.go index ccff3d5..38702ba 100644 --- a/internal/layer/layer.go +++ b/internal/layer/layer.go @@ -96,7 +96,6 @@ func (le *ExponentialBackoffLayerExecutor) Execute(layers []Layer) error { return err } } - log.Println("🟢 Passed all validation checks") return nil } diff --git a/internal/layer/vg.go b/internal/layer/vg.go new file mode 100644 index 0000000..2a64a8d --- /dev/null +++ b/internal/layer/vg.go @@ -0,0 +1,84 @@ +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 CreateVolumeGroupLayer struct { + lvmBackend backend.LvmBackend +} + +func NewCreateVolumeGroupLayer(lb backend.LvmBackend) *CreateVolumeGroupLayer { + return &CreateVolumeGroupLayer{ + lvmBackend: lb, + } +} + +func (cvgl *CreateVolumeGroupLayer) Modify(c *config.Config) ([]action.Action, error) { + actions := make([]action.Action, 0) + for name, cd := range c.Devices { + if len(cd.Lvm) == 0 { + continue + } + + vg, _ := cvgl.lvmBackend.GetVolumeGroup(cd.Lvm) + + if vg != nil { + pvs, err := cvgl.lvmBackend.SearchPhysicalVolumes(vg) + if err != nil { + return nil, err + } + if len(pvs) == 1 { + if pvs[0].Name == name { + continue + } + return nil, fmt.Errorf("🔴 %s: Volume group %s belongs to the incorrect physical volume. Expected=%s, Actual=%s", name, vg.Name, name, pvs[0].Name) + } + return nil, fmt.Errorf("🔴 %s: Cannot manage a volume group %s with more than one physical volumes associated", name, vg.Name) + } + + pv, err := cvgl.lvmBackend.GetPhysicalVolume(name) + if err != nil { + return nil, err + } + + vg, err = cvgl.lvmBackend.SearchVolumeGroup(pv) + if err != nil { + return nil, err + } + + if vg != nil { + return nil, fmt.Errorf("🔴 %s: Volume group %s already exists on physical volume %s", name, vg.Name, pv.Name) + } + + mode := c.GetMode(name) + a := cvgl.lvmBackend.CreateVolumeGroup(cd.Lvm, pv.Name) + actions = append(actions, a.SetMode(mode)) + } + return actions, nil +} + +func (cvgl *CreateVolumeGroupLayer) Validate(c *config.Config) error { + for name, cd := range c.Devices { + if len(cd.Lvm) == 0 { + continue + } + _, err := cvgl.lvmBackend.GetVolumeGroup(cd.Lvm) + if err != nil { + return fmt.Errorf("🔴 %s: Failed volume group validation checks. Volume group %s does not exist", name, cd.Lvm) + } + } + return nil +} + +func (cvgl *CreateVolumeGroupLayer) Warning() string { + return DisabledWarning +} + +func (cvgl *CreateVolumeGroupLayer) From(c *config.Config) error { + return cvgl.lvmBackend.From(c) +} diff --git a/internal/model/lvm.go b/internal/model/lvm.go index 4fe4243..a8596d3 100644 --- a/internal/model/lvm.go +++ b/internal/model/lvm.go @@ -1,24 +1,10 @@ package model -type LvmDevice struct { - Name string - Child *PhysicalVolume -} - type PhysicalVolume struct { - Name string - Parent *LvmDevice - Child *VolumeGroup + Name string } type VolumeGroup struct { - Name string - Parent []*PhysicalVolume - Children []*LogicalVolume -} - -type LogicalVolume struct { - Name string - Parent *VolumeGroup - Active bool + Name string + PhysicalVolume string } diff --git a/internal/service/lvm.go b/internal/service/lvm.go index 58e82df..c0ba481 100644 --- a/internal/service/lvm.go +++ b/internal/service/lvm.go @@ -1,11 +1,18 @@ package service import ( + "encoding/json" + "fmt" + + "github.com/reecetech/ebs-bootstrap/internal/model" "github.com/reecetech/ebs-bootstrap/internal/utils" ) type LvmService interface { + GetPhysicalVolumes() ([]*model.PhysicalVolume, error) + GetVolumeGroups() ([]*model.VolumeGroup, error) CreatePhysicalVolume(name string) error + CreateVolumeGroup(name string, physicalVolume string) error } type LinuxLvmService struct { @@ -20,14 +27,70 @@ type PvsResponse struct { } `json:"report"` } +type VgsResponse struct { + Report []struct { + VolumeGroup []struct { + Name string `json:"vg_name"` + PhysicalVolume string `json:"pv_name"` + } `json:"vg"` + } `json:"report"` +} + func NewLinuxLvmService(rf utils.RunnerFactory) *LinuxLvmService { return &LinuxLvmService{ runnerFactory: rf, } } +func (ls *LinuxLvmService) GetPhysicalVolumes() ([]*model.PhysicalVolume, error) { + r := ls.runnerFactory.Select(utils.Pvs) + output, err := r.Command("-o", "pv_name", "--reportformat", "json") + if err != nil { + return nil, err + } + pr := &PvsResponse{} + err = json.Unmarshal([]byte(output), pr) + if err != nil { + return nil, fmt.Errorf("🔴 Failed to decode pvs response: %v", err) + } + pvs := make([]*model.PhysicalVolume, len(pr.Report[0].PhysicalVolume)) + for i, pv := range pr.Report[0].PhysicalVolume { + pvs[i] = &model.PhysicalVolume{ + Name: pv.Name, + } + } + return pvs, nil +} + +func (ls *LinuxLvmService) GetVolumeGroups() ([]*model.VolumeGroup, error) { + r := ls.runnerFactory.Select(utils.Vgs) + output, err := r.Command("-o", "vg_name,pv_name", "--reportformat", "json") + if err != nil { + return nil, err + } + vr := &VgsResponse{} + err = json.Unmarshal([]byte(output), vr) + if err != nil { + return nil, fmt.Errorf("🔴 Failed to decode vgs response: %v", err) + } + vgs := make([]*model.VolumeGroup, len(vr.Report[0].VolumeGroup)) + for i, vg := range vr.Report[0].VolumeGroup { + vgs[i] = &model.VolumeGroup{ + Name: vg.Name, + PhysicalVolume: vg.PhysicalVolume, + } + } + return vgs, nil +} + func (ls *LinuxLvmService) CreatePhysicalVolume(name string) error { r := ls.runnerFactory.Select(utils.PvCreate) _, err := r.Command(name) return err } + +func (ls *LinuxLvmService) CreateVolumeGroup(name string, physicalVolume string) error { + r := ls.runnerFactory.Select(utils.VgCreate) + _, err := r.Command(name, physicalVolume) + return err +} diff --git a/internal/utils/exec.go b/internal/utils/exec.go index e5b3982..50fc22b 100644 --- a/internal/utils/exec.go +++ b/internal/utils/exec.go @@ -22,7 +22,10 @@ const ( XfsInfo Binary = "xfs_info" Resize2fs Binary = "resize2fs" XfsGrowfs Binary = "xfs_growfs" + Pvs Binary = "pvs" PvCreate Binary = "pvcreate" + Vgs Binary = "vgs" + VgCreate Binary = "vgcreate" ) type RunnerFactory interface { From ab9e553aaac2fe51b75c11edd256a5dc99d7a14c Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Sun, 19 May 2024 10:29:35 +0000 Subject: [PATCH 3/9] (feat): Add Logical Volume support --- cmd/ebs-bootstrap.go | 1 + configs/ubuntu.yml | 5 ++ internal/action/lvm.go | 43 ++++++++++++++ internal/backend/lvm.go | 86 ++++++++++++++------------- internal/data_structures/lvm_graph.go | 48 +++++++-------- internal/layer/lv.go | 71 ++++++++++++++++++++++ internal/layer/vg.go | 52 +++++++--------- internal/model/lvm.go | 5 ++ internal/service/lvm.go | 38 ++++++++++++ internal/utils/exec.go | 2 + 10 files changed, 257 insertions(+), 94 deletions(-) create mode 100644 internal/layer/lv.go diff --git a/cmd/ebs-bootstrap.go b/cmd/ebs-bootstrap.go index 9ec2efb..d54c1f1 100644 --- a/cmd/ebs-bootstrap.go +++ b/cmd/ebs-bootstrap.go @@ -54,6 +54,7 @@ func main() { lvmLayers := []layer.Layer{ layer.NewCreatePhysicalVolumeLayer(db, lb), layer.NewCreateVolumeGroupLayer(lb), + layer.NewCreateLogicalVolumeLayer(lb), } checkError(le.Execute(lvmLayers)) diff --git a/configs/ubuntu.yml b/configs/ubuntu.yml index c2d32e7..e09243c 100644 --- a/configs/ubuntu.yml +++ b/configs/ubuntu.yml @@ -1,4 +1,9 @@ +defaults: + lvmConsumption: 80 devices: /dev/vdb: fs: xfs lvm: ifmx-etc + /dev/vdc: + fs: xfs + lvm: ifmx-var \ No newline at end of file diff --git a/internal/action/lvm.go b/internal/action/lvm.go index b13d500..979307c 100644 --- a/internal/action/lvm.go +++ b/internal/action/lvm.go @@ -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) +} diff --git a/internal/backend/lvm.go b/internal/backend/lvm.go index b160cfd..42fd1bf 100644 --- a/internal/backend/lvm.go +++ b/internal/backend/lvm.go @@ -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" @@ -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 } @@ -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 { @@ -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 { @@ -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 } diff --git a/internal/data_structures/lvm_graph.go b/internal/data_structures/lvm_graph.go index e351177..ebd0b4c 100644 --- a/internal/data_structures/lvm_graph.go +++ b/internal/data_structures/lvm_graph.go @@ -14,9 +14,9 @@ const ( ) type LvmNode struct { - id string - Name string - Active bool + id string + Name string + // Active bool nodeType LvmNodeType children []*LvmNode parents []*LvmNode @@ -24,9 +24,9 @@ type LvmNode struct { 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{}, @@ -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{}, @@ -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{}, @@ -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 { @@ -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 } diff --git a/internal/layer/lv.go b/internal/layer/lv.go new file mode 100644 index 0000000..cea99cc --- /dev/null +++ b/internal/layer/lv.go @@ -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) +} diff --git a/internal/layer/vg.go b/internal/layer/vg.go index 2a64a8d..499a333 100644 --- a/internal/layer/vg.go +++ b/internal/layer/vg.go @@ -24,39 +24,24 @@ func (cvgl *CreateVolumeGroupLayer) Modify(c *config.Config) ([]action.Action, e if len(cd.Lvm) == 0 { continue } - - vg, _ := cvgl.lvmBackend.GetVolumeGroup(cd.Lvm) - - if vg != nil { - pvs, err := cvgl.lvmBackend.SearchPhysicalVolumes(vg) - if err != nil { - return nil, err - } - if len(pvs) == 1 { - if pvs[0].Name == name { - continue - } - return nil, fmt.Errorf("🔴 %s: Volume group %s belongs to the incorrect physical volume. Expected=%s, Actual=%s", name, vg.Name, name, pvs[0].Name) - } - return nil, fmt.Errorf("🔴 %s: Cannot manage a volume group %s with more than one physical volumes associated", name, vg.Name) - } - - pv, err := cvgl.lvmBackend.GetPhysicalVolume(name) - if err != nil { - return nil, err + vg := cvgl.lvmBackend.SearchVolumeGroup(name) + if vg != nil && vg.Name != cd.Lvm { + return nil, fmt.Errorf("🔴 %s: Physical volume %s already has volume group %s associated", name, name, vg.Name) } - vg, err = cvgl.lvmBackend.SearchVolumeGroup(pv) - if err != nil { - return nil, err + vgs := cvgl.lvmBackend.GetVolumeGroups(cd.Lvm) + if len(vgs) == 1 { + if vgs[0].PhysicalVolume == name { + continue + } + return nil, fmt.Errorf("🔴 %s: Volume group %s does not belong to physical volume %s", name, cd.Lvm, name) } - - if vg != nil { - return nil, fmt.Errorf("🔴 %s: Volume group %s already exists on physical volume %s", name, vg.Name, pv.Name) + if len(vgs) > 1 { + return nil, fmt.Errorf("🔴 %s: Cannot manage volume group %s with more than one physical volumes associated", name, cd.Lvm) } mode := c.GetMode(name) - a := cvgl.lvmBackend.CreateVolumeGroup(cd.Lvm, pv.Name) + a := cvgl.lvmBackend.CreateVolumeGroup(cd.Lvm, name) actions = append(actions, a.SetMode(mode)) } return actions, nil @@ -67,10 +52,17 @@ func (cvgl *CreateVolumeGroupLayer) Validate(c *config.Config) error { if len(cd.Lvm) == 0 { continue } - _, err := cvgl.lvmBackend.GetVolumeGroup(cd.Lvm) - if err != nil { - return fmt.Errorf("🔴 %s: Failed volume group validation checks. Volume group %s does not exist", name, cd.Lvm) + vgs := cvgl.lvmBackend.GetVolumeGroups(cd.Lvm) + if len(vgs) == 1 { + if vgs[0].PhysicalVolume == name { + return nil + } + return fmt.Errorf("🔴 %s: Failed to validate volume group. Expected=%s, Actual=%s", name, name, vgs[0].PhysicalVolume) } + if len(vgs) > 1 { + return fmt.Errorf("🔴 %s: Failed to validate volume group. #(Physical volume) Expected=%d, Actual=%d", name, 1, len(vgs)) + } + } return nil } diff --git a/internal/model/lvm.go b/internal/model/lvm.go index a8596d3..1461c2a 100644 --- a/internal/model/lvm.go +++ b/internal/model/lvm.go @@ -8,3 +8,8 @@ type VolumeGroup struct { Name string PhysicalVolume string } + +type LogicalVolume struct { + Name string + VolumeGroup string +} diff --git a/internal/service/lvm.go b/internal/service/lvm.go index c0ba481..0242e08 100644 --- a/internal/service/lvm.go +++ b/internal/service/lvm.go @@ -11,8 +11,10 @@ import ( type LvmService interface { GetPhysicalVolumes() ([]*model.PhysicalVolume, error) GetVolumeGroups() ([]*model.VolumeGroup, error) + GetLogicalVolumes() ([]*model.LogicalVolume, error) CreatePhysicalVolume(name string) error CreateVolumeGroup(name string, physicalVolume string) error + CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent int) error } type LinuxLvmService struct { @@ -36,6 +38,15 @@ type VgsResponse struct { } `json:"report"` } +type LvsResponse struct { + Report []struct { + LogicalVolume []struct { + Name string `json:"lv_name"` + VolumeGroup string `json:"vg_name"` + } `json:"lv"` + } `json:"report"` +} + func NewLinuxLvmService(rf utils.RunnerFactory) *LinuxLvmService { return &LinuxLvmService{ runnerFactory: rf, @@ -83,6 +94,27 @@ func (ls *LinuxLvmService) GetVolumeGroups() ([]*model.VolumeGroup, error) { return vgs, nil } +func (ls *LinuxLvmService) GetLogicalVolumes() ([]*model.LogicalVolume, error) { + r := ls.runnerFactory.Select(utils.Lvs) + output, err := r.Command("-o", "lv_name,vg_name", "--reportformat", "json") + if err != nil { + return nil, err + } + lr := &LvsResponse{} + err = json.Unmarshal([]byte(output), lr) + if err != nil { + return nil, fmt.Errorf("🔴 Failed to decode lvs response: %v", err) + } + lvs := make([]*model.LogicalVolume, len(lr.Report[0].LogicalVolume)) + for i, lv := range lr.Report[0].LogicalVolume { + lvs[i] = &model.LogicalVolume{ + Name: lv.Name, + VolumeGroup: lv.VolumeGroup, + } + } + return lvs, nil +} + func (ls *LinuxLvmService) CreatePhysicalVolume(name string) error { r := ls.runnerFactory.Select(utils.PvCreate) _, err := r.Command(name) @@ -94,3 +126,9 @@ func (ls *LinuxLvmService) CreateVolumeGroup(name string, physicalVolume string) _, err := r.Command(name, physicalVolume) return err } + +func (ls *LinuxLvmService) CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent int) error { + r := ls.runnerFactory.Select(utils.LvCreate) + _, err := r.Command("-l", fmt.Sprintf("%d%%FREE", freeSpacePercent), "-n", name, volumeGroup) + return err +} diff --git a/internal/utils/exec.go b/internal/utils/exec.go index 50fc22b..645840e 100644 --- a/internal/utils/exec.go +++ b/internal/utils/exec.go @@ -26,6 +26,8 @@ const ( PvCreate Binary = "pvcreate" Vgs Binary = "vgs" VgCreate Binary = "vgcreate" + Lvs Binary = "lvs" + LvCreate Binary = "lvcreate" ) type RunnerFactory interface { From ec256bcf5e44e0b18abd39d9cb412a08840048fc Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Sun, 19 May 2024 10:45:35 +0000 Subject: [PATCH 4/9] (feat): Add Layer.ShouldProcess() method to avoid unneccesary layers --- configs/ubuntu.yml | 10 +++++++++- internal/layer/directory.go | 9 +++++++++ internal/layer/format.go | 4 ++++ internal/layer/label.go | 9 +++++++++ internal/layer/layer.go | 4 ++++ internal/layer/layer_test.go | 4 ++++ internal/layer/lv.go | 9 +++++++++ internal/layer/mount.go | 9 +++++++++ internal/layer/owner.go | 9 +++++++++ internal/layer/permissions.go | 9 +++++++++ internal/layer/pv.go | 9 +++++++++ internal/layer/resize.go | 9 +++++++++ internal/layer/vg.go | 9 +++++++++ 13 files changed, 102 insertions(+), 1 deletion(-) diff --git a/configs/ubuntu.yml b/configs/ubuntu.yml index e09243c..47ac3fd 100644 --- a/configs/ubuntu.yml +++ b/configs/ubuntu.yml @@ -4,6 +4,14 @@ devices: /dev/vdb: fs: xfs lvm: ifmx-etc + mountPoint: /mnt/foo + user: ubuntu + group: ubuntu + permissions: 755 /dev/vdc: fs: xfs - lvm: ifmx-var \ No newline at end of file + lvm: ifmx-var + mountPoint: /mnt/bar + user: ubuntu + group: ubuntu + permissions: 755 \ No newline at end of file diff --git a/internal/layer/directory.go b/internal/layer/directory.go index 18c9fa2..793f891 100644 --- a/internal/layer/directory.go +++ b/internal/layer/directory.go @@ -62,3 +62,12 @@ func (fdl *CreateDirectoryLayer) Validate(c *config.Config) error { func (fdl *CreateDirectoryLayer) Warning() string { return DisabledWarning } + +func (fdl *CreateDirectoryLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.MountPoint) > 0 { + return true + } + } + return false +} diff --git a/internal/layer/format.go b/internal/layer/format.go index f5f5d1c..86ce2dd 100644 --- a/internal/layer/format.go +++ b/internal/layer/format.go @@ -67,3 +67,7 @@ func (fdl *FormatDeviceLayer) Validate(c *config.Config) error { func (fdl *FormatDeviceLayer) Warning() string { return "Formatting larger disks can take several seconds ⌛" } + +func (fdl *FormatDeviceLayer) ShouldProcess(c *config.Config) bool { + return true +} diff --git a/internal/layer/label.go b/internal/layer/label.go index 0c32fd8..d0e14a3 100644 --- a/internal/layer/label.go +++ b/internal/layer/label.go @@ -68,3 +68,12 @@ func (fdl *LabelDeviceLayer) Validate(c *config.Config) error { func (fdl *LabelDeviceLayer) Warning() string { return "Certain file systems require that devices be unmounted prior to labeling" } + +func (fdl *LabelDeviceLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.Label) > 0 { + return true + } + } + return false +} diff --git a/internal/layer/layer.go b/internal/layer/layer.go index 38702ba..75725ec 100644 --- a/internal/layer/layer.go +++ b/internal/layer/layer.go @@ -19,6 +19,7 @@ type Layer interface { Modify(c *config.Config) ([]action.Action, error) Validate(config *config.Config) error Warning() string + ShouldProcess(config *config.Config) bool } type LayerExecutor interface { @@ -70,6 +71,9 @@ func NewExponentialBackoffLayerExecutor(c *config.Config, ae action.ActionExecut func (le *ExponentialBackoffLayerExecutor) Execute(layers []Layer) error { for _, layer := range layers { + if !layer.ShouldProcess(le.config) { + continue + } err := layer.From(le.config) if err != nil { return err diff --git a/internal/layer/layer_test.go b/internal/layer/layer_test.go index b2a3e1d..d973d61 100644 --- a/internal/layer/layer_test.go +++ b/internal/layer/layer_test.go @@ -40,6 +40,10 @@ func (ml *MockLayer) Warning() string { return DisabledWarning } +func (ml *MockLayer) ShouldProcess(c *config.Config) bool { + return true +} + func TestExponentialBackoffLayerExecutor(t *testing.T) { mae := action.NewDefaultActionExecutor() // Lets generate ExponentialBackoffParameters with a custom diff --git a/internal/layer/lv.go b/internal/layer/lv.go index cea99cc..52331a3 100644 --- a/internal/layer/lv.go +++ b/internal/layer/lv.go @@ -69,3 +69,12 @@ func (cvgl *CreateLogicalVolumeLayer) Warning() string { func (cvgl *CreateLogicalVolumeLayer) From(c *config.Config) error { return cvgl.lvmBackend.From(c) } + +func (cvgl *CreateLogicalVolumeLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.Lvm) > 0 { + return true + } + } + return false +} diff --git a/internal/layer/mount.go b/internal/layer/mount.go index 6e2d042..18929e7 100644 --- a/internal/layer/mount.go +++ b/internal/layer/mount.go @@ -95,3 +95,12 @@ func (fdl *MountDeviceLayer) Validate(c *config.Config) error { func (fdl *MountDeviceLayer) Warning() string { return "Devices mounted to a location, not specified in the configuration, will be unmounted" } + +func (fdl *MountDeviceLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.MountPoint) > 0 { + return true + } + } + return false +} diff --git a/internal/layer/owner.go b/internal/layer/owner.go index 43087a7..4a43933 100644 --- a/internal/layer/owner.go +++ b/internal/layer/owner.go @@ -115,3 +115,12 @@ func (fdl *ChangeOwnerLayer) Validate(c *config.Config) error { func (fdl *ChangeOwnerLayer) Warning() string { return DisabledWarning } + +func (fdl *ChangeOwnerLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.MountPoint) > 0 && (len(cd.User) > 0 || len(cd.Group) > 0) { + return true + } + } + return false +} diff --git a/internal/layer/permissions.go b/internal/layer/permissions.go index 89b76f5..c3d8365 100644 --- a/internal/layer/permissions.go +++ b/internal/layer/permissions.go @@ -72,3 +72,12 @@ func (fdl *ChangePermissionsLayer) Validate(c *config.Config) error { func (fdl *ChangePermissionsLayer) Warning() string { return DisabledWarning } + +func (fdl *ChangePermissionsLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.MountPoint) > 0 && cd.Permissions != 0 { + return true + } + } + return false +} diff --git a/internal/layer/pv.go b/internal/layer/pv.go index 2b3a87b..aa69637 100644 --- a/internal/layer/pv.go +++ b/internal/layer/pv.go @@ -68,3 +68,12 @@ func (cpvl *CreatePhysicalVolumeLayer) From(c *config.Config) error { // Lvmackend does not have to be initialised as we are only creating a Physical Volume return cpvl.deviceBackend.From(c) } + +func (cpvl *CreatePhysicalVolumeLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.Lvm) > 0 { + return true + } + } + return false +} diff --git a/internal/layer/resize.go b/internal/layer/resize.go index 8da913d..0d2042a 100644 --- a/internal/layer/resize.go +++ b/internal/layer/resize.go @@ -83,3 +83,12 @@ func (fdl *ResizeDeviceLayer) Validate(c *config.Config) error { func (fdl *ResizeDeviceLayer) Warning() string { return DisabledWarning } + +func (fdl *ResizeDeviceLayer) ShouldProcess(c *config.Config) bool { + for name := range c.Devices { + if c.GetResizeFs(name) { + return true + } + } + return false +} diff --git a/internal/layer/vg.go b/internal/layer/vg.go index 499a333..93e86ed 100644 --- a/internal/layer/vg.go +++ b/internal/layer/vg.go @@ -74,3 +74,12 @@ func (cvgl *CreateVolumeGroupLayer) Warning() string { func (cvgl *CreateVolumeGroupLayer) From(c *config.Config) error { return cvgl.lvmBackend.From(c) } + +func (cvgl *CreateVolumeGroupLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.Lvm) > 0 { + return true + } + } + return false +} From 9bda81ed5c98f40c7b699a65874bf1fa69dd6c9b Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Sun, 19 May 2024 21:43:27 +0000 Subject: [PATCH 5/9] (feat): Add volume activation layer --- cmd/ebs-bootstrap.go | 1 + configs/ubuntu.yml | 2 +- internal/action/lvm.go | 41 ++++++++++++++ internal/backend/lvm.go | 27 ++++++++- internal/data_structures/lvm_graph.go | 82 ++++++++++++++------------- internal/layer/lv_activate.go | 73 ++++++++++++++++++++++++ internal/model/lvm.go | 9 +++ internal/service/lvm.go | 20 ++++++- internal/utils/exec.go | 1 + 9 files changed, 215 insertions(+), 41 deletions(-) create mode 100644 internal/layer/lv_activate.go diff --git a/cmd/ebs-bootstrap.go b/cmd/ebs-bootstrap.go index d54c1f1..21c4205 100644 --- a/cmd/ebs-bootstrap.go +++ b/cmd/ebs-bootstrap.go @@ -55,6 +55,7 @@ func main() { layer.NewCreatePhysicalVolumeLayer(db, lb), layer.NewCreateVolumeGroupLayer(lb), layer.NewCreateLogicalVolumeLayer(lb), + layer.NewActivateLogicalVolumeLayer(lb), } checkError(le.Execute(lvmLayers)) diff --git a/configs/ubuntu.yml b/configs/ubuntu.yml index 47ac3fd..af81fb6 100644 --- a/configs/ubuntu.yml +++ b/configs/ubuntu.yml @@ -1,5 +1,5 @@ defaults: - lvmConsumption: 80 + lvmConsumption: 100 devices: /dev/vdb: fs: xfs diff --git a/internal/action/lvm.go b/internal/action/lvm.go index 979307c..5be10a6 100644 --- a/internal/action/lvm.go +++ b/internal/action/lvm.go @@ -129,3 +129,44 @@ func (a *CreateLogicalVolumeAction) Refuse() string { 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) } + +type ActivateLogicalVolumeAction struct { + name string + volumeGroup string + mode model.Mode + lvmService service.LvmService +} + +func NewActivateLogicalVolumeAction(name string, volumeGroup string, ls service.LvmService) *ActivateLogicalVolumeAction { + return &ActivateLogicalVolumeAction{ + name: name, + volumeGroup: volumeGroup, + mode: model.Empty, + lvmService: ls, + } +} + +func (a *ActivateLogicalVolumeAction) Execute() error { + return a.lvmService.ActivateLogicalVolume(a.name, a.volumeGroup) +} + +func (a *ActivateLogicalVolumeAction) GetMode() model.Mode { + return a.mode +} + +func (a *ActivateLogicalVolumeAction) SetMode(mode model.Mode) Action { + a.mode = mode + return a +} + +func (a *ActivateLogicalVolumeAction) Prompt() string { + return fmt.Sprintf("Would you like to activate logical volume %s in volume group %s", a.name, a.volumeGroup) +} + +func (a *ActivateLogicalVolumeAction) Refuse() string { + return fmt.Sprintf("Refused to activate logical volume %s in volume group %s", a.name, a.volumeGroup) +} + +func (a *ActivateLogicalVolumeAction) Success() string { + return fmt.Sprintf("Successfully activated logical volume %s in volume group %s", a.name, a.volumeGroup) +} diff --git a/internal/backend/lvm.go b/internal/backend/lvm.go index 42fd1bf..6186255 100644 --- a/internal/backend/lvm.go +++ b/internal/backend/lvm.go @@ -1,6 +1,8 @@ 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" @@ -12,7 +14,9 @@ type LvmBackend interface { CreatePhysicalVolume(name string) action.Action CreateVolumeGroup(name string, physicalVolume string) action.Action CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent 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 From(config *config.Config) error @@ -46,6 +50,22 @@ func (lb *LinuxLvmBackend) GetVolumeGroups(name string) []*model.VolumeGroup { return vgs } +func (lb *LinuxLvmBackend) GetLogicalVolume(name string, volumeGroup string) (*model.LogicalVolume, error) { + node, err := lb.lvmGraph.GetLogicalVolume(name, volumeGroup) + if err != nil { + return nil, err + } + vgs := lb.lvmGraph.GetParents(node, datastructures.VolumeGroup) + if len(vgs) == 0 { + return nil, fmt.Errorf("🔴 %s: Logical volume has no volume group", node.Name) + } + return &model.LogicalVolume{ + Name: node.Name, + VolumeGroup: vgs[0].Name, + State: model.LogicalVolumeState(node.State), + }, nil +} + func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) []*model.LogicalVolume { lvs := []*model.LogicalVolume{} node, err := lb.lvmGraph.GetVolumeGroup(volumeGroup) @@ -57,6 +77,7 @@ func (lb *LinuxLvmBackend) SearchLogicalVolumes(volumeGroup string) []*model.Log lvs = append(lvs, &model.LogicalVolume{ Name: lv.Name, VolumeGroup: node.Name, + State: model.LogicalVolumeState(lv.State), }) } return lvs @@ -89,6 +110,10 @@ func (lb *LinuxLvmBackend) CreateLogicalVolume(name string, volumeGroup string, return action.NewCreateLogicalVolumeAction(name, freeSpacePercent, volumeGroup, lb.lvmService) } +func (lb *LinuxLvmBackend) ActivateLogicalVolume(name string, volumeGroup string) action.Action { + return action.NewActivateLogicalVolumeAction(name, volumeGroup, lb.lvmService) +} + func (db *LinuxLvmBackend) From(config *config.Config) error { pvs, err := db.lvmService.GetPhysicalVolumes() if err != nil { @@ -110,7 +135,7 @@ func (db *LinuxLvmBackend) From(config *config.Config) error { return err } for _, lv := range lvs { - db.lvmGraph.AddLogicalVolume(lv.Name, lv.VolumeGroup) + db.lvmGraph.AddLogicalVolume(lv.Name, lv.VolumeGroup, datastructures.LvmNodeState(lv.State)) } return nil } diff --git a/internal/data_structures/lvm_graph.go b/internal/data_structures/lvm_graph.go index ebd0b4c..7e87f02 100644 --- a/internal/data_structures/lvm_graph.go +++ b/internal/data_structures/lvm_graph.go @@ -4,30 +4,39 @@ import ( "fmt" ) -type LvmNodeType int +type LvmNodeState int32 +type LvmNodeCategory int32 const ( - BlockDevice LvmNodeType = 0 - PhysicalVolume LvmNodeType = 1 - VolumeGroup LvmNodeType = 2 - LogicalVolume LvmNodeType = 3 + BlockDeviceActive LvmNodeState = 0b0000001 + PhysicalVolumeActive LvmNodeState = 0b0000010 + VolumeGroupInactive LvmNodeState = 0b0000100 + VolumeGroupActive LvmNodeState = 0b0001100 + LogicalVolumeInactive LvmNodeState = 0b0010000 + LogicalVolumeActive LvmNodeState = 0b0110000 + LogicalVolumeUnsupported LvmNodeState = 0b1110000 +) + +const ( + BlockDevice LvmNodeCategory = 0b0000001 + PhysicalVolume LvmNodeCategory = 0b0000010 + VolumeGroup LvmNodeCategory = 0b0000100 + LogicalVolume LvmNodeCategory = 0b0010000 ) type LvmNode struct { - id string - Name string - // Active bool - nodeType LvmNodeType + id string + Name string + State LvmNodeState children []*LvmNode parents []*LvmNode } func NewBlockDevice(name string) *LvmNode { return &LvmNode{ - id: fmt.Sprintf("device:%s", name), - Name: name, - // Active: true, - nodeType: BlockDevice, + id: fmt.Sprintf("device:%s", name), + Name: name, + State: BlockDeviceActive, children: []*LvmNode{}, parents: []*LvmNode{}, } @@ -35,10 +44,9 @@ func NewBlockDevice(name string) *LvmNode { func NewPhysicalVolume(name string) *LvmNode { return &LvmNode{ - id: fmt.Sprintf("pv:%s", name), - Name: name, - // Active: true, - nodeType: PhysicalVolume, + id: fmt.Sprintf("pv:%s", name), + Name: name, + State: PhysicalVolumeActive, children: []*LvmNode{}, parents: []*LvmNode{}, } @@ -46,21 +54,19 @@ func NewPhysicalVolume(name string) *LvmNode { func NewVolumeGroup(name string) *LvmNode { return &LvmNode{ - id: fmt.Sprintf("vg:%s", name), - Name: name, - // Active: false, - nodeType: VolumeGroup, + id: fmt.Sprintf("vg:%s", name), + Name: name, + State: VolumeGroupInactive, children: []*LvmNode{}, parents: []*LvmNode{}, } } -func NewLogicalVolume(name string, vg string) *LvmNode { +func NewLogicalVolume(name string, vg string, State LvmNodeState) *LvmNode { return &LvmNode{ - id: fmt.Sprintf("lv:%s:vg:%s", name, vg), - Name: name, - // Active: active, - nodeType: LogicalVolume, + id: fmt.Sprintf("lv:%s:vg:%s", name, vg), + Name: name, + State: State, children: []*LvmNode{}, parents: []*LvmNode{}, } @@ -132,8 +138,8 @@ func (lg *LvmGraph) AddVolumeGroup(name string, pv string) error { return nil } -func (lg *LvmGraph) AddLogicalVolume(name string, vg string) error { - lv := NewLogicalVolume(name, vg) +func (lg *LvmGraph) AddLogicalVolume(name string, vg string, state LvmNodeState) error { + lv := NewLogicalVolume(name, vg, state) _, found := lg.nodes[lv.id] if found { @@ -152,12 +158,12 @@ func (lg *LvmGraph) AddLogicalVolume(name string, vg string) 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.State == LogicalVolumeActive { + vgn.State = VolumeGroupActive + break + } + } return nil } @@ -188,20 +194,20 @@ func (lg *LvmGraph) GetLogicalVolume(name string, vg string) (*LvmNode, error) { return node, nil } -func (lg *LvmGraph) GetParents(node *LvmNode, nodeType LvmNodeType) []*LvmNode { +func (lg *LvmGraph) GetParents(node *LvmNode, state LvmNodeCategory) []*LvmNode { parents := []*LvmNode{} for _, p := range node.parents { - if p.nodeType == nodeType { + if int32(p.State)&int32(state) > 0 { parents = append(parents, p) } } return parents } -func (lg *LvmGraph) GetChildren(node *LvmNode, nodeType LvmNodeType) []*LvmNode { +func (lg *LvmGraph) GetChildren(node *LvmNode, state LvmNodeCategory) []*LvmNode { children := []*LvmNode{} for _, c := range node.children { - if c.nodeType == nodeType { + if int32(c.State)&int32(state) > 0 { children = append(children, c) } } diff --git a/internal/layer/lv_activate.go b/internal/layer/lv_activate.go new file mode 100644 index 0000000..fb5fffb --- /dev/null +++ b/internal/layer/lv_activate.go @@ -0,0 +1,73 @@ +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" + "github.com/reecetech/ebs-bootstrap/internal/model" +) + +type ActivateLogicalVolumeLayer struct { + lvmBackend backend.LvmBackend +} + +func NewActivateLogicalVolumeLayer(lb backend.LvmBackend) *ActivateLogicalVolumeLayer { + return &ActivateLogicalVolumeLayer{ + lvmBackend: lb, + } +} + +func (cvgl *ActivateLogicalVolumeLayer) Modify(c *config.Config) ([]action.Action, error) { + actions := make([]action.Action, 0) + for name, cd := range c.Devices { + if len(cd.Lvm) == 0 { + continue + } + + lv, err := cvgl.lvmBackend.GetLogicalVolume(cd.Lvm, cd.Lvm) + if err != nil { + return nil, err + } + + if lv.State == model.Active { + continue + } + + if lv.State == model.Unsupported { + return nil, fmt.Errorf("🔴 %s: Can not activate a logical volume in an unsupported state", lv.Name) + } + + mode := c.GetMode(name) + a := cvgl.lvmBackend.ActivateLogicalVolume(cd.Lvm, cd.Lvm) + actions = append(actions, a.SetMode(mode)) + } + return actions, nil +} + +func (cvgl *ActivateLogicalVolumeLayer) Validate(c *config.Config) error { + for _, cd := range c.Devices { + if len(cd.Lvm) == 0 { + continue + } + } + return nil +} + +func (cvgl *ActivateLogicalVolumeLayer) Warning() string { + return DisabledWarning +} + +func (cvgl *ActivateLogicalVolumeLayer) From(c *config.Config) error { + return cvgl.lvmBackend.From(c) +} + +func (cvgl *ActivateLogicalVolumeLayer) ShouldProcess(c *config.Config) bool { + for _, cd := range c.Devices { + if len(cd.Lvm) > 0 { + return true + } + } + return false +} diff --git a/internal/model/lvm.go b/internal/model/lvm.go index 1461c2a..5b05cbb 100644 --- a/internal/model/lvm.go +++ b/internal/model/lvm.go @@ -9,7 +9,16 @@ type VolumeGroup struct { PhysicalVolume string } +type LogicalVolumeState int32 + +const ( + Inactive LogicalVolumeState = 0b0010000 + Active LogicalVolumeState = 0b0110000 + Unsupported LogicalVolumeState = 0b1110000 +) + type LogicalVolume struct { Name string VolumeGroup string + State LogicalVolumeState } diff --git a/internal/service/lvm.go b/internal/service/lvm.go index 0242e08..f92540b 100644 --- a/internal/service/lvm.go +++ b/internal/service/lvm.go @@ -15,6 +15,7 @@ type LvmService interface { CreatePhysicalVolume(name string) error CreateVolumeGroup(name string, physicalVolume string) error CreateLogicalVolume(name string, volumeGroup string, freeSpacePercent int) error + ActivateLogicalVolume(name string, volumeGroup string) error } type LinuxLvmService struct { @@ -43,6 +44,7 @@ type LvsResponse struct { LogicalVolume []struct { Name string `json:"lv_name"` VolumeGroup string `json:"vg_name"` + Attributes string `json:"lv_attr"` } `json:"lv"` } `json:"report"` } @@ -96,7 +98,7 @@ func (ls *LinuxLvmService) GetVolumeGroups() ([]*model.VolumeGroup, error) { func (ls *LinuxLvmService) GetLogicalVolumes() ([]*model.LogicalVolume, error) { r := ls.runnerFactory.Select(utils.Lvs) - output, err := r.Command("-o", "lv_name,vg_name", "--reportformat", "json") + output, err := r.Command("-o", "lv_name,vg_name,lv_attr", "--reportformat", "json") if err != nil { return nil, err } @@ -107,9 +109,19 @@ func (ls *LinuxLvmService) GetLogicalVolumes() ([]*model.LogicalVolume, error) { } lvs := make([]*model.LogicalVolume, len(lr.Report[0].LogicalVolume)) for i, lv := range lr.Report[0].LogicalVolume { + var state model.LogicalVolumeState + switch lv.Attributes[4] { + case 'a': + state = model.Active + case '-': + state = model.Inactive + default: + state = model.Unsupported + } lvs[i] = &model.LogicalVolume{ Name: lv.Name, VolumeGroup: lv.VolumeGroup, + State: state, } } return lvs, nil @@ -132,3 +144,9 @@ func (ls *LinuxLvmService) CreateLogicalVolume(name string, volumeGroup string, _, err := r.Command("-l", fmt.Sprintf("%d%%FREE", freeSpacePercent), "-n", name, volumeGroup) return err } + +func (ls *LinuxLvmService) ActivateLogicalVolume(name string, volumeGroup string) error { + r := ls.runnerFactory.Select(utils.LvChange) + _, err := r.Command("-ay", fmt.Sprintf("%s/%s", volumeGroup, name)) + return err +} diff --git a/internal/utils/exec.go b/internal/utils/exec.go index 645840e..070ecdc 100644 --- a/internal/utils/exec.go +++ b/internal/utils/exec.go @@ -28,6 +28,7 @@ const ( VgCreate Binary = "vgcreate" Lvs Binary = "lvs" LvCreate Binary = "lvcreate" + LvChange Binary = "lvchange" ) type RunnerFactory interface { From 519771a220961fa660c37ec242c806b25976fb28 Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Mon, 20 May 2024 00:18:10 +0000 Subject: [PATCH 6/9] (feat): Add LvmConsumptionValidator() --- cmd/ebs-bootstrap.go | 42 ++++++++++++++++-------------------- internal/config/validator.go | 25 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/cmd/ebs-bootstrap.go b/cmd/ebs-bootstrap.go index 21c4205..39ad3c0 100644 --- a/cmd/ebs-bootstrap.go +++ b/cmd/ebs-bootstrap.go @@ -42,14 +42,23 @@ func main() { dae := action.NewDefaultActionExecutor() le := layer.NewExponentialBackoffLayerExecutor(c, dae, layer.DefaultExponentialBackoffParameters()) - // Modify Config - modifiers := []config.Modifier{ - config.NewAwsNVMeDriverModifier(ans, lds), + // Validate Config + validators := []config.Validator{ + config.NewFileSystemValidator(), + config.NewModeValidator(), + config.NewResizeThresholdValidator(), + config.NewMountPointValidator(), + config.NewMountOptionsValidator(), + config.NewOwnerValidator(uos), + config.NewLvmConsumptionValidator(), } - for _, m := range modifiers { - checkError(m.Modify(c)) + for _, v := range validators { + checkError(v.Validate(c)) } + // NVMe Device Modifier + checkError(config.NewAwsNVMeDriverModifier(ans, lds).Modify(c)) + // LVM Layers lvmLayers := []layer.Layer{ layer.NewCreatePhysicalVolumeLayer(db, lb), @@ -60,26 +69,10 @@ func main() { checkError(le.Execute(lvmLayers)) // LVM Modifiers - lvmModifiers := []config.Modifier{ - config.NewLvmModifier(), - } - for _, m := range lvmModifiers { - checkError(m.Modify(c)) - } + checkError(config.NewLvmModifier().Modify(c)) - // Validate Config - validators := []config.Validator{ - config.NewFileSystemValidator(), - config.NewModeValidator(), - config.NewResizeThresholdValidator(), - config.NewDeviceValidator(lds), - config.NewMountPointValidator(), - config.NewMountOptionsValidator(), - config.NewOwnerValidator(uos), - } - for _, v := range validators { - checkError(v.Validate(c)) - } + // Device Validator + checkError(config.NewDeviceValidator(lds).Validate(c)) // Layers layers := []layer.Layer{ @@ -91,6 +84,7 @@ func main() { layer.NewChangeOwnerLayer(ub, fb), layer.NewChangePermissionsLayer(fb), } + checkError(le.Execute(layers)) log.Println("🟢 Passed all validation checks") } diff --git a/internal/config/validator.go b/internal/config/validator.go index 04d674d..7e9f70b 100644 --- a/internal/config/validator.go +++ b/internal/config/validator.go @@ -187,3 +187,28 @@ func (rtv *ResizeThresholdValidator) Validate(c *Config) error { func (rtv *ResizeThresholdValidator) isValid(rt float64) bool { return rt >= 0 && rt <= 100 } + +type LvmConsumptionValidator struct{} + +func NewLvmConsumptionValidator() *LvmConsumptionValidator { + return &LvmConsumptionValidator{} +} + +func (lcv *LvmConsumptionValidator) Validate(c *Config) error { + if !lcv.isValid(c.Defaults.LvmConsumption) { + return fmt.Errorf("🔴 '%d' (default) must be an integer between 0 and 100 (inclusive)", c.Defaults.LvmConsumption) + } + if !lcv.isValid(c.overrides.LvmConsumption) { + return fmt.Errorf("🔴 '%d' (-lvm-consumption) must be an integer between 0 and 100 (inclusive)", c.overrides.LvmConsumption) + } + for name, device := range c.Devices { + if !lcv.isValid(device.LvmConsumption) { + return fmt.Errorf("🔴 %s: '%d' must be an integer between 0 and 100 (inclusive)", name, device.LvmConsumption) + } + } + return nil +} + +func (lcv *LvmConsumptionValidator) isValid(lc int) bool { + return lc >= 0 && lc <= 100 +} From 144f3c7936cbf537d687eb6b5f3aeb711a820a21 Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Mon, 20 May 2024 01:31:46 +0000 Subject: [PATCH 7/9] (feat): Error messages update --- LVM.md | 54 -------------------------------------------- cmd/ebs-bootstrap.go | 6 ++--- internal/layer/vg.go | 4 ++-- 3 files changed, 5 insertions(+), 59 deletions(-) delete mode 100644 LVM.md diff --git a/LVM.md b/LVM.md deleted file mode 100644 index cd1f82d..0000000 --- a/LVM.md +++ /dev/null @@ -1,54 +0,0 @@ -``` -$ sudo blockdev --getsize64 /dev/vdb -10737418240 - -$ sudo pvdisplay /dev/vdb --units B -... - PV Size 10737418240 B / not usable 4194304 B - -$ sudo vgdisplay vgdemo --units B -... - VG Size 10733223936 B - -# 10737418240 - 10733223936 = 4194304 Bytes (4 KB) - -$ sudo lvdisplay vgdemo/lvdemo --units B -... -LV Size 10733223936 B -``` - -``` -LV -> VG -VG -> PV -PV -> Block Device -``` - -``` -$ sudo lvs -o lv_name,vg_name,lv_size,vg_size,lv_attr --units B --nosuffix - LV VG LSize VSize Attr - ubuntu-lv ubuntu-vg 31079792640B 31079792640B -wi-ao---- - lvdemo vgdemo 10733223936B 10733223936B -wi-a----- - -$ sudo vgs -o vg_name,pv_name --units B --nosuffix - VG PV - ubuntu-vg /dev/vda3 - vgdemo /dev/vdb - -$ 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" -``` \ No newline at end of file diff --git a/cmd/ebs-bootstrap.go b/cmd/ebs-bootstrap.go index 39ad3c0..2270e26 100644 --- a/cmd/ebs-bootstrap.go +++ b/cmd/ebs-bootstrap.go @@ -31,7 +31,7 @@ func main() { c, err := config.New(os.Args) checkError(err) - // Service + Config Consumers + // Services db := backend.NewLinuxDeviceBackend(lds, fssf) fb := backend.NewLinuxFileBackend(ufs) ub := backend.NewLinuxOwnerBackend(uos) @@ -74,7 +74,7 @@ func main() { // Device Validator checkError(config.NewDeviceValidator(lds).Validate(c)) - // Layers + // File System Layers layers := []layer.Layer{ layer.NewFormatDeviceLayer(db), layer.NewLabelDeviceLayer(db), @@ -84,8 +84,8 @@ func main() { layer.NewChangeOwnerLayer(ub, fb), layer.NewChangePermissionsLayer(fb), } - checkError(le.Execute(layers)) + log.Println("🟢 Passed all validation checks") } diff --git a/internal/layer/vg.go b/internal/layer/vg.go index 93e86ed..ca51758 100644 --- a/internal/layer/vg.go +++ b/internal/layer/vg.go @@ -34,10 +34,10 @@ func (cvgl *CreateVolumeGroupLayer) Modify(c *config.Config) ([]action.Action, e if vgs[0].PhysicalVolume == name { continue } - return nil, fmt.Errorf("🔴 %s: Volume group %s does not belong to physical volume %s", name, cd.Lvm, name) + return nil, fmt.Errorf("🔴 %s: Volume group %s already exists and belongs to physical volume %s", name, cd.Lvm, vgs[0].PhysicalVolume) } if len(vgs) > 1 { - return nil, fmt.Errorf("🔴 %s: Cannot manage volume group %s with more than one physical volumes associated", name, cd.Lvm) + return nil, fmt.Errorf("🔴 %s: Cannot manage volume group %s because it is associated with more than one physical volume", name, cd.Lvm) } mode := c.GetMode(name) From 382cecc79e06116fdcddbf6adfab2bcbfd5d6b91 Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Mon, 20 May 2024 01:38:52 +0000 Subject: [PATCH 8/9] (feat): Rename data_structures package --- internal/backend/lvm.go | 2 +- internal/{data_structures => datastructures}/lvm_graph.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename internal/{data_structures => datastructures}/lvm_graph.go (100%) diff --git a/internal/backend/lvm.go b/internal/backend/lvm.go index 6186255..bec98b1 100644 --- a/internal/backend/lvm.go +++ b/internal/backend/lvm.go @@ -5,7 +5,7 @@ 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/datastructures" "github.com/reecetech/ebs-bootstrap/internal/model" "github.com/reecetech/ebs-bootstrap/internal/service" ) diff --git a/internal/data_structures/lvm_graph.go b/internal/datastructures/lvm_graph.go similarity index 100% rename from internal/data_structures/lvm_graph.go rename to internal/datastructures/lvm_graph.go From bd57ab298232def719932d36b094ce60f9e305ca Mon Sep 17 00:00:00 2001 From: lasith-kg Date: Mon, 20 May 2024 12:53:19 +0000 Subject: [PATCH 9/9] (feat): Suggestions from Pull Request --- configs/ubuntu.yml | 2 +- internal/config/modifier.go | 5 ++--- internal/datastructures/lvm_graph.go | 2 ++ internal/model/lvm.go | 8 +++++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/configs/ubuntu.yml b/configs/ubuntu.yml index af81fb6..2ab6075 100644 --- a/configs/ubuntu.yml +++ b/configs/ubuntu.yml @@ -14,4 +14,4 @@ devices: mountPoint: /mnt/bar user: ubuntu group: ubuntu - permissions: 755 \ No newline at end of file + permissions: 755 diff --git a/internal/config/modifier.go b/internal/config/modifier.go index 09f7fcc..4dc5d99 100644 --- a/internal/config/modifier.go +++ b/internal/config/modifier.go @@ -70,9 +70,8 @@ func NewLvmModifier() *LvmModifier { } func (lm *LvmModifier) Modify(c *Config) error { - // Fetch a copy of the original keys as we are updating - // the config in-place and it is unsafe to iterate over it - // directly + // Fetch a copy of the original keys as we are updating the + // config in-place and it is unsafe to iterate over it directly keys := make([]string, len(c.Devices)) for name := range c.Devices { keys = append(keys, name) diff --git a/internal/datastructures/lvm_graph.go b/internal/datastructures/lvm_graph.go index 7e87f02..29bbd89 100644 --- a/internal/datastructures/lvm_graph.go +++ b/internal/datastructures/lvm_graph.go @@ -197,6 +197,7 @@ func (lg *LvmGraph) GetLogicalVolume(name string, vg string) (*LvmNode, error) { func (lg *LvmGraph) GetParents(node *LvmNode, state LvmNodeCategory) []*LvmNode { parents := []*LvmNode{} for _, p := range node.parents { + // Bitmasking to check if the parent nodes is of the desired category if int32(p.State)&int32(state) > 0 { parents = append(parents, p) } @@ -207,6 +208,7 @@ func (lg *LvmGraph) GetParents(node *LvmNode, state LvmNodeCategory) []*LvmNode func (lg *LvmGraph) GetChildren(node *LvmNode, state LvmNodeCategory) []*LvmNode { children := []*LvmNode{} for _, c := range node.children { + // Bitmasking to check if children nodes is of the desired category if int32(c.State)&int32(state) > 0 { children = append(children, c) } diff --git a/internal/model/lvm.go b/internal/model/lvm.go index 5b05cbb..1feb5de 100644 --- a/internal/model/lvm.go +++ b/internal/model/lvm.go @@ -1,5 +1,7 @@ package model +import "github.com/reecetech/ebs-bootstrap/internal/datastructures" + type PhysicalVolume struct { Name string } @@ -12,9 +14,9 @@ type VolumeGroup struct { type LogicalVolumeState int32 const ( - Inactive LogicalVolumeState = 0b0010000 - Active LogicalVolumeState = 0b0110000 - Unsupported LogicalVolumeState = 0b1110000 + Inactive LogicalVolumeState = LogicalVolumeState(datastructures.LogicalVolumeInactive) + Active LogicalVolumeState = LogicalVolumeState(datastructures.LogicalVolumeActive) + Unsupported LogicalVolumeState = LogicalVolumeState(datastructures.LogicalVolumeUnsupported) ) type LogicalVolume struct {