diff --git a/app/cmd/dmsetup/dmsetup.go b/app/cmd/dmsetup/dmsetup.go new file mode 100644 index 0000000..53e39c9 --- /dev/null +++ b/app/cmd/dmsetup/dmsetup.go @@ -0,0 +1,245 @@ +package dmsetup + +import ( + "fmt" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + + commonTypes "github.com/longhorn/go-common-libs/types" + "github.com/longhorn/go-spdk-helper/pkg/util" +) + +func Cmd() cli.Command { + return cli.Command{ + Name: "dmsetup", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "host-proc", + Usage: fmt.Sprintf("The host proc path of namespace executor. By default %v", commonTypes.ProcDirectory), + Value: commonTypes.ProcDirectory, + }, + }, + Subcommands: []cli.Command{ + CreateCmd(), + RemoveCmd(), + SuspendCmd(), + ResumeCmd(), + ReloadCmd(), + DepsCmd(), + }, + } +} + +func CreateCmd() cli.Command { + return cli.Command{ + Name: "create", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "table", + Usage: "One-line table directly on the command line.", + Required: true, + }, + }, + Usage: "Create a device mapper device with the given name and table: create --table ", + Action: func(c *cli.Context) { + if err := create(c); err != nil { + logrus.WithError(err).Fatalf("Failed to create device %v with table %v", c.Args().First(), c.String("table")) + } + }, + } +} + +func create(c *cli.Context) error { + executor, err := util.NewExecutor(c.GlobalString("host-proc")) + if err != nil { + return err + } + + deviceName := c.Args().First() + if deviceName == "" { + return fmt.Errorf("device name is required") + } + + logrus.Infof("Creating device %v with table %v", deviceName, c.String("table")) + + return util.DmsetupCreate(deviceName, c.String("table"), executor) +} + +func SuspendCmd() cli.Command { + return cli.Command{ + Name: "suspend", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "noflush", + Usage: "Do not flush outstanding I/O when suspending a device. Default: false.", + Required: false, + }, + cli.BoolFlag{ + Name: "nolockfs", + Usage: "Do not attempt to synchronize filesystem. Default: false.", + Required: false, + }, + }, + Usage: "Suspend the device mapper device with the given name: suspend --noflush --nolockfs ", + Action: func(c *cli.Context) { + if err := suspend(c); err != nil { + logrus.WithError(err).Fatalf("Failed to suspend device %v", c.Args().First()) + } + }, + } +} + +func suspend(c *cli.Context) error { + executor, err := util.NewExecutor(c.GlobalString("host-proc")) + if err != nil { + return err + } + + deviceName := c.Args().First() + if deviceName == "" { + return fmt.Errorf("device name is required") + } + + logrus.Infof("Suspending device %v with noflush %v and nolockfs %v", deviceName, c.Bool("noflush"), c.Bool("nolockfs")) + + return util.DmsetupSuspend(deviceName, c.Bool("noflush"), c.Bool("nolockfs"), executor) +} + +func ResumeCmd() cli.Command { + return cli.Command{ + Name: "resume", + Flags: []cli.Flag{}, + Usage: "Resume the device mapper device with the given name: resume ", + Action: func(c *cli.Context) { + if err := resume(c); err != nil { + logrus.WithError(err).Fatalf("Failed to resume device %v", c.Args().First()) + } + }, + } +} + +func resume(c *cli.Context) error { + executor, err := util.NewExecutor(c.GlobalString("host-proc")) + if err != nil { + return err + } + + deviceName := c.Args().First() + if deviceName == "" { + return fmt.Errorf("device name is required") + } + + logrus.Infof("Resuming device %v", deviceName) + + return util.DmsetupResume(deviceName, executor) +} + +func ReloadCmd() cli.Command { + return cli.Command{ + Name: "reload", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "table", + Usage: "One-line table directly on the command line.", + Required: true, + }, + }, + Usage: "Reload the table of the device mapper device with the given name and table: reload --table ", + Action: func(c *cli.Context) { + if err := reload(c); err != nil { + logrus.WithError(err).Fatalf("Failed to reload device %v with table %v", c.Args().First(), c.String("table")) + } + }, + } +} + +func reload(c *cli.Context) error { + executor, err := util.NewExecutor(c.GlobalString("host-proc")) + if err != nil { + return err + } + + deviceName := c.Args().First() + if deviceName == "" { + return fmt.Errorf("device name is required") + } + + logrus.Infof("Reloading device %v with table %v", deviceName, c.String("table")) + + return util.DmsetupReload(deviceName, c.String("table"), executor) +} + +func RemoveCmd() cli.Command { + return cli.Command{ + Name: "remove", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "force", + Usage: "Try harder to complete operation. Default: false.", + Required: false, + }, + cli.BoolFlag{ + Name: "deferred", + Usage: "Enable deferred removal of open devices. The device will be removed when the last user closes it. Default: false.", + Required: false, + }, + }, + Usage: "Remove the device mapper device with the given name: remove --force --deferred ", + Action: func(c *cli.Context) { + if err := remove(c); err != nil { + logrus.WithError(err).Fatalf("Failed to create device %v with table %v", c.Args().First(), c.String("table")) + } + }, + } +} + +func remove(c *cli.Context) error { + executor, err := util.NewExecutor(c.GlobalString("host-proc")) + if err != nil { + return err + } + + deviceName := c.Args().First() + if deviceName == "" { + return fmt.Errorf("device name is required") + } + + logrus.Infof("Removing device %v with force %v and deferred %v", deviceName, c.Bool("force"), c.Bool("deferred")) + + return util.DmsetupRemove(deviceName, c.Bool("force"), c.Bool("deferred"), executor) +} + +func DepsCmd() cli.Command { + return cli.Command{ + Name: "deps", + Flags: []cli.Flag{}, + Usage: "Outputting a list of devices referenced by the live table for the specified device: deps ", + Action: func(c *cli.Context) { + if err := deps(c); err != nil { + logrus.WithError(err).Fatalf("Failed to output a list of devices referenced by the live table for the specified device %v", c.Args().First()) + } + }, + } +} + +func deps(c *cli.Context) error { + executor, err := util.NewExecutor(c.GlobalString("host-proc")) + if err != nil { + return err + } + + deviceName := c.Args().First() + if deviceName == "" { + return fmt.Errorf("device name is required") + } + + logrus.Infof("Outputting a list of devices referenced by the live table for the specified device %v", deviceName) + + output, err := util.DmsetupDeps(deviceName, executor) + if err != nil { + return err + } + fmt.Printf("Dependent devices: %v", output) + return nil +} diff --git a/main.go b/main.go index 7cfe5d6..a1e90ec 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/longhorn/go-spdk-helper/app/cmd/advanced" "github.com/longhorn/go-spdk-helper/app/cmd/basic" + "github.com/longhorn/go-spdk-helper/app/cmd/dmsetup" "github.com/longhorn/go-spdk-helper/app/cmd/nvmecli" "github.com/longhorn/go-spdk-helper/app/cmd/spdksetup" "github.com/longhorn/go-spdk-helper/app/cmd/spdktgt" @@ -43,6 +44,8 @@ func main() { nvmecli.Cmd(), + dmsetup.Cmd(), + spdktgt.Cmd(), spdksetup.Cmd(), } diff --git a/pkg/util/dmsetup.go b/pkg/util/dmsetup.go index c761ca0..591012c 100644 --- a/pkg/util/dmsetup.go +++ b/pkg/util/dmsetup.go @@ -1,7 +1,9 @@ package util import ( + "fmt" "regexp" + "strings" commonNs "github.com/longhorn/go-common-libs/ns" @@ -98,3 +100,76 @@ func parseDependentDevicesFromString(str string) []string { return devices } + +type DeviceInfo struct { + Name string + BlockDeviceName string + TableLive bool + TableInactive bool + Suspended bool + ReadOnly bool + Major uint32 + Minor uint32 + OpenCount uint32 // Open reference count + TargetCount uint32 // Number of targets in the live table + EventNumber uint32 // Last event sequence number (used by wait) +} + +// DmsetupInfo returns the information of the device mapper device with the given name +func DmsetupInfo(dmDeviceName string, executor *commonNs.Executor) ([]*DeviceInfo, error) { + opts := []string{ + "info", + "--columns", + "--noheadings", + "-o", + "name,blkdevname,attr,major,minor,open,segments,events", + "--separator", + " ", + dmDeviceName, + } + + outputStr, err := executor.Execute(nil, dmsetupBinary, opts, types.ExecuteTimeout) + if err != nil { + return nil, err + } + + var ( + lines = strings.Split(outputStr, "\n") + devices = []*DeviceInfo{} + ) + + for _, line := range lines { + var ( + attr = "" + info = &DeviceInfo{} + ) + + // Break, if the line is empty or EOF + if line == "" { + break + } + + _, err := fmt.Sscan(line, + &info.Name, + &info.BlockDeviceName, + &attr, + &info.Major, + &info.Minor, + &info.OpenCount, + &info.TargetCount, + &info.EventNumber) + if err != nil { + continue + } + + // Parse attributes (see "man 8 dmsetup" for details) + info.Suspended = strings.Contains(attr, "s") + info.ReadOnly = strings.Contains(attr, "r") + info.TableLive = strings.Contains(attr, "L") + info.TableInactive = strings.Contains(attr, "I") + + devices = append(devices, info) + } + + return devices, nil +}