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

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	hyperclient "github.com/Cloud-Foundations/Dominator/hypervisor/client"
    17  	"github.com/Cloud-Foundations/Dominator/lib/errors"
    18  	"github.com/Cloud-Foundations/Dominator/lib/format"
    19  	"github.com/Cloud-Foundations/Dominator/lib/log"
    20  	proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    21  )
    22  
    23  type directoryRestorer struct {
    24  	dirname string
    25  }
    26  
    27  type gzipDecompressor struct{}
    28  
    29  type tarRestorer struct {
    30  	closer     io.Closer
    31  	nextHeader *tar.Header
    32  	reader     *tar.Reader
    33  }
    34  
    35  type tarReader struct {
    36  	reader *tar.Reader
    37  }
    38  
    39  type readerMaker interface {
    40  	MakeReader(w io.ReadCloser) io.ReadCloser
    41  }
    42  
    43  type vmRestorer interface {
    44  	Close() error
    45  	OpenReader(filename string) (io.ReadCloser, uint64, error)
    46  }
    47  
    48  func copyVolumeFromVmRestorer(writer io.Writer, restorer vmRestorer,
    49  	volIndex uint, size uint64, logger log.DebugLogger) error {
    50  	var filename string
    51  	if volIndex == 0 {
    52  		filename = "root"
    53  	} else {
    54  		filename = fmt.Sprintf("secondary-volume.%d", volIndex-1)
    55  	}
    56  	logger.Debugf(0, "uploading %s\n", filename)
    57  	if reader, size, err := restorer.OpenReader(filename); err != nil {
    58  		return err
    59  	} else {
    60  		defer reader.Close()
    61  		startTime := time.Now()
    62  		if _, err := io.CopyN(writer, reader, int64(size)); err != nil {
    63  			return err
    64  		}
    65  		duration := time.Since(startTime)
    66  		speed := uint64(float64(size) / duration.Seconds())
    67  		logger.Debugf(0, "sent %s (%s/s)\n",
    68  			format.FormatBytes(size), format.FormatBytes(speed))
    69  		return nil
    70  	}
    71  }
    72  
    73  func decodeJsonFromVmRestorer(restorer vmRestorer, filename string,
    74  	data interface{}) error {
    75  	if reader, size, err := restorer.OpenReader(filename); err != nil {
    76  		return err
    77  	} else {
    78  		defer reader.Close()
    79  		return json.NewDecoder(
    80  			&io.LimitedReader{R: reader, N: int64(size)}).Decode(data)
    81  	}
    82  }
    83  
    84  func readFromVmRestorer(restorer vmRestorer, filename string) ([]byte, error) {
    85  	if reader, size, err := restorer.OpenReader(filename); err != nil {
    86  		return nil, err
    87  	} else {
    88  		defer reader.Close()
    89  		data := make([]byte, size)
    90  		if _, err := io.ReadAtLeast(reader, data, int(size)); err != nil {
    91  			return nil, err
    92  		}
    93  		return data, nil
    94  	}
    95  }
    96  
    97  func restoreVmSubcommand(args []string, logger log.DebugLogger) error {
    98  	if err := restoreVm(args[0], logger); err != nil {
    99  		return fmt.Errorf("Error restoring VM: %s", err)
   100  	}
   101  	return nil
   102  }
   103  
   104  func restoreVm(source string, logger log.DebugLogger) error {
   105  	if hypervisor, err := getHypervisorAddress(); err != nil {
   106  		return err
   107  	} else {
   108  		logger.Debugf(0, "restoring VM on %s\n", hypervisor)
   109  		return restoreVmOnHypervisor(hypervisor, source, logger)
   110  	}
   111  }
   112  
   113  func restoreVmOnHypervisor(hypervisor, source string,
   114  	logger log.DebugLogger) error {
   115  	client, err := dialHypervisor(hypervisor)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer client.Close()
   120  	u, err := url.Parse(source)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	var restorer vmRestorer
   125  	if u.Scheme == "dir" {
   126  		if restorer, err = newDirectoryRestorer(u.Path); err != nil {
   127  			return err
   128  		}
   129  	} else if u.Scheme == "file" {
   130  		if strings.HasSuffix(u.Path, ".tar") {
   131  			if restorer, err = newTarRestorer(u.Path, nil); err != nil {
   132  				return err
   133  			}
   134  		} else if strings.HasSuffix(u.Path, ".tar.gz") ||
   135  			strings.HasSuffix(u.Path, ".tgz") {
   136  			restorer, err = newTarRestorer(u.Path, gzipDecompressor{})
   137  			if err != nil {
   138  				return err
   139  			}
   140  		} else {
   141  			return fmt.Errorf("unknown extension: %s", u.Path)
   142  		}
   143  	} else {
   144  		return fmt.Errorf("unknown scheme: %s", u.Scheme)
   145  	}
   146  	defer restorer.Close()
   147  	logger.Debugln(0, "reading metadata")
   148  	var vmInfo proto.VmInfo
   149  	err = decodeJsonFromVmRestorer(restorer, "info.json", &vmInfo)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	vmInfo.ImageName = ""
   154  	vmInfo.ImageURL = ""
   155  	userData, err := readFromVmRestorer(restorer, "user-data.raw")
   156  	if err != nil {
   157  		if !os.IsNotExist(err) {
   158  			return err
   159  		}
   160  	}
   161  	request := proto.CreateVmRequest{
   162  		DhcpTimeout:          *dhcpTimeout,
   163  		ImageDataSize:        vmInfo.Volumes[0].Size,
   164  		SecondaryVolumes:     vmInfo.Volumes[1:],
   165  		SecondaryVolumesData: true,
   166  		UserDataSize:         uint64(len(userData)),
   167  		VmInfo:               vmInfo,
   168  	}
   169  	conn, err := client.Call("Hypervisor.CreateVm")
   170  	if err != nil {
   171  		return fmt.Errorf("error calling Hypervisor.CreateVm: %s", err)
   172  	}
   173  	doCloseConn := true
   174  	defer func() {
   175  		if doCloseConn {
   176  			conn.Close()
   177  		}
   178  	}()
   179  	if err := conn.Encode(request); err != nil {
   180  		return fmt.Errorf("error encoding request: %s", err)
   181  	}
   182  	if err != nil {
   183  		return err
   184  	}
   185  	err = copyVolumeFromVmRestorer(conn, restorer, 0, vmInfo.Volumes[0].Size,
   186  		logger)
   187  	if err != nil {
   188  		return err
   189  	}
   190  	if _, err := conn.Write(userData); err != nil {
   191  		return err
   192  	}
   193  	for index, volume := range vmInfo.Volumes[1:] {
   194  		err := copyVolumeFromVmRestorer(conn, restorer, uint(index+1),
   195  			volume.Size, logger)
   196  		if err != nil {
   197  			return err
   198  		}
   199  	}
   200  	reply, err := processCreateVmResponses(conn, logger)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	doCloseConn = false
   205  	conn.Close()
   206  	if err := hyperclient.AcknowledgeVm(client, reply.IpAddress); err != nil {
   207  		return fmt.Errorf("error acknowledging VM: %s", err)
   208  	}
   209  	fmt.Println(reply.IpAddress)
   210  	if reply.DhcpTimedOut {
   211  		return errors.New("DHCP ACK timed out")
   212  	}
   213  	if *dhcpTimeout > 0 {
   214  		logger.Debugln(0, "Received DHCP ACK")
   215  	}
   216  	return nil
   217  }
   218  
   219  func newDirectoryRestorer(dirname string) (*directoryRestorer, error) {
   220  	if fi, err := os.Stat(dirname); err != nil {
   221  		return nil, err
   222  	} else if !fi.IsDir() {
   223  		return nil, fmt.Errorf("%s is not a directory", dirname)
   224  	}
   225  	return &directoryRestorer{dirname: dirname}, nil
   226  }
   227  
   228  func (restorer *directoryRestorer) Close() error {
   229  	return nil
   230  }
   231  
   232  func (restorer *directoryRestorer) Filename(filename string) string {
   233  	return filepath.Join(restorer.dirname, filename)
   234  }
   235  
   236  func (restorer *directoryRestorer) OpenReader(filename string) (
   237  	io.ReadCloser, uint64, error) {
   238  	file, err := os.OpenFile(restorer.Filename(filename), os.O_RDONLY, 0)
   239  	if err != nil {
   240  		return nil, 0, err
   241  	} else if fi, err := file.Stat(); err != nil {
   242  		file.Close()
   243  		return nil, 0, err
   244  	} else {
   245  		return file, uint64(fi.Size()), nil
   246  	}
   247  }
   248  
   249  func (gzipDecompressor) MakeReader(r io.ReadCloser) io.ReadCloser {
   250  	if reader, err := gzip.NewReader(r); err != nil {
   251  		panic(err)
   252  	} else {
   253  		return &wrappedReadCloser{real: r, wrap: reader}
   254  	}
   255  }
   256  
   257  func newTarRestorer(filename string,
   258  	decompressor readerMaker) (*tarRestorer, error) {
   259  	file, err := os.OpenFile(filename, os.O_RDONLY, 0)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	readCloser := io.ReadCloser(file)
   264  	if decompressor != nil {
   265  		readCloser = decompressor.MakeReader(readCloser)
   266  	}
   267  	return &tarRestorer{
   268  		closer: readCloser,
   269  		reader: tar.NewReader(readCloser),
   270  	}, nil
   271  }
   272  
   273  func (restorer *tarRestorer) Close() error {
   274  	return restorer.closer.Close()
   275  }
   276  
   277  func (restorer *tarRestorer) OpenReader(filename string) (
   278  	io.ReadCloser, uint64, error) {
   279  	header := restorer.nextHeader
   280  	if header == nil {
   281  		var err error
   282  		if header, err = restorer.reader.Next(); err != nil {
   283  			return nil, 0, err
   284  		}
   285  	}
   286  	restorer.nextHeader = header
   287  	if header.Name != filename {
   288  		return nil, 0, &os.PathError{
   289  			Op:   "have: " + header.Name + " want:",
   290  			Path: filename,
   291  			Err:  os.ErrNotExist,
   292  		}
   293  	} else {
   294  		restorer.nextHeader = nil
   295  		return ioutil.NopCloser(&tarReader{restorer.reader}),
   296  			uint64(header.Size), nil
   297  	}
   298  }
   299  
   300  func (r tarReader) Read(p []byte) (n int, err error) {
   301  	return r.reader.Read(p)
   302  }