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