From e958862464f43ddd4bc8fe4b768512afa77d4ee8 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 | 17 ++++++++++ pkg/minikube/command/ssh_runner.go | 51 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/pkg/minikube/assets/vm_assets.go b/pkg/minikube/assets/vm_assets.go index fbc405e19704..c10ff6f79cf3 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 @@ -44,6 +46,7 @@ type BaseAsset struct { TargetDir string TargetName string Permissions string + ModTime time.Time } // GetAssetName returns asset name @@ -66,6 +69,11 @@ func (b *BaseAsset) GetPermissions() string { return b.Permissions } +// GetModTime returns mod time +func (b *BaseAsset) GetModTime() time.Time { + return b.ModTime +} + // FileAsset is an asset using a file type FileAsset struct { BaseAsset @@ -104,6 +112,15 @@ func (f *FileAsset) GetLength() (flen int) { return int(fi.Size()) } +// GetModTime returns modification timeof 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..a0e74a75573c 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.fileExistsRemotely(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) fileExistsRemotely(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)