github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/vm-control/saveVm.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "fmt" 8 "io" 9 "net" 10 "net/url" 11 "os" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "github.com/Cloud-Foundations/Dominator/lib/errors" 17 "github.com/Cloud-Foundations/Dominator/lib/format" 18 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 19 "github.com/Cloud-Foundations/Dominator/lib/json" 20 "github.com/Cloud-Foundations/Dominator/lib/log" 21 "github.com/Cloud-Foundations/Dominator/lib/rsync" 22 "github.com/Cloud-Foundations/Dominator/lib/srpc" 23 proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor" 24 ) 25 26 type directorySaver struct { 27 dirname string 28 filename string // If != "", lock all files to this filename. 29 } 30 31 type gzipCompressor struct{} 32 33 type tarSaver struct { 34 closer io.Closer 35 header tar.Header 36 writer *tar.Writer 37 } 38 39 type tarWriter struct { 40 writer *tar.Writer 41 } 42 43 type wrappedWriteCloser struct { 44 real io.Closer 45 wrap io.WriteCloser 46 } 47 48 type writerMaker interface { 49 MakeWriter(w io.WriteCloser) io.WriteCloser 50 } 51 52 type writeSeekCloser interface { 53 io.Closer 54 io.WriteSeeker 55 } 56 57 type vmSaver interface { 58 Close() error 59 CopyToFile(filename string, reader io.Reader, length uint64) error 60 OpenReader(filename string) (io.ReadCloser, uint64, error) 61 OpenWriter(filename string, length uint64) (writeSeekCloser, error) 62 } 63 64 func copyVolumeToVmSaver(saver vmSaver, client *srpc.Client, ipAddr net.IP, 65 volIndex uint, size uint64, logger log.DebugLogger) error { 66 var filename string 67 if volIndex == 0 { 68 filename = "root" 69 } else { 70 filename = fmt.Sprintf("secondary-volume.%d", volIndex-1) 71 } 72 if reader, initialFileSize, err := saver.OpenReader(filename); err != nil { 73 return err 74 } else { 75 if reader != nil { 76 defer reader.Close() 77 } else { 78 if initialFileSize > size { 79 return errors.New("file larger than volume") 80 } 81 } 82 if writer, err := saver.OpenWriter(filename, size); err != nil { 83 return err 84 } else { 85 err := copyVmVolumeToWriter(writer, reader, initialFileSize, 86 client, ipAddr, volIndex, size, logger) 87 if err != nil { 88 writer.Close() 89 return err 90 } 91 return writer.Close() 92 } 93 } 94 } 95 96 func copyVmVolumeToWriter(writer io.WriteSeeker, reader io.Reader, 97 initialFileSize uint64, client *srpc.Client, ipAddr net.IP, volIndex uint, 98 size uint64, logger log.DebugLogger) error { 99 request := proto.GetVmVolumeRequest{ 100 IpAddress: ipAddr, 101 VolumeIndex: volIndex, 102 } 103 conn, err := client.Call("Hypervisor.GetVmVolume") 104 if err != nil { 105 return err 106 } 107 defer conn.Close() 108 if err := conn.Encode(request); err != nil { 109 return fmt.Errorf("error encoding request: %s", err) 110 } 111 if err := conn.Flush(); err != nil { 112 return err 113 } 114 var response proto.GetVmVolumeResponse 115 if err := conn.Decode(&response); err != nil { 116 return err 117 } 118 if err := errors.New(response.Error); err != nil { 119 return err 120 } 121 startTime := time.Now() 122 stats, err := rsync.GetBlocks(conn, conn, conn, reader, writer, 123 size, initialFileSize) 124 if err != nil { 125 return err 126 } 127 duration := time.Since(startTime) 128 speed := uint64(float64(stats.NumRead) / duration.Seconds()) 129 logger.Debugf(0, "sent %s B, received %s/%s B (%.0f * speedup, %s/s)\n", 130 format.FormatBytes(stats.NumWritten), format.FormatBytes(stats.NumRead), 131 format.FormatBytes(size), 132 float64(size)/float64(stats.NumRead+stats.NumWritten), 133 format.FormatBytes(speed)) 134 return nil 135 } 136 137 func encodeJsonToVmSaver(saver vmSaver, filename string, 138 data interface{}) error { 139 buffer := &bytes.Buffer{} 140 if err := json.WriteWithIndent(buffer, " ", data); err != nil { 141 return err 142 } 143 return saver.CopyToFile(filename, buffer, uint64(buffer.Len())) 144 } 145 146 func saveVmSubcommand(args []string, logger log.DebugLogger) error { 147 if err := saveVm(args[0], args[1], logger); err != nil { 148 return fmt.Errorf("Error saving VM: %s", err) 149 } 150 return nil 151 } 152 153 func saveVm(vmHostname, destination string, logger log.DebugLogger) error { 154 if vmIP, hypervisor, err := lookupVmAndHypervisor(vmHostname); err != nil { 155 return err 156 } else { 157 return saveVmOnHypervisor(hypervisor, vmIP, destination, logger) 158 } 159 } 160 161 func saveVmOnHypervisor(hypervisor string, ipAddr net.IP, destination string, 162 logger log.DebugLogger) error { 163 client, err := dialHypervisor(hypervisor) 164 if err != nil { 165 return err 166 } 167 defer client.Close() 168 vmInfo, err := getVmInfoClient(client, ipAddr) 169 if err != nil { 170 return err 171 } 172 u, err := url.Parse(destination) 173 if err != nil { 174 return err 175 } 176 var saver vmSaver 177 if u.Scheme == "dir" { 178 if realSaver, err := newDirectorySaver(u.Path); err != nil { 179 return err 180 } else { 181 saver = realSaver 182 } 183 } else if u.Scheme == "file" { 184 if strings.HasSuffix(u.Path, ".tar") { 185 if saver, err = newTarSaver(u.Path, nil); err != nil { 186 return err 187 } 188 } else if strings.HasSuffix(u.Path, ".tar.gz") || 189 strings.HasSuffix(u.Path, ".tgz") { 190 if saver, err = newTarSaver(u.Path, gzipCompressor{}); err != nil { 191 return err 192 } 193 } else { 194 return fmt.Errorf("unknown extension: %s", u.Path) 195 } 196 } else { 197 return fmt.Errorf("unknown scheme: %s", u.Scheme) 198 } 199 logger.Debugln(0, "saving metadata") 200 if err := encodeJsonToVmSaver(saver, "info.json", vmInfo); err != nil { 201 return err 202 } 203 conn, length, err := callGetVmUserData(client, ipAddr) 204 if err != nil { 205 return err 206 } 207 if length > 0 { 208 logger.Debugln(0, "saving user data") 209 err = saver.CopyToFile("user-data.raw", conn, length) 210 } 211 conn.Close() 212 if err != nil { 213 return err 214 } 215 for index, volume := range vmInfo.Volumes { 216 err := copyVolumeToVmSaver(saver, client, ipAddr, uint(index), 217 volume.Size, logger) 218 if err != nil { 219 return err 220 } 221 } 222 return saver.Close() 223 } 224 225 func newDirectorySaver(dirname string) (*directorySaver, error) { 226 if dirname != "" { 227 if err := os.MkdirAll(dirname, fsutil.DirPerms); err != nil { 228 return nil, err 229 } 230 } 231 return &directorySaver{dirname: dirname}, nil 232 } 233 234 func (saver *directorySaver) Close() error { 235 return nil 236 } 237 238 func (saver *directorySaver) CopyToFile(filename string, reader io.Reader, 239 length uint64) error { 240 file, err := os.OpenFile(saver.Filename(filename), os.O_WRONLY|os.O_CREATE, 241 fsutil.PrivateFilePerms) 242 if err != nil { 243 return err 244 } 245 if _, err := io.CopyN(file, reader, int64(length)); err != nil { 246 file.Close() 247 return err 248 } 249 return file.Close() 250 } 251 252 func (saver *directorySaver) Filename(filename string) string { 253 if saver.filename != "" { 254 return saver.filename 255 } 256 return filepath.Join(saver.dirname, filename) 257 } 258 259 func (saver *directorySaver) OpenReader(filename string) ( 260 io.ReadCloser, uint64, error) { 261 file, err := os.OpenFile(saver.Filename(filename), os.O_RDONLY, 0) 262 if err != nil { 263 if os.IsNotExist(err) { 264 return nil, 0, nil 265 } 266 return nil, 0, err 267 } else if fi, err := file.Stat(); err != nil { 268 file.Close() 269 return nil, 0, err 270 } else { 271 return file, uint64(fi.Size()), nil 272 } 273 } 274 275 func (saver *directorySaver) OpenWriter(filename string, length uint64) ( 276 writeSeekCloser, error) { 277 return os.OpenFile(saver.Filename(filename), 278 os.O_WRONLY|os.O_CREATE, fsutil.PrivateFilePerms) 279 } 280 281 func (gzipCompressor) MakeWriter(w io.WriteCloser) io.WriteCloser { 282 return &wrappedWriteCloser{real: w, wrap: gzip.NewWriter(w)} 283 } 284 285 func newTarSaver(filename string, compressor writerMaker) (*tarSaver, error) { 286 file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 287 fsutil.PrivateFilePerms) 288 if err != nil { 289 return nil, err 290 } 291 writeCloser := io.WriteCloser(file) 292 if compressor != nil { 293 writeCloser = compressor.MakeWriter(writeCloser) 294 } 295 return &tarSaver{ 296 closer: writeCloser, 297 header: tar.Header{ 298 Uid: os.Getuid(), 299 Gid: os.Getgid(), 300 Format: tar.FormatPAX, 301 }, 302 writer: tar.NewWriter(writeCloser), 303 }, nil 304 } 305 306 func (saver *tarSaver) Close() error { 307 if err := saver.writer.Close(); err != nil { 308 saver.closer.Close() 309 return err 310 } 311 return saver.closer.Close() 312 } 313 314 func (saver *tarSaver) CopyToFile(filename string, reader io.Reader, 315 length uint64) error { 316 if writer, err := saver.OpenWriter(filename, length); err != nil { 317 return err 318 } else if _, err := io.CopyN(writer, reader, int64(length)); err != nil { 319 writer.Close() 320 return err 321 } else { 322 return writer.Close() 323 } 324 } 325 326 func (saver *tarSaver) OpenReader(filename string) ( 327 io.ReadCloser, uint64, error) { 328 return nil, 0, nil 329 } 330 331 func (saver *tarSaver) OpenWriter(filename string, length uint64) ( 332 writeSeekCloser, error) { 333 header := saver.header 334 header.Typeflag = tar.TypeReg 335 header.Name = filename 336 header.Size = int64(length) 337 header.Mode = 0400 338 header.ModTime = time.Now() 339 header.AccessTime = header.ModTime 340 header.ChangeTime = header.ModTime 341 if err := saver.writer.WriteHeader(&header); err != nil { 342 return nil, err 343 } 344 return &tarWriter{saver.writer}, nil 345 } 346 347 func (w tarWriter) Close() error { 348 return w.writer.Flush() 349 } 350 351 func (w tarWriter) Seek(offset int64, whence int) (int64, error) { 352 if offset == 0 && whence == io.SeekStart { 353 return 0, nil 354 } 355 if offset == 0 && whence == io.SeekCurrent { 356 return 0, nil 357 } 358 return 0, errors.New("cannot seek") 359 } 360 361 func (w tarWriter) Write(p []byte) (n int, err error) { 362 return w.writer.Write(p) 363 } 364 365 func (w *wrappedWriteCloser) Close() error { 366 if err := w.wrap.Close(); err != nil { 367 w.real.Close() 368 return err 369 } 370 return w.real.Close() 371 } 372 373 func (w *wrappedWriteCloser) Write(p []byte) (n int, err error) { 374 return w.wrap.Write(p) 375 }