github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/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  	"fmt"
    21  	"os"
    22  	"syscall"
    23  	"time"
    24  
    25  	"github.com/coreos/rkt/stage0"
    26  	"github.com/coreos/rkt/store"
    27  	"github.com/spf13/cobra"
    28  )
    29  
    30  const (
    31  	defaultGracePeriod        = 30 * time.Minute
    32  	defaultPreparedExpiration = 24 * time.Hour
    33  )
    34  
    35  var (
    36  	cmdGC = &cobra.Command{
    37  		Use:   "gc [--grace-period=duration] [--expire-prepared=duration]",
    38  		Short: "Garbage collect rkt pods no longer in use",
    39  		Long: `This is intended to be run periodically from a timer or cron job.
    40  
    41  Garbage collection is a 2-step process. First, stopped pods are moved to the
    42  garbage by one invocation of the gc command. A subsequent invocation will clean
    43  up the pod, assuming the pod has been in the garbage for more time than the
    44  specified grace period.
    45  
    46  Use --grace-period=0s to effectively disable the grace-period.`,
    47  		Run: ensureSuperuser(runWrapper(runGC)),
    48  	}
    49  	flagGracePeriod        time.Duration
    50  	flagPreparedExpiration time.Duration
    51  )
    52  
    53  func init() {
    54  	cmdRkt.AddCommand(cmdGC)
    55  	cmdGC.Flags().DurationVar(&flagGracePeriod, "grace-period", defaultGracePeriod, "duration to wait before discarding inactive pods from garbage")
    56  	cmdGC.Flags().DurationVar(&flagPreparedExpiration, "expire-prepared", defaultPreparedExpiration, "duration to wait before expiring prepared pods")
    57  }
    58  
    59  func runGC(cmd *cobra.Command, args []string) (exit int) {
    60  	if err := renameExited(); err != nil {
    61  		stderr.PrintE("failed to rename exited pods", err)
    62  		return 1
    63  	}
    64  
    65  	if err := renameAborted(); err != nil {
    66  		stderr.PrintE("failed to rename aborted pods", err)
    67  		return 1
    68  	}
    69  
    70  	if err := renameExpired(flagPreparedExpiration); err != nil {
    71  		stderr.PrintE("failed to rename expired prepared pods", err)
    72  		return 1
    73  	}
    74  
    75  	if err := emptyExitedGarbage(flagGracePeriod); err != nil {
    76  		stderr.PrintE("failed to empty exitedGarbage", err)
    77  		return 1
    78  	}
    79  
    80  	if err := emptyGarbage(); err != nil {
    81  		stderr.PrintE("failed to empty garbage", err)
    82  		return 1
    83  	}
    84  
    85  	return
    86  }
    87  
    88  // renameExited renames exited pods to the exitedGarbage directory
    89  func renameExited() error {
    90  	if err := walkPods(includeRunDir, func(p *pod) {
    91  		if p.isExited {
    92  			stderr.Printf("moving pod %q to garbage", p.uuid)
    93  			if err := p.xToExitedGarbage(); err != nil && err != os.ErrNotExist {
    94  				stderr.PrintE("rename error", err)
    95  			}
    96  		}
    97  	}); err != nil {
    98  		return err
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // emptyExitedGarbage discards sufficiently aged pods from exitedGarbageDir()
   105  func emptyExitedGarbage(gracePeriod time.Duration) error {
   106  	if err := walkPods(includeExitedGarbageDir, func(p *pod) {
   107  		gp := p.path()
   108  		st := &syscall.Stat_t{}
   109  		if err := syscall.Lstat(gp, st); err != nil {
   110  			if err != syscall.ENOENT {
   111  				stderr.PrintE(fmt.Sprintf("unable to stat %q, ignoring", gp), err)
   112  			}
   113  			return
   114  		}
   115  
   116  		if expiration := time.Unix(st.Ctim.Unix()).Add(gracePeriod); time.Now().After(expiration) {
   117  			if err := p.ExclusiveLock(); err != nil {
   118  				return
   119  			}
   120  			stdout.Printf("Garbage collecting pod %q", p.uuid)
   121  
   122  			deletePod(p)
   123  		} else {
   124  			stderr.Printf("pod %q not removed: still within grace period (%s)", p.uuid, gracePeriod)
   125  		}
   126  	}); err != nil {
   127  		return err
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // renameAborted renames failed prepares to the garbage directory
   134  func renameAborted() error {
   135  	if err := walkPods(includePrepareDir, func(p *pod) {
   136  		if p.isAbortedPrepare {
   137  			stderr.Printf("moving failed prepare %q to garbage", p.uuid)
   138  			if err := p.xToGarbage(); err != nil && err != os.ErrNotExist {
   139  				stderr.PrintE("rename error", err)
   140  			}
   141  		}
   142  	}); err != nil {
   143  		return err
   144  	}
   145  	return nil
   146  }
   147  
   148  // renameExpired renames expired prepared pods to the garbage directory
   149  func renameExpired(preparedExpiration time.Duration) error {
   150  	if err := walkPods(includePreparedDir, func(p *pod) {
   151  		st := &syscall.Stat_t{}
   152  		pp := p.path()
   153  		if err := syscall.Lstat(pp, st); err != nil {
   154  			if err != syscall.ENOENT {
   155  				stderr.PrintE(fmt.Sprintf("unable to stat %q, ignoring", pp), err)
   156  			}
   157  			return
   158  		}
   159  
   160  		if expiration := time.Unix(st.Ctim.Unix()).Add(preparedExpiration); time.Now().After(expiration) {
   161  			stderr.Printf("moving expired prepared pod %q to garbage", p.uuid)
   162  			if err := p.xToGarbage(); err != nil && err != os.ErrNotExist {
   163  				stderr.PrintE("rename error", err)
   164  			}
   165  		}
   166  	}); err != nil {
   167  		return err
   168  	}
   169  	return nil
   170  }
   171  
   172  // emptyGarbage discards everything from garbageDir()
   173  func emptyGarbage() error {
   174  	if err := walkPods(includeGarbageDir, func(p *pod) {
   175  		if err := p.ExclusiveLock(); err != nil {
   176  			return
   177  		}
   178  		stdout.Printf("Garbage collecting pod %q", p.uuid)
   179  
   180  		deletePod(p)
   181  	}); err != nil {
   182  		return err
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  // deletePod cleans up files and resource associated with the pod
   189  // pod must be under exclusive lock and be in either ExitedGarbage
   190  // or Garbage state
   191  func deletePod(p *pod) {
   192  	if !p.isExitedGarbage && !p.isGarbage {
   193  		stderr.Panicf("logic error: deletePod called with non-garbage pod %q (status %q)", p.uuid, p.getState())
   194  	}
   195  
   196  	if p.isExitedGarbage {
   197  		s, err := store.NewStore(getDataDir())
   198  		if err != nil {
   199  			stderr.PrintE("cannot open store", err)
   200  			return
   201  		}
   202  		defer s.Close()
   203  
   204  		// execute stage1's GC
   205  		stage1TreeStoreID, err := p.getStage1TreeStoreID()
   206  		if err != nil {
   207  			stderr.PrintE("error getting stage1 treeStoreID", err)
   208  			stderr.Print("skipping stage1 GC")
   209  		} else {
   210  			if globalFlags.Debug {
   211  				stage0.InitDebug()
   212  			}
   213  			stage1RootFS := s.GetTreeStoreRootFS(stage1TreeStoreID)
   214  			if err = stage0.GC(p.path(), p.uuid, stage1RootFS); err != nil {
   215  				stderr.PrintE(fmt.Sprintf("problem performing stage1 GC on %q", p.uuid), err)
   216  			}
   217  		}
   218  
   219  		// unmount all leftover mounts
   220  		if err := stage0.MountGC(p.path(), p.uuid.String()); err != nil {
   221  			stderr.PrintE(fmt.Sprintf("GC of leftover mounts for pod %q failed", p.uuid), err)
   222  			return
   223  		}
   224  	}
   225  
   226  	if err := os.RemoveAll(p.path()); err != nil {
   227  		stderr.PrintE(fmt.Sprintf("unable to remove pod %q", p.uuid), err)
   228  		os.Exit(1)
   229  	}
   230  }