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 }