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 }