Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: vgchange and pointer returns #10

Merged
merged 1 commit into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ const (
ArgsTypePVs ArgsType = "pvs"
ArgsTypeVGs ArgsType = "vgs"
ArgsTypeLVCreate ArgsType = "lvcreate"
ArgsTypeLVChange ArgsType = "lvchange"
ArgsTypeVGCreate ArgsType = "vgcreate"
ArgsTypeVGChange ArgsType = "vgchange"
ArgsTypeLVRename ArgsType = "lvrename"
)

Expand Down
31 changes: 28 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package lvm2go

import (
"context"
"errors"
)

var (
ErrVolumeGroupNotFound = errors.New("volume group not found")
ErrLogicalVolumeNotFound = errors.New("logical volume not found")
)

type client struct{}
Expand Down Expand Up @@ -41,14 +47,23 @@ type MetaClient interface {

// VolumeGroupClient is a client that provides operations on lvm2 volume groups.
type VolumeGroupClient interface {
// VG returns a volume group that matches the given options.
//
// If no VolumeGroupName is defined, ErrVolumeGroupNameRequired is returned.
// If no volume group is found, ErrVolumeGroupNotFound is returned.
//
// It is equivalent to calling VGs with the same options and returning the first volume group in the list.
// see VGs for more information.
VG(ctx context.Context, opts ...VGsOption) (*VolumeGroup, error)

// VGs return a list of volume groups that match the given options.
//
// If no volume groups are found, an empty slice is returned.
// If options limit the number of volume groups returned,
// the slice may be shorter than the total number of volume groups.
//
// See man lvm vgs for more information.
VGs(ctx context.Context, opts ...VGsOption) ([]VolumeGroup, error)
VGs(ctx context.Context, opts ...VGsOption) ([]*VolumeGroup, error)

// VGCreate creates a new volume group with the given options.
//
Expand Down Expand Up @@ -83,14 +98,24 @@ type VolumeGroupClient interface {

// LogicalVolumeClient is a client that provides operations on lvm2 logical volumes.
type LogicalVolumeClient interface {
// LV returns a logical volume that matches the given options.
//
// If no LogicalVolumeName is defined, ErrLogicalVolumeNameRequired is returned.
// If no VolumeGroupName is defined, ErrVolumeGroupNameRequired is returned.
// If no logical volume is found in the volume group, ErrLogicalVolumeNotFound is returned.
//
// It is equivalent to calling LVs with the same options and returning the first logical volume in the list.
// see LVs for more information.
LV(ctx context.Context, opts ...LVsOption) (*LogicalVolume, error)

// LVs return a list of logical volumes that match the given options.
//
// If no logical volumes are found, an empty slice is returned.
// If options limit the number of volume groups returned,
// the slice may be shorter than the total number of logical volumes.
//
// See man lvm lvs for more information.
LVs(ctx context.Context, opts ...LVsOption) ([]LogicalVolume, error)
LVs(ctx context.Context, opts ...LVsOption) ([]*LogicalVolume, error)

// LVCreate creates a new logical volume with the given options.
//
Expand Down Expand Up @@ -137,7 +162,7 @@ type PhysicalVolumeClient interface {
// the slice may be shorter than the total number of physical volumes.
//
// See man lvm pvs for more information.
PVs(ctx context.Context, opts ...PVsOption) ([]PhysicalVolume, error)
PVs(ctx context.Context, opts ...PVsOption) ([]*PhysicalVolume, error)

// PVCreate creates a new physical volume with the given options.
//
Expand Down
10 changes: 1 addition & 9 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ func TestLVs(t *testing.T) {
t.Fatal(err)
}

if len(lvs) != len(tc.Volumes) {
t.Fatalf("Expected %d logical volumes, got %d", len(tc.Volumes), len(lvs))
}

for _, expected := range infra.lvs {
found := false
for _, lv := range lvs {
Expand All @@ -97,14 +93,10 @@ func TestLVs(t *testing.T) {
}
}

vgs, err := clnt.VGs(ctx, infra.volumeGroup.Name)
vg, err := clnt.VG(ctx, infra.volumeGroup.Name)
if err != nil {
t.Fatal(err)
}
if len(vgs) != 1 {
t.Fatalf("Expected 1 volume group, got %d", len(vgs))
}
vg := vgs[0]

if vg.Name != infra.volumeGroup.Name {
t.Fatalf("Expected volume group %s, got %s", infra.volumeGroup.Name, vg.Name)
Expand Down
1 change: 1 addition & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

func Test_RawConfig(t *testing.T) {
t.Parallel()
FailTestIfNotRoot(t)
slog.SetDefault(slog.New(NewContextPropagatingSlogHandler(NewTestingHandler(t))))
slog.SetLogLoggerLevel(slog.LevelDebug)
Expand Down
4 changes: 3 additions & 1 deletion json_convert_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ func unmarshalAndConvertToStrings(raw map[string]json.RawMessage, key string, fi
return err
}

*fieldPtr = strings.Split(str, ",")
if len(str) > 0 {
*fieldPtr = strings.Split(str, ",")
}

return nil
}
Expand Down
20 changes: 18 additions & 2 deletions logical_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type LogicalVolume struct {
Major int64 `json:"lv_kernel_major"`
Minor int64 `json:"lv_kernel_minor"`

Tags string `json:"lv_tags"`
Tags Tags `json:"lv_tags"`
Attr LVAttributes `json:"lv_attr"`
Size Size `json:"lv_size"`

Expand All @@ -43,7 +43,6 @@ func (lv *LogicalVolume) UnmarshalJSON(data []byte) error {
"lv_name": (*string)(&lv.Name),
"lv_full_name": &lv.FullName,
"lv_path": &lv.Path,
"lv_tags": &lv.Tags,
"origin": &lv.Origin,
"pool_lv": &lv.PoolLogicalVolume,
"vg_name": (*string)(&lv.VolumeGroupName),
Expand All @@ -55,6 +54,14 @@ func (lv *LogicalVolume) UnmarshalJSON(data []byte) error {
}
}

for key, fieldPtr := range map[string]*Tags{
"lv_tags": &lv.Tags,
} {
if err := unmarshalAndConvertToStrings(raw, key, (*[]string)(fieldPtr)); err != nil {
return err
}
}

for key, fieldPtr := range map[string]*int64{
"lv_kernel_major": &lv.Major,
"lv_kernel_minor": &lv.Minor,
Expand Down Expand Up @@ -119,6 +126,10 @@ func (opt LogicalVolumeName) ApplyToLVChangeOptions(opts *LVChangeOptions) {
opts.LogicalVolumeName = opt
}

func (opt LogicalVolumeName) ApplyToLVsOptions(opts *LVsOptions) {
opts.LogicalVolumeName = opt
}

type FQLogicalVolumeName struct {
VolumeGroupName
LogicalVolumeName
Expand Down Expand Up @@ -147,11 +158,16 @@ func (opt *FQLogicalVolumeName) ApplyToLVResizeOptions(opts *LVResizeOptions) {
func (opt *FQLogicalVolumeName) ApplyToLVReduceOptions(opts *LVReduceOptions) {
opts.VolumeGroupName, opts.LogicalVolumeName = opt.VolumeGroupName, opt.LogicalVolumeName
}

func (opt *FQLogicalVolumeName) ApplyToLVRenameOptions(opts *LVRenameOptions) {
opts.VolumeGroupName = opt.VolumeGroupName
opts.SetOldOrNew(opt.LogicalVolumeName)
}

func (opt *FQLogicalVolumeName) ApplyToLVsOptions(opts *LVsOptions) {
opts.VolumeGroupName, opts.LogicalVolumeName = opt.VolumeGroupName, opt.LogicalVolumeName
}

func (opt *FQLogicalVolumeName) Split() (VolumeGroupName, LogicalVolumeName) {
return opt.VolumeGroupName, opt.LogicalVolumeName
}
Expand Down
1 change: 1 addition & 0 deletions lv_attr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

func TestLVAttributes(t *testing.T) {
t.Parallel()
type args struct {
raw string
}
Expand Down
2 changes: 1 addition & 1 deletion lvchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (c *client) LVChange(ctx context.Context, opts ...LVChangeOption) error {
}

func (list LVChangeOptionsList) AsArgs() (Arguments, error) {
args := NewArgs(ArgsTypeGeneric)
args := NewArgs(ArgsTypeLVChange)
options := LVChangeOptions{}
for _, opt := range list {
opt.ApplyToLVChangeOptions(&options)
Expand Down
1 change: 1 addition & 0 deletions lvextend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

func TestLVExtend(t *testing.T) {
t.Parallel()
FailTestIfNotRoot(t)

clnt := NewClient()
Expand Down
1 change: 1 addition & 0 deletions lvmdevices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
)

func TestLVMDevices(t *testing.T) {
t.Parallel()
FailTestIfNotRoot(t)

_, err := exec.LookPath("lvmdevices")
Expand Down
1 change: 1 addition & 0 deletions lvrename_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

func TestLVRename(t *testing.T) {
t.Parallel()
FailTestIfNotRoot(t)

clnt := NewClient()
Expand Down
38 changes: 36 additions & 2 deletions lvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
type (
LVsOptions struct {
VolumeGroupName
LogicalVolumeName
Tags
Select

Expand All @@ -27,10 +28,10 @@ var (
// LVs returns a list of logical volumes that match the given options.
// If no logical volumes are found, nil is returned.
// It is really just a wrapper around the `lvs --reportformat json` command.
func (c *client) LVs(ctx context.Context, opts ...LVsOption) ([]LogicalVolume, error) {
func (c *client) LVs(ctx context.Context, opts ...LVsOption) ([]*LogicalVolume, error) {
type lvReport struct {
Report []struct {
LV []LogicalVolume `json:"lv"`
LV []*LogicalVolume `json:"lv"`
} `json:"report"`
}

Expand Down Expand Up @@ -67,6 +68,39 @@ func (c *client) LVs(ctx context.Context, opts ...LVsOption) ([]LogicalVolume, e
return lvs, nil
}

func (c *client) LV(ctx context.Context, opts ...LVsOption) (*LogicalVolume, error) {
foundVG := false
foundLV := false
for _, opt := range opts {
if _, ok := opt.(VolumeGroupName); ok {
foundVG = true
}
if _, ok := opt.(LogicalVolumeName); ok {
foundLV = true
}
if foundVG && foundLV {
break
}
}
if !foundVG {
return nil, ErrVolumeGroupNameRequired
}
if !foundLV {
return nil, ErrLogicalVolumeNameRequired
}

lvs, err := c.LVs(ctx, opts...)
if err != nil {
return nil, err
}

if len(lvs) == 0 {
return nil, ErrLogicalVolumeNotFound
}

return lvs[0], nil
}

func (opts *LVsOptions) ApplyToArgs(args Arguments) error {
for _, arg := range []Argument{
opts.VolumeGroupName,
Expand Down
3 changes: 3 additions & 0 deletions physical_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package lvm2go

import (
"encoding/json"
"errors"
)

var ErrPhysicalVolumeNameRequired = errors.New("PhysicalVolumeName is required for a fully qualified physical volume")

type PhysicalVolume struct {
UUID string `json:"pv_uuid"`
Name PhysicalVolumeName `json:"pv_name"`
Expand Down
22 changes: 11 additions & 11 deletions pvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ var (
// PVs returns a list of logical volumes that match the given options.
// If no logical volumes are found, nil is returned.
// It is really just a wrapper around the `lvs --reportformat json` command.
func (c *client) PVs(ctx context.Context, opts ...PVsOption) ([]PhysicalVolume, error) {
func (c *client) PVs(ctx context.Context, opts ...PVsOption) ([]*PhysicalVolume, error) {
type lvReport struct {
Report []struct {
PV []PhysicalVolume `json:"pv"`
PV []*PhysicalVolume `json:"pv"`
} `json:"report"`
}

Expand Down Expand Up @@ -68,16 +68,16 @@ func (c *client) PVs(ctx context.Context, opts ...PVsOption) ([]PhysicalVolume,
}

func (opts *PVsOptions) ApplyToArgs(args Arguments) error {
if err := opts.VolumeGroupName.ApplyToArgs(args); err != nil {
return err
}

if err := opts.CommonOptions.ApplyToArgs(args); err != nil {
return err
}

if err := opts.ColumnOptions.ApplyToArgs(args); err != nil {
return err
for _, arg := range []Argument{
opts.VolumeGroupName,
opts.Tags,
opts.CommonOptions,
opts.ColumnOptions,
} {
if err := arg.ApplyToArgs(args); err != nil {
return err
}
}

return nil
Expand Down
1 change: 1 addition & 0 deletions size_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func init() {
}

func Test_Size(t *testing.T) {
t.Parallel()
for _, tc := range DefaultSizeTestCases {
t.Run(tc.InputToParse, func(t *testing.T) {
actual, err := ParseSize(tc.InputToParse)
Expand Down
7 changes: 7 additions & 0 deletions tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ func (opt Tags) ApplyToLVCreateOptions(opts *LVCreateOptions) {
func (opt Tags) ApplyToVGRemoveOptions(opts *VGRemoveOptions) {
opts.Tags = opt
}
func (opt Tags) ApplyToVGChangeOptions(opts *VGChangeOptions) {
opts.Tags = opt
}
func (opt Tags) ApplyToLVRemoveOptions(opts *LVRemoveOptions) {
opts.Tags = opt
}
Expand All @@ -29,6 +32,10 @@ func (opt Tags) ApplyToArgs(args Arguments) error {
}

switch args.GetType() {
case ArgsTypeLVChange:
fallthrough
case ArgsTypeVGChange:
fallthrough
case ArgsTypeVGCreate:
fallthrough
case ArgsTypeLVCreate:
Expand Down
8 changes: 7 additions & 1 deletion util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,15 @@ func (t LoopbackDevices) PhysicalVolumeNames() PhysicalVolumeNames {

}

// testLoopbackCreationSync is a mutex to synchronize the creation of loopback devices in tests
// so that they don't interfere with each other by requesting the same free loopback device
var testLoopbackCreationSync = sync.Mutex{}

func MakeTestLoopbackDevice(t *testing.T, size Size) LoopbackDevice {
t.Helper()
ctx := context.Background()
testLoopbackCreationSync.Lock()
defer testLoopbackCreationSync.Unlock()

backingFilePath := filepath.Join(t.TempDir(), fmt.Sprintf("%s.img", NewNonDeterministicTestID(t)))

Expand Down Expand Up @@ -132,7 +139,6 @@ func MakeTestLoopbackDevice(t *testing.T, size Size) LoopbackDevice {
}
logger = logger.With("loop", loop)
logger.DebugContext(ctx, "created test loopback device successfully")

t.Cleanup(func() {
logger.DebugContext(ctx, "cleaning up test loopback device")
if err := loop.Close(); err != nil {
Expand Down
Loading
Loading