diff --git a/block/volume.go b/block/volume.go index 60f39b2..da0e050 100644 --- a/block/volume.go +++ b/block/volume.go @@ -1,6 +1,12 @@ package block import ( + "fmt" + "io" + "os" + "time" + + "github.com/coreos/pkg/progressutil" "github.com/coreos/torus" "github.com/coreos/torus/blockset" "github.com/coreos/torus/models" @@ -32,6 +38,56 @@ func CreateBlockVolume(mds torus.MetadataService, volume string, size uint64) er }) } +func CreateBlockFromSnapshot(srv *torus.Server, origvol, origsnap, newvol string, progress bool) error { + // open original snapshot + srcVol, err := OpenBlockVolume(srv, origvol) + if err != nil { + return fmt.Errorf("couldn't open block volume %s: %v", origvol, err) + } + bfsrc, err := srcVol.OpenSnapshot(origsnap) + if err != nil { + return fmt.Errorf("couldn't open snapshot: %v", err) + } + size := bfsrc.Size() + + // create new volume + err = CreateBlockVolume(srv.MDS, newvol, size) + if err != nil { + return fmt.Errorf("error creating volume %s: %v", newvol, err) + } + blockvolDist, err := OpenBlockVolume(srv, newvol) + if err != nil { + return fmt.Errorf("couldn't open block volume %s: %v", newvol, err) + } + bfdist, err := blockvolDist.OpenBlockFile() + if err != nil { + return fmt.Errorf("couldn't open blockfile %s: %v", newvol, err) + } + defer bfdist.Close() + + if progress { + pb := progressutil.NewCopyProgressPrinter() + pb.AddCopy(bfsrc, newvol, int64(size), bfdist) + err := pb.PrintAndWait(os.Stderr, 500*time.Millisecond, nil) + if err != nil { + return fmt.Errorf("couldn't copy: %v", err) + } + } else { + n, err := io.Copy(bfdist, bfsrc) + if err != nil { + return fmt.Errorf("couldn't copy: %v", err) + } + if n != int64(size) { + return fmt.Errorf("copied size %d doesn't match original size %d", n, size) + } + } + err = bfdist.Sync() + if err != nil { + return fmt.Errorf("couldn't sync: %v", err) + } + return nil +} + func OpenBlockVolume(s *torus.Server, volume string) (*BlockVolume, error) { vol, err := s.MDS.GetVolume(volume) if err != nil { diff --git a/cmd/torusctl/block.go b/cmd/torusctl/block.go index 43b1375..c6daab1 100644 --- a/cmd/torusctl/block.go +++ b/cmd/torusctl/block.go @@ -3,6 +3,7 @@ package main import ( "os" + "github.com/coreos/torus" "github.com/coreos/torus/internal/flagconfig" "github.com/spf13/cobra" ) @@ -20,8 +21,24 @@ var blockCreateCommand = &cobra.Command{ Run: volumeCreateBlockAction, } +var blockCreateFromSnapshotCommand = &cobra.Command{ + Use: "from-snapshot VOLUME@SNAPSHOT_NAME NEW_NAME", + Short: "create a block volume from snapshot", + Long: "creates a block volume named NAME from snapshot", + Run: func(cmd *cobra.Command, args []string) { + err := volumeCreateBlockFromSnapshotAction(cmd, args) + if err == torus.ErrUsage { + cmd.Usage() + os.Exit(1) + } else if err != nil { + die("%v", err) + } + }, +} + func init() { blockCommand.AddCommand(blockCreateCommand) + blockCreateCommand.AddCommand(blockCreateFromSnapshotCommand) flagconfig.AddConfigFlags(blockCommand.PersistentFlags()) } diff --git a/cmd/torusctl/volume.go b/cmd/torusctl/volume.go index a24c734..09a92a5 100644 --- a/cmd/torusctl/volume.go +++ b/cmd/torusctl/volume.go @@ -1,8 +1,10 @@ package main import ( + "fmt" "os" + "github.com/coreos/torus" "github.com/coreos/torus/block" "github.com/dustin/go-humanize" "github.com/spf13/cobra" @@ -33,10 +35,27 @@ var volumeCreateBlockCommand = &cobra.Command{ Run: volumeCreateBlockAction, } +var volumeCreateBlockFromSnapshotCommand = &cobra.Command{ + Use: "from-snapshot VOLUME@SNAPSHOT_NAME NEW_NAME", + Short: "create a block volume from snapshot", + Long: "creates a block volume named NAME from snapshot", + Run: func(cmd *cobra.Command, args []string) { + err := volumeCreateBlockFromSnapshotAction(cmd, args) + if err == torus.ErrUsage { + cmd.Usage() + os.Exit(1) + } else if err != nil { + die("%v", err) + } + }, +} + func init() { volumeCommand.AddCommand(volumeDeleteCommand) volumeCommand.AddCommand(volumeListCommand) volumeCommand.AddCommand(volumeCreateBlockCommand) + volumeCreateBlockCommand.AddCommand(volumeCreateBlockFromSnapshotCommand) + volumeCreateBlockFromSnapshotCommand.Flags().BoolVarP(&progress, "progress", "p", false, "show progress") volumeListCommand.Flags().BoolVarP(&outputAsCSV, "csv", "", false, "output as csv instead") volumeListCommand.Flags().BoolVarP(&outputAsSI, "si", "", false, "output sizes in powers of 1000") } @@ -109,3 +128,21 @@ func volumeCreateBlockAction(cmd *cobra.Command, args []string) { die("error creating volume %s: %v", args[0], err) } } + +func volumeCreateBlockFromSnapshotAction(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return torus.ErrUsage + } + snapshot := args[0] + newVolName := args[1] + + vol := ParseSnapName(snapshot) + if vol.Snapshot == "" { + return fmt.Errorf("can't restore a snapshot without a name, please use the form VOLUME@SNAPSHOT_NAME") + } + + srv := createServer() + defer srv.Close() + + return block.CreateBlockFromSnapshot(srv, vol.Volume, vol.Snapshot, newVolName, progress) +}