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 }