github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/vm-control/saveVm.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/Cloud-Foundations/Dominator/lib/errors"
    17  	"github.com/Cloud-Foundations/Dominator/lib/format"
    18  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    19  	"github.com/Cloud-Foundations/Dominator/lib/json"
    20  	"github.com/Cloud-Foundations/Dominator/lib/log"
    21  	"github.com/Cloud-Foundations/Dominator/lib/rsync"
    22  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    23  	proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    24  )
    25  
    26  type directorySaver struct {
    27  	dirname  string
    28  	filename string // If != "", lock all files to this filename.
    29  }
    30  
    31  type gzipCompressor struct{}
    32  
    33  type tarSaver struct {
    34  	closer io.Closer
    35  	header tar.Header
    36  	writer *tar.Writer
    37  }
    38  
    39  type tarWriter struct {
    40  	writer *tar.Writer
    41  }
    42  
    43  type wrappedWriteCloser struct {
    44  	real io.Closer
    45  	wrap io.WriteCloser
    46  }
    47  
    48  type writerMaker interface {
    49  	MakeWriter(w io.WriteCloser) io.WriteCloser
    50  }
    51  
    52  type writeSeekCloser interface {
    53  	io.Closer
    54  	io.WriteSeeker
    55  }
    56  
    57  type vmSaver interface {
    58  	Close() error
    59  	CopyToFile(filename string, reader io.Reader, length uint64) error
    60  	OpenReader(filename string) (io.ReadCloser, uint64, error)
    61  	OpenWriter(filename string, length uint64) (writeSeekCloser, error)
    62  }
    63  
    64  func copyVolumeToVmSaver(saver vmSaver, client *srpc.Client, ipAddr net.IP,
    65  	volIndex uint, size uint64, logger log.DebugLogger) error {
    66  	var filename string
    67  	if volIndex == 0 {
    68  		filename = "root"
    69  	} else {
    70  		filename = fmt.Sprintf("secondary-volume.%d", volIndex-1)
    71  	}
    72  	if reader, initialFileSize, err := saver.OpenReader(filename); err != nil {
    73  		return err
    74  	} else {
    75  		if reader != nil {
    76  			defer reader.Close()
    77  		} else {
    78  			if initialFileSize > size {
    79  				return errors.New("file larger than volume")
    80  			}
    81  		}
    82  		if writer, err := saver.OpenWriter(filename, size); err != nil {
    83  			return err
    84  		} else {
    85  			err := copyVmVolumeToWriter(writer, reader, initialFileSize,
    86  				client, ipAddr, volIndex, size, logger)
    87  			if err != nil {
    88  				writer.Close()
    89  				return err
    90  			}
    91  			return writer.Close()
    92  		}
    93  	}
    94  }
    95  
    96  func copyVmVolumeToWriter(writer io.WriteSeeker, reader io.Reader,
    97  	initialFileSize uint64, client *srpc.Client, ipAddr net.IP, volIndex uint,
    98  	size uint64, logger log.DebugLogger) error {
    99  	request := proto.GetVmVolumeRequest{
   100  		IpAddress:   ipAddr,
   101  		VolumeIndex: volIndex,
   102  	}
   103  	conn, err := client.Call("Hypervisor.GetVmVolume")
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer conn.Close()
   108  	if err := conn.Encode(request); err != nil {
   109  		return fmt.Errorf("error encoding request: %s", err)
   110  	}
   111  	if err := conn.Flush(); err != nil {
   112  		return err
   113  	}
   114  	var response proto.GetVmVolumeResponse
   115  	if err := conn.Decode(&response); err != nil {
   116  		return err
   117  	}
   118  	if err := errors.New(response.Error); err != nil {
   119  		return err
   120  	}
   121  	startTime := time.Now()
   122  	stats, err := rsync.GetBlocks(conn, conn, conn, reader, writer,
   123  		size, initialFileSize)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	duration := time.Since(startTime)
   128  	speed := uint64(float64(stats.NumRead) / duration.Seconds())
   129  	logger.Debugf(0, "sent %s B, received %s/%s B (%.0f * speedup, %s/s)\n",
   130  		format.FormatBytes(stats.NumWritten), format.FormatBytes(stats.NumRead),
   131  		format.FormatBytes(size),
   132  		float64(size)/float64(stats.NumRead+stats.NumWritten),
   133  		format.FormatBytes(speed))
   134  	return nil
   135  }
   136  
   137  func encodeJsonToVmSaver(saver vmSaver, filename string,
   138  	data interface{}) error {
   139  	buffer := &bytes.Buffer{}
   140  	if err := json.WriteWithIndent(buffer, "    ", data); err != nil {
   141  		return err
   142  	}
   143  	return saver.CopyToFile(filename, buffer, uint64(buffer.Len()))
   144  }
   145  
   146  func saveVmSubcommand(args []string, logger log.DebugLogger) error {
   147  	if err := saveVm(args[0], args[1], logger); err != nil {
   148  		return fmt.Errorf("Error saving VM: %s", err)
   149  	}
   150  	return nil
   151  }
   152  
   153  func saveVm(vmHostname, destination string, logger log.DebugLogger) error {
   154  	if vmIP, hypervisor, err := lookupVmAndHypervisor(vmHostname); err != nil {
   155  		return err
   156  	} else {
   157  		return saveVmOnHypervisor(hypervisor, vmIP, destination, logger)
   158  	}
   159  }
   160  
   161  func saveVmOnHypervisor(hypervisor string, ipAddr net.IP, destination string,
   162  	logger log.DebugLogger) error {
   163  	client, err := dialHypervisor(hypervisor)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	defer client.Close()
   168  	vmInfo, err := getVmInfoClient(client, ipAddr)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	u, err := url.Parse(destination)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	var saver vmSaver
   177  	if u.Scheme == "dir" {
   178  		if realSaver, err := newDirectorySaver(u.Path); err != nil {
   179  			return err
   180  		} else {
   181  			saver = realSaver
   182  		}
   183  	} else if u.Scheme == "file" {
   184  		if strings.HasSuffix(u.Path, ".tar") {
   185  			if saver, err = newTarSaver(u.Path, nil); err != nil {
   186  				return err
   187  			}
   188  		} else if strings.HasSuffix(u.Path, ".tar.gz") ||
   189  			strings.HasSuffix(u.Path, ".tgz") {
   190  			if saver, err = newTarSaver(u.Path, gzipCompressor{}); err != nil {
   191  				return err
   192  			}
   193  		} else {
   194  			return fmt.Errorf("unknown extension: %s", u.Path)
   195  		}
   196  	} else {
   197  		return fmt.Errorf("unknown scheme: %s", u.Scheme)
   198  	}
   199  	logger.Debugln(0, "saving metadata")
   200  	if err := encodeJsonToVmSaver(saver, "info.json", vmInfo); err != nil {
   201  		return err
   202  	}
   203  	conn, length, err := callGetVmUserData(client, ipAddr)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	if length > 0 {
   208  		logger.Debugln(0, "saving user data")
   209  		err = saver.CopyToFile("user-data.raw", conn, length)
   210  	}
   211  	conn.Close()
   212  	if err != nil {
   213  		return err
   214  	}
   215  	for index, volume := range vmInfo.Volumes {
   216  		err := copyVolumeToVmSaver(saver, client, ipAddr, uint(index),
   217  			volume.Size, logger)
   218  		if err != nil {
   219  			return err
   220  		}
   221  	}
   222  	return saver.Close()
   223  }
   224  
   225  func newDirectorySaver(dirname string) (*directorySaver, error) {
   226  	if dirname != "" {
   227  		if err := os.MkdirAll(dirname, fsutil.DirPerms); err != nil {
   228  			return nil, err
   229  		}
   230  	}
   231  	return &directorySaver{dirname: dirname}, nil
   232  }
   233  
   234  func (saver *directorySaver) Close() error {
   235  	return nil
   236  }
   237  
   238  func (saver *directorySaver) CopyToFile(filename string, reader io.Reader,
   239  	length uint64) error {
   240  	file, err := os.OpenFile(saver.Filename(filename), os.O_WRONLY|os.O_CREATE,
   241  		fsutil.PrivateFilePerms)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	if _, err := io.CopyN(file, reader, int64(length)); err != nil {
   246  		file.Close()
   247  		return err
   248  	}
   249  	return file.Close()
   250  }
   251  
   252  func (saver *directorySaver) Filename(filename string) string {
   253  	if saver.filename != "" {
   254  		return saver.filename
   255  	}
   256  	return filepath.Join(saver.dirname, filename)
   257  }
   258  
   259  func (saver *directorySaver) OpenReader(filename string) (
   260  	io.ReadCloser, uint64, error) {
   261  	file, err := os.OpenFile(saver.Filename(filename), os.O_RDONLY, 0)
   262  	if err != nil {
   263  		if os.IsNotExist(err) {
   264  			return nil, 0, nil
   265  		}
   266  		return nil, 0, err
   267  	} else if fi, err := file.Stat(); err != nil {
   268  		file.Close()
   269  		return nil, 0, err
   270  	} else {
   271  		return file, uint64(fi.Size()), nil
   272  	}
   273  }
   274  
   275  func (saver *directorySaver) OpenWriter(filename string, length uint64) (
   276  	writeSeekCloser, error) {
   277  	return os.OpenFile(saver.Filename(filename),
   278  		os.O_WRONLY|os.O_CREATE, fsutil.PrivateFilePerms)
   279  }
   280  
   281  func (gzipCompressor) MakeWriter(w io.WriteCloser) io.WriteCloser {
   282  	return &wrappedWriteCloser{real: w, wrap: gzip.NewWriter(w)}
   283  }
   284  
   285  func newTarSaver(filename string, compressor writerMaker) (*tarSaver, error) {
   286  	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE,
   287  		fsutil.PrivateFilePerms)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	writeCloser := io.WriteCloser(file)
   292  	if compressor != nil {
   293  		writeCloser = compressor.MakeWriter(writeCloser)
   294  	}
   295  	return &tarSaver{
   296  		closer: writeCloser,
   297  		header: tar.Header{
   298  			Uid:    os.Getuid(),
   299  			Gid:    os.Getgid(),
   300  			Format: tar.FormatPAX,
   301  		},
   302  		writer: tar.NewWriter(writeCloser),
   303  	}, nil
   304  }
   305  
   306  func (saver *tarSaver) Close() error {
   307  	if err := saver.writer.Close(); err != nil {
   308  		saver.closer.Close()
   309  		return err
   310  	}
   311  	return saver.closer.Close()
   312  }
   313  
   314  func (saver *tarSaver) CopyToFile(filename string, reader io.Reader,
   315  	length uint64) error {
   316  	if writer, err := saver.OpenWriter(filename, length); err != nil {
   317  		return err
   318  	} else if _, err := io.CopyN(writer, reader, int64(length)); err != nil {
   319  		writer.Close()
   320  		return err
   321  	} else {
   322  		return writer.Close()
   323  	}
   324  }
   325  
   326  func (saver *tarSaver) OpenReader(filename string) (
   327  	io.ReadCloser, uint64, error) {
   328  	return nil, 0, nil
   329  }
   330  
   331  func (saver *tarSaver) OpenWriter(filename string, length uint64) (
   332  	writeSeekCloser, error) {
   333  	header := saver.header
   334  	header.Typeflag = tar.TypeReg
   335  	header.Name = filename
   336  	header.Size = int64(length)
   337  	header.Mode = 0400
   338  	header.ModTime = time.Now()
   339  	header.AccessTime = header.ModTime
   340  	header.ChangeTime = header.ModTime
   341  	if err := saver.writer.WriteHeader(&header); err != nil {
   342  		return nil, err
   343  	}
   344  	return &tarWriter{saver.writer}, nil
   345  }
   346  
   347  func (w tarWriter) Close() error {
   348  	return w.writer.Flush()
   349  }
   350  
   351  func (w tarWriter) Seek(offset int64, whence int) (int64, error) {
   352  	if offset == 0 && whence == io.SeekStart {
   353  		return 0, nil
   354  	}
   355  	if offset == 0 && whence == io.SeekCurrent {
   356  		return 0, nil
   357  	}
   358  	return 0, errors.New("cannot seek")
   359  }
   360  
   361  func (w tarWriter) Write(p []byte) (n int, err error) {
   362  	return w.writer.Write(p)
   363  }
   364  
   365  func (w *wrappedWriteCloser) Close() error {
   366  	if err := w.wrap.Close(); err != nil {
   367  		w.real.Close()
   368  		return err
   369  	}
   370  	return w.real.Close()
   371  }
   372  
   373  func (w *wrappedWriteCloser) Write(p []byte) (n int, err error) {
   374  	return w.wrap.Write(p)
   375  }