From 822a336f00c12f3f8824ab0e75b10e8b17f26dc6 Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Thu, 7 Nov 2019 11:47:01 -0800 Subject: [PATCH] Only copy new or modified files into VM on restart When minikube restarts, we can save time by only copying over files that have changes or don't exist in the VM. The code in this PR first checks if the file already exists in the VM, and skips copying it over again if it does. --- pkg/minikube/assets/vm_assets.go | 16 ++++++++++ pkg/minikube/command/ssh_runner.go | 51 +++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/minikube/assets/vm_assets.go b/pkg/minikube/assets/vm_assets.go index fbc405e19704..737ff9403966 100644 --- a/pkg/minikube/assets/vm_assets.go +++ b/pkg/minikube/assets/vm_assets.go @@ -23,6 +23,7 @@ import ( "io" "os" "path" + "time" "github.com/golang/glog" "github.com/pkg/errors" @@ -36,6 +37,7 @@ type CopyableFile interface { GetTargetDir() string GetTargetName() string GetPermissions() string + GetModTime() time.Time } // BaseAsset is the base asset class @@ -66,6 +68,11 @@ func (b *BaseAsset) GetPermissions() string { return b.Permissions } +// GetModTime returns mod time +func (b *BaseAsset) GetModTime() time.Time { + return time.Time{} +} + // FileAsset is an asset using a file type FileAsset struct { BaseAsset @@ -104,6 +111,15 @@ func (f *FileAsset) GetLength() (flen int) { return int(fi.Size()) } +// GetModTime returns modification time of the file +func (f *FileAsset) GetModTime() time.Time { + fi, err := os.Stat(f.AssetName) + if err != nil { + return time.Time{} + } + return fi.ModTime() +} + func (f *FileAsset) Read(p []byte) (int, error) { if f.reader == nil { return 0, errors.New("Error attempting FileAsset.Read, FileAsset.reader uninitialized") diff --git a/pkg/minikube/command/ssh_runner.go b/pkg/minikube/command/ssh_runner.go index a341afb0498c..05ca53d00141 100644 --- a/pkg/minikube/command/ssh_runner.go +++ b/pkg/minikube/command/ssh_runner.go @@ -23,6 +23,8 @@ import ( "io" "os/exec" "path" + "strconv" + "strings" "sync" "time" @@ -143,6 +145,11 @@ func (s *SSHRunner) RunCmd(cmd *exec.Cmd) (*RunResult, error) { // Copy copies a file to the remote over SSH. func (s *SSHRunner) Copy(f assets.CopyableFile) error { + dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName())) + if s.fileExistsInVM(f, dst) { + glog.Infof("Skipping copying %s as it already exists", f.GetAssetName()) + } + sess, err := s.c.NewSession() if err != nil { return errors.Wrap(err, "NewSession") @@ -156,7 +163,6 @@ func (s *SSHRunner) Copy(f assets.CopyableFile) error { // StdinPipe is closed. But let's use errgroup to make it explicit. var g errgroup.Group var copied int64 - dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName())) glog.Infof("Transferring %d bytes to %s", f.GetLength(), dst) g.Go(func() error { @@ -189,6 +195,49 @@ func (s *SSHRunner) Copy(f assets.CopyableFile) error { return g.Wait() } +func (s *SSHRunner) fileExistsInVM(f assets.CopyableFile, dst string) bool { + sess, err := s.c.NewSession() + if err != nil { + return false + } + + // check if sizes of the two files are the same + srcSize := f.GetLength() + size := fmt.Sprintf("ls -l %s | cut -d \" \" -f5", dst) + out, err := sess.CombinedOutput(size) + if err != nil { + return false + } + dstSize, err := strconv.Atoi(strings.Trim(string(out), "\n")) + if err != nil { + return false + } + if srcSize != dstSize { + return false + } + + sess, err = s.c.NewSession() + if err != nil { + return false + } + // ensure src file hasn't been modified since dst was copied over + srcModTime := f.GetModTime() + stat := "stat -c %Y" + fmt.Sprintf(" %s", dst) + out, err = sess.CombinedOutput(stat) + if err != nil { + return false + } + unix, err := strconv.Atoi(strings.Trim(string(out), "\n")) + if err != nil { + return false + } + dstModTime := time.Unix(int64(unix), 0) + if err != nil { + return false + } + return srcModTime.Before(dstModTime) +} + // teePrefix copies bytes from a reader to writer, logging each new line. func teePrefix(prefix string, r io.Reader, w io.Writer, logger func(format string, args ...interface{})) error { scanner := bufio.NewScanner(r)