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 }