Skip to content

Commit

Permalink
add the ability to create lvm in the partition command
Browse files Browse the repository at this point in the history
  • Loading branch information
walkerus committed Aug 30, 2023
1 parent 404dab7 commit d5aba0a
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 3 deletions.
3 changes: 2 additions & 1 deletion actions/rootio/v1/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ RUN ./autogen.sh; ./configure
RUN make LDFLAGS="--static"

# Build final image
FROM scratch
FROM alpine
RUN apk add lvm2
COPY --from=mke2fs /e2fsprogs-1.45.6/misc/mke2fs.static /sbin/mke2fs
COPY --from=swap util-linux/swapon /sbin/swapon
COPY --from=swap util-linux/mkswap /sbin/mkswap
Expand Down
10 changes: 10 additions & 0 deletions actions/rootio/v1/cmd/rootio.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ var rootioPartition = &cobra.Command{
if err != nil {
log.Error(err)
}

if len(metadata.Instance.Storage.VolumeGroups) > 0 {
log.Infoln("Creating Volume Groups")
}

for _, vg := range metadata.Instance.Storage.VolumeGroups {
if err := storage.CreateVolumeGroup(vg); err != nil {
log.Error(err)
}
}
}
},
}
Expand Down
183 changes: 183 additions & 0 deletions actions/rootio/v1/pkg/lvm/lvm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package lvm

import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"

log "github.com/sirupsen/logrus"
)

var lvNameRegexp = regexp.MustCompile("^[A-Za-z0-9_+.][A-Za-z0-9_+.-]*$")
var vgNameRegexp = regexp.MustCompile("^[A-Za-z0-9_+.][A-Za-z0-9_+.-]*$")
var tagRegexp = regexp.MustCompile("^[A-Za-z0-9_+.][A-Za-z0-9_+.-]*$")

type VolumeGroup struct {
name string
}

// CreatePhysicalVolume creates a physical volume of the given device.
func CreatePhysicalVolume(dev string) error {
if err := run("pvcreate", dev); err != nil {
return fmt.Errorf("lvm: CreatePhysicalVolume: %v", err)
}
return nil
}

// PVScan runs the `pvscan --cache <dev>` command. It scans for the
// device at `dev` and adds it to the LVM metadata cache if `lvmetad`
// is running. If `dev` is an empty string, it scans all devices.
func PVScan(dev string) error {
args := []string{"--cache"}
if dev != "" {
args = append(args, dev)
}
return run("pvscan", args...)
}

// VGScan runs the `vgscan --cache <name>` command. It scans for the
// volume group and adds it to the LVM metadata cache if `lvmetad`
// is running. If `name` is an empty string, it scans all volume groups.
func VGScan(name string) error {
args := []string{"--cache"}
if name != "" {
args = append(args, name)
}
return run("vgscan", args...)
}

// ValidateVolumeGroupName validates a volume group name. A valid volume group
// name can consist of a limited range of characters only. The allowed
// characters are [A-Za-z0-9_+.-].
func ValidateVolumeGroupName(name string) error {
if !vgNameRegexp.MatchString(name) {
return fmt.Errorf("lvm: Volume group name %q contains invalid character, valid set includes: [A-Za-z0-9_+.-]", name)
}
return nil
}

// ValidateTag validates a tag. LVM tags are strings of up to 1024
// characters. LVM tags cannot start with a hyphen. A valid tag can consist of
// a limited range of characters only. The allowed characters are
// [A-Za-z0-9_+.-]. As of the Red Hat Enterprise Linux 6.1 release, the list of
// allowed characters was extended, and tags can contain the /, =, !, :, #, and
// & characters.
// See https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/logical_volume_manager_administration/lvm_tags
func ValidateTag(tag string) error {
if len(tag) > 1024 {
return fmt.Errorf("lvm: Tag %q is too long, maximum length is 1024 characters", tag)
}
if !tagRegexp.MatchString(tag) {
return fmt.Errorf("lvm: Tag %q contains invalid character, valid set includes: [A-Za-z0-9_+.-]", tag)
}
return nil
}

// CreateVolumeGroup creates a new volume group.
func CreateVolumeGroup(name string, pvs []string, tags []string) (*VolumeGroup, error) {
var args []string

if err := ValidateVolumeGroupName(name); err != nil {
return nil, err
}

for _, tag := range tags {
if tag != "" {
if err := ValidateTag(tag); err != nil {
return nil, err
}
args = append(args, "--add-tag="+tag)
}
}

args = append(args, name)
for _, pv := range pvs {
args = append(args, pv)
}

if err := run("vgcreate", args...); err != nil {
return nil, err
}

if err := PVScan(""); err != nil {
log.Warnf("error during pvscan: %s", err.Error())
}

if err := VGScan(""); err != nil {
log.Warnf("error during vgscan: %s", err.Error())
}
return &VolumeGroup{name}, nil
}

// ValidateLogicalVolumeName validates a volume group name. A valid volume
// group name can consist of a limited range of characters only. The allowed
// characters are [A-Za-z0-9_+.-].
func ValidateLogicalVolumeName(name string) error {
if !lvNameRegexp.MatchString(name) {
return fmt.Errorf("lvm: Logical volume name %q contains invalid character, valid set includes: [A-Za-z0-9_+.-]", name)
}

return nil
}

// CreateLogicalVolume creates a logical volume of the given device
// and size.
//
// The actual size may be larger than asked for as the smallest
// increment is the size of an extent on the volume group in question.
//
// If sizeInBytes is zero the entire available space is allocated.
//
// Additional optional config items can be specified using CreateLogicalVolumeOpt
func (vg *VolumeGroup) CreateLogicalVolume(name string, sizeInBytes uint64, tags []string, opts []string) error {
if err := ValidateLogicalVolumeName(name); err != nil {
return err
}

// Validate the tag.
var args []string
for _, tag := range tags {
if tag != "" {
if err := ValidateTag(tag); err != nil {
return err
}
args = append(args, "--add-tag="+tag)
}
}
args = append(args, fmt.Sprintf("--size=%db", sizeInBytes))
args = append(args, "--name="+name)
args = append(args, vg.name)
args = append(args, opts...)

if err := run("lvcreate", args...); err != nil {
if isInsufficientSpace(err) {
return fmt.Errorf("lvm: not enough free space")
}
if isInsufficientDevices(err) {
return fmt.Errorf("lvm: not enough underlying devices")
}
return err
}
return nil
}

func run(cmd string, extraArgs ...string) error {
var args []string
args = append(args, extraArgs...)
c := exec.Command(cmd, args...)
c.Stdout, c.Stderr = os.Stdout, os.Stderr

return c.Run()
}

// isInsufficientSpace returns true if the error is due to insufficient space
func isInsufficientSpace(err error) bool {
return strings.Contains(strings.ToLower(err.Error()), "insufficient free space")
}

// isInsufficientDevices returns true if the error is due to insufficient underlying devices
func isInsufficientDevices(err error) bool {
return strings.Contains(err.Error(), "Insufficient suitable allocatable extents for logical volume")
}
29 changes: 29 additions & 0 deletions actions/rootio/v1/pkg/storage/lvm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package storage

import (
"fmt"

"github.com/tinkerbell/hub/actions/rootio/v1/pkg/lvm"
"github.com/tinkerbell/hub/actions/rootio/v1/pkg/types.go"
)

func CreateVolumeGroup(volumeGroup types.VolumeGroup) error {
for _, p := range volumeGroup.PhysicalVolumes {
if err := lvm.CreatePhysicalVolume(p); err != nil {
return fmt.Errorf("failed to create physical volume %s: %v", p, err)
}
}

vg, err := lvm.CreateVolumeGroup(volumeGroup.Name, volumeGroup.PhysicalVolumes, volumeGroup.Tags)
if err != nil {
return fmt.Errorf("failed to create volume group %s: %v", volumeGroup.Name, err)
}

for _, lv := range volumeGroup.LogicalVolumes {
if err := vg.CreateLogicalVolume(lv.Name, lv.Size, lv.Tags, lv.Opts); err != nil {
return fmt.Errorf("failed to create logical volume %s: %v", lv.Name, err)
}
}

return nil
}
21 changes: 19 additions & 2 deletions actions/rootio/v1/pkg/types.go/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ type Instance struct {
Version string `json:"version"`
} `json:"operating_system_version"`
Storage struct {
Disks []Disk `json:"disks"`
Filesystems []Filesystem `json:"filesystems"`
Disks []Disk `json:"disks"`
Filesystems []Filesystem `json:"filesystems"`
VolumeGroups []VolumeGroup `json:"volume_groups"`
} `json:"storage"`
}

Expand Down Expand Up @@ -63,6 +64,22 @@ type Partitions struct {
Size uint64 `json:"size"`
}

// VolumeGroup defines the configuration of a volume group
type VolumeGroup struct {
Name string `json:"name"`
PhysicalVolumes []string `json:"physical_volumes"`
LogicalVolumes []LogicalVolume `json:"logical_volumes"`
Tags []string `json:"tags"`
}

// LogicalVolume defines the configuration of a logical volume.
type LogicalVolume struct {
Name string `json:"name"`
Size uint64 `json:"size"`
Tags []string `json:"tags"`
Opts []string `json:"opts"`
}

// RetrieveData retrieves metadata from Hegel.
func RetrieveData() (*Metadata, error) {
metadataURL := os.Getenv("MIRROR_HOST")
Expand Down

0 comments on commit d5aba0a

Please sign in to comment.