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

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strings"
    12  	"syscall"
    13  
    14  	hyperclient "github.com/Cloud-Foundations/Dominator/hypervisor/client"
    15  	"github.com/Cloud-Foundations/Dominator/lib/errors"
    16  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    17  	"github.com/Cloud-Foundations/Dominator/lib/json"
    18  	"github.com/Cloud-Foundations/Dominator/lib/log"
    19  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    20  	"github.com/Cloud-Foundations/Dominator/lib/srpc/setupclient"
    21  	proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    22  )
    23  
    24  const (
    25  	dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP |
    26  		syscall.S_IROTH | syscall.S_IXOTH
    27  	privateFilePerms = syscall.S_IRUSR | syscall.S_IWUSR
    28  )
    29  
    30  var (
    31  	errorCommitAbandoned = errors.New("you abandoned your VM")
    32  	errorCommitDeferred  = errors.New("you deferred committing your VM")
    33  )
    34  
    35  func askForCommitDecision(client *srpc.Client, ipAddress net.IP) error {
    36  	response, err := askForInputChoice("Commit VM "+ipAddress.String(),
    37  		[]string{"commit", "defer", "abandon"})
    38  	if err != nil {
    39  		return err
    40  	}
    41  	switch response {
    42  	case "abandon":
    43  		err := hyperclient.DestroyVm(client, ipAddress, nil)
    44  		if err != nil {
    45  			return err
    46  		}
    47  		return errorCommitAbandoned
    48  	case "commit":
    49  		return commitVm(client, ipAddress)
    50  	case "defer":
    51  		return errorCommitDeferred
    52  	}
    53  	return fmt.Errorf("invalid response: %s", response)
    54  }
    55  
    56  func askForInputChoice(prompt string, choices []string) (string, error) {
    57  	reader := bufio.NewReader(os.Stdin)
    58  	choicesMap := make(map[string]struct{}, len(choices))
    59  	for _, choice := range choices {
    60  		choicesMap[choice] = struct{}{}
    61  	}
    62  	for {
    63  		fmt.Fprintf(os.Stderr, "%s (%s)? ", prompt, strings.Join(choices, "/"))
    64  		if response, err := reader.ReadString('\n'); err != nil {
    65  			return "", fmt.Errorf("deferring, error reading input: %s", err)
    66  		} else {
    67  			response = response[:len(response)-1]
    68  			if _, ok := choicesMap[response]; ok {
    69  				return response, nil
    70  			}
    71  		}
    72  	}
    73  }
    74  
    75  func commitVm(client *srpc.Client, ipAddress net.IP) error {
    76  	request := proto.CommitImportedVmRequest{ipAddress}
    77  	var reply proto.CommitImportedVmResponse
    78  	err := client.RequestReply("Hypervisor.CommitImportedVm", request, &reply)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	return errors.New(reply.Error)
    83  }
    84  
    85  func importLocalVmSubcommand(args []string, logger log.DebugLogger) error {
    86  	if err := importLocalVm(args[0], args[1], logger); err != nil {
    87  		return fmt.Errorf("Error importing VM: %s", err)
    88  	}
    89  	return nil
    90  }
    91  
    92  func importLocalVm(infoFile, rootVolume string, logger log.DebugLogger) error {
    93  	var vmInfo proto.VmInfo
    94  	if err := json.ReadFromFile(infoFile, &vmInfo); err != nil {
    95  		return err
    96  	}
    97  	return importLocalVmInfo(vmInfo, rootVolume, logger)
    98  }
    99  
   100  func importLocalVmInfo(vmInfo proto.VmInfo, rootVolume string,
   101  	logger log.DebugLogger) error {
   102  	hypervisor := fmt.Sprintf(":%d", *hypervisorPortNum)
   103  	client, err := srpc.DialHTTP("tcp", hypervisor, 0)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer client.Close()
   108  	rootCookie, err := readRootCookie(client, logger)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	directories, err := listVolumeDirectories(client)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	dirname := filepath.Join(directories[0], "import")
   117  	if err := os.Mkdir(dirname, dirPerms); err != nil {
   118  		if !os.IsExist(err) {
   119  			return err
   120  		}
   121  	}
   122  	dirname = filepath.Join(dirname, fmt.Sprintf("%d", os.Getpid()))
   123  	if err := os.Mkdir(dirname, dirPerms); err != nil {
   124  		return err
   125  	}
   126  	defer os.RemoveAll(dirname)
   127  	logger.Debugf(0, "created: %s\n", dirname)
   128  	rootFilename := filepath.Join(dirname, "root")
   129  	if err := os.Link(rootVolume, rootFilename); err != nil {
   130  		err = fsutil.CopyFile(rootFilename, rootVolume, privateFilePerms)
   131  		if err != nil {
   132  			return err
   133  		}
   134  	}
   135  	request := proto.ImportLocalVmRequest{
   136  		VerificationCookie: rootCookie,
   137  		VmInfo:             vmInfo,
   138  		VolumeFilenames:    []string{rootFilename},
   139  	}
   140  	var reply proto.GetVmInfoResponse
   141  	err = client.RequestReply("Hypervisor.ImportLocalVm", request, &reply)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	if err := errors.New(reply.Error); err != nil {
   146  		return err
   147  	}
   148  	logger.Debugln(0, "imported VM")
   149  	os.RemoveAll(dirname)
   150  	err = maybeWatchVm(client, hypervisor, vmInfo.Address.IpAddress, logger)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	return askForCommitDecision(client, vmInfo.Address.IpAddress)
   155  }
   156  
   157  func listVolumeDirectories(client *srpc.Client) ([]string, error) {
   158  	var request proto.ListVolumeDirectoriesRequest
   159  	var reply proto.ListVolumeDirectoriesResponse
   160  	err := client.RequestReply("Hypervisor.ListVolumeDirectories", request,
   161  		&reply)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	if err := errors.New(reply.Error); err != nil {
   166  		return nil, err
   167  	}
   168  	return reply.Directories, nil
   169  }
   170  
   171  func readRootCookie(client *srpc.Client,
   172  	logger log.DebugLogger) ([]byte, error) {
   173  	rootCookiePath, err := hyperclient.GetRootCookiePath(client)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	rootCookie, err := ioutil.ReadFile(rootCookiePath)
   178  	if err != nil && os.IsPermission(err) {
   179  		// Try again with sudo(8).
   180  		args := make([]string, 0, len(os.Args)+1)
   181  		if sudoPath, err := exec.LookPath("sudo"); err != nil {
   182  			return nil, err
   183  		} else {
   184  			args = append(args, sudoPath)
   185  		}
   186  		if myPath, err := exec.LookPath(os.Args[0]); err != nil {
   187  			return nil, err
   188  		} else {
   189  			args = append(args, myPath)
   190  		}
   191  		args = append(args, "-certDirectory", setupclient.GetCertDirectory())
   192  		args = append(args, os.Args[1:]...)
   193  		if err := syscall.Exec(args[0], args, os.Environ()); err != nil {
   194  			return nil, errors.New("unable to Exec: " + err.Error())
   195  		}
   196  	}
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	logger.Debugln(0, "have cookie")
   201  	return rootCookie, nil
   202  }