Skip to content

Commit

Permalink
(feat): Add service.DeviceService test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed Dec 28, 2023
1 parent 77b05a5 commit 8b759f2
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 17 deletions.
1 change: 0 additions & 1 deletion internal/model/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

type BlockDevice struct {
Name string
Uuid string
MountPoint string
FileSystem FileSystem
Label string
Expand Down
19 changes: 9 additions & 10 deletions internal/service/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ type LsblkBlockDeviceResponse struct {

type LsblkBlockDevice struct {
Name *string `json:"name"`
Uuid *string `json:"uuid"`
Label *string `json:"label"`
FsType *string `json:"fstype"`
MountPoint *string `json:"mountpoint"`
Expand All @@ -54,14 +53,14 @@ func (du *LinuxDeviceService) GetSize(name string) (uint64, error) {

func (du *LinuxDeviceService) GetBlockDevices() ([]string, error) {
r := du.RunnerFactory.Select(utils.Lsblk)
output, err := r.Command("--nodeps", "-o", "NAME,LABEL,FSTYPE,MOUNTPOINT", "-J")
output, err := r.Command("--nodeps", "-o", "NAME", "-J")
if err != nil {
return nil, err
}
lbd := &LsblkBlockDeviceResponse{}
err = json.Unmarshal([]byte(output), lbd)
if err != nil {
return nil, err
return nil, fmt.Errorf("🔴 Failed to decode lsblk response: %v", err)
}
d := make([]string, len(lbd.BlockDevices))
for i := range d {
Expand All @@ -72,27 +71,27 @@ func (du *LinuxDeviceService) GetBlockDevices() ([]string, error) {

func (du *LinuxDeviceService) GetBlockDevice(name string) (*model.BlockDevice, error) {
r := du.RunnerFactory.Select(utils.Lsblk)
output, err := r.Command("--nodeps", "-o", "NAME,UUID,LABEL,FSTYPE,MOUNTPOINT", "-J", name)
output, err := r.Command("--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", name)
if err != nil {
return nil, err
}
lbd := &LsblkBlockDeviceResponse{}
err = json.Unmarshal([]byte(output), lbd)
if err != nil {
return nil, err
return nil, fmt.Errorf("🔴 Failed to decode lsblk response: %v", err)
}
if len(lbd.BlockDevices) != 1 {
return nil, fmt.Errorf("🔴 %s: An unexpected number of block devices were returned: Expected=1 Actual=%d", name, len(lbd.BlockDevices))
}
fst, err := model.ParseFileSystem(utils.Safe(lbd.BlockDevices[0].FsType))
fst := utils.Safe(lbd.BlockDevices[0].FsType)
fs, err := model.ParseFileSystem(fst)
if err != nil {
return nil, err
return nil, fmt.Errorf("🔴 %s: '%s' is not a supported file system", name, fst)
}
return &model.BlockDevice{
Name: "/dev/" + utils.Safe(lbd.BlockDevices[0].Name),
Uuid: utils.Safe(lbd.BlockDevices[0].Uuid),
Name: name,
Label: utils.Safe(lbd.BlockDevices[0].Label),
FileSystem: fst,
FileSystem: fs,
MountPoint: utils.Safe(lbd.BlockDevices[0].MountPoint),
}, nil
}
Expand Down
325 changes: 325 additions & 0 deletions internal/service/device_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
package service

import (
"fmt"
"testing"

"github.com/reecetech/ebs-bootstrap/internal/model"
"github.com/reecetech/ebs-bootstrap/internal/utils"
)

func TestGetSize(t *testing.T) {
subtests := []struct {
Name string
Device string
RunnerBinary utils.Binary
RunnerArgs []string
RunnerOutput string
RunnerError error
ExpectedOutput uint64
ExpectedError error
}{
{
Name: "blockdev=success + cast=success",
Device: "/dev/vdb",
RunnerBinary: utils.BlockDev,
RunnerArgs: []string{"--getsize64", "/dev/vdb"},
RunnerOutput: "12345",
RunnerError: nil,
ExpectedOutput: 12345,
ExpectedError: nil,
},
{
Name: "blockdev=success + cast=failure",
Device: "/dev/vdc",
RunnerBinary: utils.BlockDev,
RunnerArgs: []string{"--getsize64", "/dev/vdc"},
RunnerOutput: "lsblk: permission denied",
RunnerError: nil,
ExpectedOutput: 0,
ExpectedError: fmt.Errorf("🔴 Failed to cast block device size to unsigned 64-bit integer"),
},
{
Name: "blockdev=error",
Device: "/dev/vdd",
RunnerBinary: utils.BlockDev,
RunnerArgs: []string{"--getsize64", "/dev/vdd"},
RunnerOutput: "",
RunnerError: fmt.Errorf("🔴 blockdev is either not installed or accessible from $PATH"),
ExpectedOutput: 0,
ExpectedError: fmt.Errorf("🔴 blockdev is either not installed or accessible from $PATH"),
},
}
for _, subtest := range subtests {
mrf := utils.NewMockRunnerFactory(subtest.RunnerBinary, subtest.RunnerArgs, subtest.RunnerOutput, subtest.RunnerError)
lds := NewLinuxDeviceService(mrf)
size, err := lds.GetSize(subtest.Device)
utils.CheckError("lds.GetSize()", t, subtest.ExpectedError, err)
utils.CheckOutput("lds.GetSize()", t, subtest.ExpectedOutput, size)
}
}

func TestGetBlockDevices(t *testing.T) {
subtests := []struct {
Name string
RunnerBinary utils.Binary
RunnerArgs []string
RunnerOutput string
RunnerError error
ExpectedOutput []string
ExpectedError error
}{
{
Name: "lsblk=success + json=valid",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "NAME", "-J"},
RunnerOutput: `{"blockdevices": [
{"name":"nvme1n1"},
{"name":"nvme0n1"}
]}`,
RunnerError: nil,
ExpectedOutput: []string{"/dev/nvme1n1", "/dev/nvme0n1"},
ExpectedError: nil,
},
{
Name: "lsblk=success + json=invalid",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "NAME", "-J"},
RunnerOutput: `{"invalid_json"}`,
RunnerError: nil,
ExpectedOutput: nil,
ExpectedError: fmt.Errorf("🔴 Failed to decode lsblk response: *"),
},
{
Name: "lsblk=failure",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "NAME", "-J"},
RunnerOutput: "",
RunnerError: fmt.Errorf("🔴 lsblk: invalid option -- 'J'"),
ExpectedOutput: nil,
ExpectedError: fmt.Errorf("🔴 lsblk: invalid option -- 'J'"),
},
}
for _, subtest := range subtests {
mrf := utils.NewMockRunnerFactory(subtest.RunnerBinary, subtest.RunnerArgs, subtest.RunnerOutput, subtest.RunnerError)
lds := NewLinuxDeviceService(mrf)
size, err := lds.GetBlockDevices()
utils.CheckErrorGlob("lds.GetBlockDevices()", t, subtest.ExpectedError, err)
utils.CheckOutput("lds.GetBlockDevices()", t, subtest.ExpectedOutput, size)
}
}

func TestGetBlockDevice(t *testing.T) {
subtests := []struct {
Name string
Device string
RunnerBinary utils.Binary
RunnerArgs []string
RunnerOutput string
RunnerError error
ExpectedOutput *model.BlockDevice
ExpectedError error
}{
{
Name: "lsblk=success",
Device: "/dev/nvme1n1",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/nvme1n1"},
RunnerOutput: `{"blockdevices": [
{"name":"nvme1n1", "label":"external-vol", "fstype":"xfs", "mountpoint":"/mnt/app"}
]}`,
RunnerError: nil,
ExpectedOutput: &model.BlockDevice{
Name: "/dev/nvme1n1",
Label: "external-vol",
FileSystem: model.Xfs,
MountPoint: "/mnt/app",
},
ExpectedError: nil,
},
{
Name: "lsblk=success + device=unformatted",
Device: "/dev/nvme1n1",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/nvme1n1"},
RunnerOutput: `{"blockdevices": [
{"name":"nvme1n1", "label":null, "fstype":null, "mountpoint":null}
]}`,
RunnerError: nil,
ExpectedOutput: &model.BlockDevice{
Name: "/dev/nvme1n1",
Label: "",
FileSystem: model.Unformatted,
MountPoint: "",
},
ExpectedError: nil,
},
/* Reference [https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html]
---
For example, if /dev/sdb is renamed /dev/xvdf, then /dev/sdc is renamed /dev/xvdg.
Amazon Linux creates a symbolic link for the name you specified to the renamed device.
Other operating systems could behave differently.
--
With this behaviour established on Amazon Linux, it is possible for lsblk to return a
device name that might not reflect the one that was provided to lsblk. To simplify the
output of ebs-bootstrap, lets force the BlockDevice to house the name that was provided
to the lsblk call
*/
{
Name: "lsblk=success + symlink=true",
Device: "/dev/sdb",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/sdb"},
RunnerOutput: `{"blockdevices": [
{"name":"xvdf", "label":"external-vol", "fstype":"xfs", "mountpoint":"/mnt/app"}
]}`,
RunnerError: nil,
ExpectedOutput: &model.BlockDevice{
Name: "/dev/sdb",
Label: "external-vol",
FileSystem: model.Xfs,
MountPoint: "/mnt/app",
},
ExpectedError: nil,
},
{
Name: "lsblk=success + json=invalid",
Device: "/dev/sdb",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/sdb"},
RunnerOutput: `{"invalid_json"}`,
RunnerError: nil,
ExpectedOutput: nil,
ExpectedError: fmt.Errorf("🔴 Failed to decode lsblk response: *"),
},
{
Name: "lsblk=success + filesystem=unsupported",
Device: "/dev/sdb",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/sdb"},
RunnerOutput: `{"blockdevices": [
{"name":"sdb", "label":null, "fstype":"jfs", "mountpoint":"/mnt/app"}
]}`,
RunnerError: nil,
ExpectedOutput: nil,
ExpectedError: fmt.Errorf("🔴 /dev/sdb: 'jfs' is not a supported file system"),
},
/* I haven't encountered a scenario where lsblk successfully returns an empty array
Typically, if it cannot find a specific block device, it would produce an error.
Nevertheless, lets test this scenario...
*/
{
Name: "lsblk=success + len(devices)==0",
Device: "/dev/sdb",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/sdb"},
RunnerOutput: `{"blockdevices": []}`,
RunnerError: nil,
ExpectedOutput: nil,
ExpectedError: fmt.Errorf("🔴 /dev/sdb: An unexpected number of block devices were returned: Expected=1 Actual=0"),
},
{
Name: "lsblk=failure",
Device: "/dev/sdc",
RunnerBinary: utils.Lsblk,
RunnerArgs: []string{"--nodeps", "-o", "LABEL,FSTYPE,MOUNTPOINT", "-J", "/dev/sdc"},
RunnerOutput: "",
RunnerError: fmt.Errorf("🔴 lsblk: /dev/sdc: not a block device"),
ExpectedOutput: nil,
ExpectedError: fmt.Errorf("🔴 lsblk: /dev/sdc: not a block device"),
},
}
for _, subtest := range subtests {
mrf := utils.NewMockRunnerFactory(subtest.RunnerBinary, subtest.RunnerArgs, subtest.RunnerOutput, subtest.RunnerError)
lds := NewLinuxDeviceService(mrf)
size, err := lds.GetBlockDevice(subtest.Device)
utils.CheckErrorGlob("lds.GetBlockDevice()", t, subtest.ExpectedError, err)
utils.CheckOutput("lds.GetBlockDevice()", t, subtest.ExpectedOutput, size)
}
}

func TestMount(t *testing.T) {
subtests := []struct {
Name string
Source string
Target string
Fs model.FileSystem
Options model.MountOptions
RunnerBinary utils.Binary
RunnerArgs []string
RunnerOutput string
RunnerError error
ExpectedError error
}{
{
Name: "mount=success",
Source: "/dev/sdb",
Target: "/mnt/data",
Fs: model.Ext4,
Options: model.MountOptions("defaults"),
RunnerBinary: utils.Mount,
RunnerArgs: []string{"/dev/sdb", "-t", "ext4", "-o", "defaults", "/mnt/data"},
RunnerOutput: "",
RunnerError: nil,
ExpectedError: nil,
},
{
Name: "mount=failure",
Source: "/dev/sdc",
Target: "/mnt/data",
Fs: model.Xfs,
Options: model.MountOptions("defaults"),
RunnerBinary: utils.Mount,
RunnerArgs: []string{"/dev/sdc", "-t", "xfs", "-o", "defaults", "/mnt/data"},
RunnerOutput: "",
RunnerError: fmt.Errorf("🔴 mount: /dev/sdc: special device /mnt/data does not exist"),
ExpectedError: fmt.Errorf("🔴 mount: /dev/sdc: special device /mnt/data does not exist"),
},
}
for _, subtest := range subtests {
mrf := utils.NewMockRunnerFactory(subtest.RunnerBinary, subtest.RunnerArgs, subtest.RunnerOutput, subtest.RunnerError)
lds := NewLinuxDeviceService(mrf)
err := lds.Mount(subtest.Source, subtest.Target, subtest.Fs, subtest.Options)
utils.CheckErrorGlob("lds.Mount()", t, subtest.ExpectedError, err)
}
}

func TestUmount(t *testing.T) {
subtests := []struct {
Name string
Source string
Target string
RunnerBinary utils.Binary
RunnerArgs []string
RunnerOutput string
RunnerError error
ExpectedError error
}{
{
Name: "umount=success",
Source: "/dev/sdb",
Target: "/mnt/data",
RunnerBinary: utils.Umount,
RunnerArgs: []string{"/mnt/data"},
RunnerOutput: "",
RunnerError: nil,
ExpectedError: nil,
},
{
Name: "umount=failure",
Source: "/dev/sdc",
Target: "/mnt/data",
RunnerBinary: utils.Umount,
RunnerArgs: []string{"/mnt/data"},
RunnerOutput: "",
RunnerError: fmt.Errorf("🔴 umount: /mnt/data: not mounted"),
ExpectedError: fmt.Errorf("🔴 umount: /mnt/data: not mounted"),
},
}
for _, subtest := range subtests {
mrf := utils.NewMockRunnerFactory(subtest.RunnerBinary, subtest.RunnerArgs, subtest.RunnerOutput, subtest.RunnerError)
lds := NewLinuxDeviceService(mrf)
err := lds.Umount(subtest.Source, subtest.Target)
utils.CheckErrorGlob("lds.Umount()", t, subtest.ExpectedError, err)
}
}
Loading

0 comments on commit 8b759f2

Please sign in to comment.