github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/rkt/gc.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //+build linux
    16  
    17  package main
    18  
    19  import (
    20  	"os"
    21  	"path/filepath"
    22  	"syscall"
    23  	"time"
    24  
    25  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
    26  	"github.com/coreos/rkt/common"
    27  	"github.com/coreos/rkt/stage0"
    28  	"github.com/coreos/rkt/store"
    29  )
    30  
    31  const (
    32  	defaultGracePeriod        = 30 * time.Minute
    33  	defaultPreparedExpiration = 24 * time.Hour
    34  )
    35  
    36  var (
    37  	cmdGC = &cobra.Command{
    38  		Use:   "gc [--grace-period=duration] [--expire-prepared=duration]",
    39  		Short: "Garbage-collect rkt pods no longer in use",
    40  		Run:   runWrapper(runGC),
    41  	}
    42  	flagGracePeriod        time.Duration
    43  	flagPreparedExpiration time.Duration
    44  )
    45  
    46  func init() {
    47  	cmdRkt.AddCommand(cmdGC)
    48  	cmdGC.Flags().DurationVar(&flagGracePeriod, "grace-period", defaultGracePeriod, "duration to wait before discarding inactive pods from garbage")
    49  	cmdGC.Flags().DurationVar(&flagPreparedExpiration, "expire-prepared", defaultPreparedExpiration, "duration to wait before expiring prepared pods")
    50  }
    51  
    52  func runGC(cmd *cobra.Command, args []string) (exit int) {
    53  	if err := renameExited(); err != nil {
    54  		stderr("Failed to rename exited pods: %v", err)
    55  		return 1
    56  	}
    57  
    58  	if err := renameAborted(); err != nil {
    59  		stderr("Failed to rename aborted pods: %v", err)
    60  		return 1
    61  	}
    62  
    63  	if err := renameExpired(flagPreparedExpiration); err != nil {
    64  		stderr("Failed to rename expired prepared pods: %v", err)
    65  		return 1
    66  	}
    67  
    68  	if err := emptyExitedGarbage(flagGracePeriod); err != nil {
    69  		stderr("Failed to empty exitedGarbage: %v", err)
    70  		return 1
    71  	}
    72  
    73  	if err := emptyGarbage(); err != nil {
    74  		stderr("Failed to empty garbage: %v", err)
    75  		return 1
    76  	}
    77  
    78  	return
    79  }
    80  
    81  // renameExited renames exited pods to the exitedGarbage directory
    82  func renameExited() error {
    83  	if err := walkPods(includeRunDir, func(p *pod) {
    84  		if p.isExited {
    85  			stderr("Moving pod %q to garbage", p.uuid)
    86  			if err := p.xToExitedGarbage(); err != nil && err != os.ErrNotExist {
    87  				stderr("Rename error: %v", err)
    88  			}
    89  		}
    90  	}); err != nil {
    91  		return err
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // emptyExitedGarbage discards sufficiently aged pods from exitedGarbageDir()
    98  func emptyExitedGarbage(gracePeriod time.Duration) error {
    99  	if err := walkPods(includeExitedGarbageDir, func(p *pod) {
   100  		gp := p.path()
   101  		st := &syscall.Stat_t{}
   102  		if err := syscall.Lstat(gp, st); err != nil {
   103  			if err != syscall.ENOENT {
   104  				stderr("Unable to stat %q, ignoring: %v", gp, err)
   105  			}
   106  			return
   107  		}
   108  
   109  		if expiration := time.Unix(st.Ctim.Unix()).Add(gracePeriod); time.Now().After(expiration) {
   110  			if err := p.ExclusiveLock(); err != nil {
   111  				return
   112  			}
   113  			stdout("Garbage collecting pod %q", p.uuid)
   114  
   115  			deletePod(p)
   116  		} else {
   117  			stderr("Pod %q not removed: still within grace period (%s)", p.uuid, gracePeriod)
   118  		}
   119  	}); err != nil {
   120  		return err
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  // renameAborted renames failed prepares to the garbage directory
   127  func renameAborted() error {
   128  	if err := walkPods(includePrepareDir, func(p *pod) {
   129  		if p.isAbortedPrepare {
   130  			stderr("Moving failed prepare %q to garbage", p.uuid)
   131  			if err := p.xToGarbage(); err != nil && err != os.ErrNotExist {
   132  				stderr("Rename error: %v", err)
   133  			}
   134  		}
   135  	}); err != nil {
   136  		return err
   137  	}
   138  	return nil
   139  }
   140  
   141  // renameExpired renames expired prepared pods to the garbage directory
   142  func renameExpired(preparedExpiration time.Duration) error {
   143  	if err := walkPods(includePreparedDir, func(p *pod) {
   144  		st := &syscall.Stat_t{}
   145  		pp := p.path()
   146  		if err := syscall.Lstat(pp, st); err != nil {
   147  			if err != syscall.ENOENT {
   148  				stderr("Unable to stat %q, ignoring: %v", pp, err)
   149  			}
   150  			return
   151  		}
   152  
   153  		if expiration := time.Unix(st.Ctim.Unix()).Add(preparedExpiration); time.Now().After(expiration) {
   154  			stderr("Moving expired prepared pod %q to garbage", p.uuid)
   155  			if err := p.xToGarbage(); err != nil && err != os.ErrNotExist {
   156  				stderr("Rename error: %v", err)
   157  			}
   158  		}
   159  	}); err != nil {
   160  		return err
   161  	}
   162  	return nil
   163  }
   164  
   165  // emptyGarbage discards everything from garbageDir()
   166  func emptyGarbage() error {
   167  	if err := walkPods(includeGarbageDir, func(p *pod) {
   168  		if err := p.ExclusiveLock(); err != nil {
   169  			return
   170  		}
   171  		stdout("Garbage collecting pod %q", p.uuid)
   172  
   173  		deletePod(p)
   174  	}); err != nil {
   175  		return err
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // deletePod cleans up files and resource associated with the pod
   182  // pod must be under exclusive lock and be in either ExitedGarbage
   183  // or Garbage state
   184  func deletePod(p *pod) {
   185  	if !p.isExitedGarbage && !p.isGarbage {
   186  		panic("logic error: deletePod called with non-garbage pod")
   187  	}
   188  
   189  	if p.isExitedGarbage {
   190  		s, err := store.NewStore(globalFlags.Dir)
   191  		if err != nil {
   192  			stderr("Cannot open store: %v", err)
   193  			return
   194  		}
   195  		defer s.Close()
   196  		stage1TreeStoreID, err := p.getStage1TreeStoreID()
   197  		if err != nil {
   198  			stderr("Error getting stage1 treeStoreID: %v", err)
   199  			return
   200  		}
   201  		stage1RootFS := s.GetTreeStoreRootFS(stage1TreeStoreID)
   202  
   203  		// execute stage1's GC
   204  		if err := stage0.GC(p.path(), p.uuid, stage1RootFS, globalFlags.Debug); err != nil {
   205  			stderr("Stage1 GC of pod %q failed: %v", p.uuid, err)
   206  			return
   207  		}
   208  
   209  		if p.usesOverlay() {
   210  			apps, err := p.getApps()
   211  			if err != nil {
   212  				stderr("Error retrieving app hashes from pod manifest: %v", err)
   213  				return
   214  			}
   215  			for _, a := range apps {
   216  				dest := filepath.Join(common.AppPath(p.path(), a.Name), "rootfs")
   217  				if err := syscall.Unmount(dest, 0); err != nil {
   218  					// machine could have been rebooted and mounts lost.
   219  					// ignore "does not exist" and "not a mount point" errors
   220  					if err != syscall.ENOENT && err != syscall.EINVAL {
   221  						stderr("Error unmounting app at %v: %v", dest, err)
   222  					}
   223  				}
   224  			}
   225  
   226  			s1 := filepath.Join(common.Stage1ImagePath(p.path()), "rootfs")
   227  			if err := syscall.Unmount(s1, 0); err != nil {
   228  				// machine could have been rebooted and mounts lost.
   229  				// ignore "does not exist" and "not a mount point" errors
   230  				if err != syscall.ENOENT && err != syscall.EINVAL {
   231  					stderr("Error unmounting stage1 at %v: %v", s1, err)
   232  					return
   233  				}
   234  			}
   235  		}
   236  	}
   237  
   238  	if err := os.RemoveAll(p.path()); err != nil {
   239  		stderr("Unable to remove pod %q: %v", p.uuid, err)
   240  	}
   241  }