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 }