github.com/Cloud-Foundations/Dominator@v0.3.4/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  	u, err := url.Parse(source)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	var restorer vmRestorer
   110  	if u.Scheme == "dir" {
   111  		if restorer, err = newDirectoryRestorer(u.Path); err != nil {
   112  			return err
   113  		}
   114  	} else if u.Scheme == "file" {
   115  		if strings.HasSuffix(u.Path, ".tar") {
   116  			if restorer, err = newTarRestorer(u.Path, nil); err != nil {
   117  				return err
   118  			}
   119  		} else if strings.HasSuffix(u.Path, ".tar.gz") ||
   120  			strings.HasSuffix(u.Path, ".tgz") {
   121  			restorer, err = newTarRestorer(u.Path, gzipDecompressor{})
   122  			if err != nil {
   123  				return err
   124  			}
   125  		} else {
   126  			return fmt.Errorf("unknown extension: %s", u.Path)
   127  		}
   128  	} else {
   129  		return fmt.Errorf("unknown scheme: %s", u.Scheme)
   130  	}
   131  	defer restorer.Close()
   132  	logger.Debugln(0, "reading metadata")
   133  	var vmInfo proto.VmInfo
   134  	err = decodeJsonFromVmRestorer(restorer, "info.json", &vmInfo)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	vmInfo.ImageName = ""
   139  	vmInfo.ImageURL = ""
   140  	userData, err := readFromVmRestorer(restorer, "user-data.raw")
   141  	if err != nil {
   142  		if !os.IsNotExist(err) {
   143  			return err
   144  		}
   145  	}
   146  	request := proto.CreateVmRequest{
   147  		DhcpTimeout:          *dhcpTimeout,
   148  		ImageDataSize:        vmInfo.Volumes[0].Size,
   149  		SecondaryVolumes:     vmInfo.Volumes[1:],
   150  		SecondaryVolumesData: true,
   151  		UserDataSize:         uint64(len(userData)),
   152  		VmInfo:               vmInfo,
   153  	}
   154  	if hypervisor, err := getHypervisorAddress(request.VmInfo); err != nil {
   155  		return err
   156  	} else {
   157  		logger.Debugf(0, "restoring VM on %s\n", hypervisor)
   158  		return restoreVmOnHypervisor(hypervisor, request, restorer, userData,
   159  			source, logger)
   160  	}
   161  }
   162  
   163  func restoreVmOnHypervisor(hypervisor string, request proto.CreateVmRequest,
   164  	restorer vmRestorer, userData []byte, source string,
   165  	logger log.DebugLogger) error {
   166  	client, err := dialHypervisor(hypervisor)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	defer client.Close()
   171  	conn, err := client.Call("Hypervisor.CreateVm")
   172  	if err != nil {
   173  		return fmt.Errorf("error calling Hypervisor.CreateVm: %s", err)
   174  	}
   175  	doCloseConn := true
   176  	defer func() {
   177  		if doCloseConn {
   178  			conn.Close()
   179  		}
   180  	}()
   181  	if err := conn.Encode(request); err != nil {
   182  		return fmt.Errorf("error encoding request: %s", err)
   183  	}
   184  	if err != nil {
   185  		return err
   186  	}
   187  	err = copyVolumeFromVmRestorer(conn, restorer, 0,
   188  		request.VmInfo.Volumes[0].Size, logger)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	if _, err := conn.Write(userData); err != nil {
   193  		return err
   194  	}
   195  	for index, volume := range request.VmInfo.Volumes[1:] {
   196  		err := copyVolumeFromVmRestorer(conn, restorer, uint(index+1),
   197  			volume.Size, logger)
   198  		if err != nil {
   199  			return err
   200  		}
   201  	}
   202  	reply, err := processCreateVmResponses(conn, logger)
   203  	if err != nil {
   204  		return err
   205  	}
   206  	doCloseConn = false
   207  	conn.Close()
   208  	if err := hyperclient.AcknowledgeVm(client, reply.IpAddress); err != nil {
   209  		return fmt.Errorf("error acknowledging VM: %s", err)
   210  	}
   211  	fmt.Println(reply.IpAddress)
   212  	if reply.DhcpTimedOut {
   213  		return errors.New("DHCP ACK timed out")
   214  	}
   215  	if *dhcpTimeout > 0 {
   216  		logger.Debugln(0, "Received DHCP ACK")
   217  	}
   218  	return nil
   219  }
   220  
   221  func newDirectoryRestorer(dirname string) (*directoryRestorer, error) {
   222  	if fi, err := os.Stat(dirname); err != nil {
   223  		return nil, err
   224  	} else if !fi.IsDir() {
   225  		return nil, fmt.Errorf("%s is not a directory", dirname)
   226  	}
   227  	return &directoryRestorer{dirname: dirname}, nil
   228  }
   229  
   230  func (restorer *directoryRestorer) Close() error {
   231  	return nil
   232  }
   233  
   234  func (restorer *directoryRestorer) Filename(filename string) string {
   235  	return filepath.Join(restorer.dirname, filename)
   236  }
   237  
   238  func (restorer *directoryRestorer) OpenReader(filename string) (
   239  	io.ReadCloser, uint64, error) {
   240  	file, err := os.OpenFile(restorer.Filename(filename), os.O_RDONLY, 0)
   241  	if err != nil {
   242  		return nil, 0, err
   243  	} else if fi, err := file.Stat(); err != nil {
   244  		file.Close()
   245  		return nil, 0, err
   246  	} else {
   247  		return file, uint64(fi.Size()), nil
   248  	}
   249  }
   250  
   251  func (gzipDecompressor) MakeReader(r io.ReadCloser) io.ReadCloser {
   252  	if reader, err := gzip.NewReader(r); err != nil {
   253  		panic(err)
   254  	} else {
   255  		return &wrappedReadCloser{real: r, wrap: reader}
   256  	}
   257  }
   258  
   259  func newTarRestorer(filename string,
   260  	decompressor readerMaker) (*tarRestorer, error) {
   261  	file, err := os.OpenFile(filename, os.O_RDONLY, 0)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	readCloser := io.ReadCloser(file)
   266  	if decompressor != nil {
   267  		readCloser = decompressor.MakeReader(readCloser)
   268  	}
   269  	return &tarRestorer{
   270  		closer: readCloser,
   271  		reader: tar.NewReader(readCloser),
   272  	}, nil
   273  }
   274  
   275  func (restorer *tarRestorer) Close() error {
   276  	return restorer.closer.Close()
   277  }
   278  
   279  func (restorer *tarRestorer) OpenReader(filename string) (
   280  	io.ReadCloser, uint64, error) {
   281  	header := restorer.nextHeader
   282  	if header == nil {
   283  		var err error
   284  		if header, err = restorer.reader.Next(); err != nil {
   285  			return nil, 0, err
   286  		}
   287  	}
   288  	restorer.nextHeader = header
   289  	if header.Name != filename {
   290  		return nil, 0, &os.PathError{
   291  			Op:   "have: " + header.Name + " want:",
   292  			Path: filename,
   293  			Err:  os.ErrNotExist,
   294  		}
   295  	} else {
   296  		restorer.nextHeader = nil
   297  		return ioutil.NopCloser(&tarReader{restorer.reader}),
   298  			uint64(header.Size), nil
   299  	}
   300  }
   301  
   302  func (r tarReader) Read(p []byte) (n int, err error) {
   303  	return r.reader.Read(p)
   304  }