github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/logarchiver/impl.go (about) 1 package logarchiver 2 3 import ( 4 "container/list" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "sync" 11 "time" 12 13 "github.com/Cloud-Foundations/Dominator/lib/format" 14 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 15 "github.com/Cloud-Foundations/Dominator/lib/json" 16 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 17 ) 18 19 type buildLogArchiver struct { 20 options BuildLogArchiveOptions 21 params BuildLogArchiveParams 22 fileSizeIncrement uint64 23 mutex sync.Mutex // Lock everything below. 24 ageList list.List // Oldest first. 25 imageStreams map[string]*imageStreamType // Key: stream name. 26 totalSize uint64 27 } 28 29 type imageStreamType struct { 30 images map[string]*imageType // Key: image leaf name. 31 name string 32 } 33 34 type imageType struct { 35 ageListElement *list.Element 36 buildInfo BuildInfo 37 imageStream *imageStreamType 38 logSize uint64 // Rounded up. 39 modTime time.Time 40 name string // Leaf name. 41 } 42 43 func roundUp(value, increment uint64) uint64 { 44 numBlocks := value / increment 45 if numBlocks*increment == value { 46 return value 47 } 48 return (numBlocks + 1) * increment 49 } 50 51 func newBuildLogArchive(options BuildLogArchiveOptions, 52 params BuildLogArchiveParams) (*buildLogArchiver, error) { 53 archive := &buildLogArchiver{ 54 imageStreams: make(map[string]*imageStreamType), 55 options: options, 56 params: params, 57 } 58 if err := archive.computeFileSizeIncrement(); err != nil { 59 return nil, fmt.Errorf("error computing file size increment: %s", err) 60 } 61 startTime := time.Now() 62 if err := archive.load(""); err != nil { 63 return nil, err 64 } 65 loadedTime := time.Now() 66 archive.makeAgeList() 67 sortedTime := time.Now() 68 archive.params.Logger.Printf( 69 "Loaded build log archive %s in %s, sorted in %s\n", 70 format.FormatBytes(archive.totalSize), 71 format.Duration(loadedTime.Sub(startTime)), 72 format.Duration(sortedTime.Sub(loadedTime))) 73 return archive, nil 74 } 75 76 // addEntry adds the image to the image stream and optionally adds it to the 77 // back of the ageList. 78 // No lock is taken. 79 func (a *buildLogArchiver) addEntry(image *imageType, name string, 80 addToAgeList bool) { 81 streamName := filepath.Dir(name) 82 imageStream := a.imageStreams[streamName] 83 if imageStream == nil { 84 imageStream = &imageStreamType{ 85 images: make(map[string]*imageType), 86 name: streamName, 87 } 88 a.imageStreams[streamName] = imageStream 89 } 90 image.imageStream = imageStream 91 imageStream.images[image.name] = image 92 a.totalSize += a.imageTotalSize(image) 93 if addToAgeList { 94 image.ageListElement = a.ageList.PushBack(image) 95 } 96 } 97 98 // addEntryWithCheck checks to see if there is sufficient space (deleting old 99 // entries if needed) and then adds the image to the image stream and the back 100 // of the ageList. 101 func (a *buildLogArchiver) addEntryWithCheck(image *imageType, 102 name string) error { 103 a.mutex.Lock() 104 defer a.mutex.Unlock() 105 for a.imageTotalSize(image)+a.totalSize < a.options.Quota { 106 a.addEntry(image, name, true) 107 return nil 108 } 109 targetSize := a.options.Quota * 95 / 100 110 if a.imageTotalSize(image)+targetSize > a.options.Quota { 111 targetSize -= a.imageTotalSize(image) 112 } 113 var deletedLogs uint 114 origTotalSize := a.totalSize 115 for a.totalSize > targetSize { 116 oldestElement := a.ageList.Front() 117 if err := a.deleteEntry(oldestElement); err != nil { 118 return err 119 } 120 a.ageList.Remove(oldestElement) 121 deletedLogs++ 122 } 123 a.params.Logger.Printf("Deleted %d archived build logs consuming %s\n", 124 deletedLogs, format.FormatBytes(origTotalSize-a.totalSize)) 125 a.addEntry(image, name, true) 126 return nil 127 } 128 129 func (a *buildLogArchiver) AddBuildLog(imageName string, buildInfo BuildInfo, 130 buildLog []byte) error { 131 dirname := filepath.Join(a.options.Topdir, imageName) 132 if err := os.MkdirAll(filepath.Dir(dirname), fsutil.DirPerms); err != nil { 133 return err 134 } 135 if err := os.Mkdir(dirname, fsutil.DirPerms); err != nil { 136 return err 137 } 138 doDelete := true 139 defer func() { 140 if doDelete { 141 os.RemoveAll(dirname) 142 } 143 }() 144 err := json.WriteToFile(filepath.Join(dirname, "buildInfo"), 145 fsutil.PublicFilePerms, " ", buildInfo) 146 if err != nil { 147 return err 148 } 149 logfile := filepath.Join(dirname, "buildLog") 150 err = ioutil.WriteFile(logfile, buildLog, fsutil.PublicFilePerms) 151 if err != nil { 152 return err 153 } 154 image := a.makeEntry(buildInfo, uint64(len(buildLog)), time.Now(), 155 imageName) 156 if err := a.addEntryWithCheck(image, imageName); err != nil { 157 return err 158 } 159 doDelete = false 160 a.params.Logger.Debugf(0, "Archived build log for: %s, %s (%s total)\n", 161 imageName, format.FormatBytes(a.imageTotalSize(image)), 162 format.FormatBytes(a.totalSize)) 163 return nil 164 } 165 166 func (a *buildLogArchiver) computeFileSizeIncrement() error { 167 if err := os.MkdirAll(a.options.Topdir, fsutil.DirPerms); err != nil { 168 return err 169 } 170 file, err := ioutil.TempFile(a.options.Topdir, "******") 171 if err != nil { 172 return err 173 } 174 filename := file.Name() 175 defer os.Remove(filename) 176 if _, err := file.Write([]byte{'\n'}); err != nil { 177 file.Close() 178 return err 179 } 180 if err := file.Close(); err != nil { 181 return err 182 } 183 var statbuf wsyscall.Stat_t 184 if err := wsyscall.Stat(filename, &statbuf); err != nil { 185 return err 186 } 187 if statbuf.Blocks < 1 { 188 statbuf.Blocks = 1 189 } 190 a.fileSizeIncrement = uint64(statbuf.Blocks) * 512 191 return nil 192 } 193 194 func (a *buildLogArchiver) deleteEntry(element *list.Element) error { 195 image := element.Value.(*imageType) 196 imageStream := image.imageStream 197 dirname := filepath.Join(a.options.Topdir, imageStream.name, image.name) 198 if err := os.RemoveAll(dirname); err != nil { 199 return err 200 } 201 delete(imageStream.images, image.name) 202 a.totalSize -= a.imageTotalSize(image) 203 return nil 204 } 205 206 func (a *buildLogArchiver) imageTotalSize(image *imageType) uint64 { 207 return image.logSize + a.fileSizeIncrement 208 } 209 210 func (a *buildLogArchiver) load(dirname string) error { 211 dirpath := filepath.Join(a.options.Topdir, dirname) 212 names, err := fsutil.ReadDirnames(dirpath, false) 213 if err != nil { 214 return err 215 } 216 var buildInfoPathname, buildLogPathname string 217 for _, name := range names { 218 switch name { 219 case "buildInfo": 220 buildInfoPathname = filepath.Join(dirpath, name) 221 continue 222 case "buildLog": 223 buildLogPathname = filepath.Join(dirpath, name) 224 continue 225 } 226 if err := a.load(filepath.Join(dirname, name)); err != nil { 227 return err 228 } 229 } 230 if buildLogPathname == "" { 231 return nil 232 } 233 var buildInfo BuildInfo 234 if buildInfoPathname != "" { 235 if err := json.ReadFromFile(buildInfoPathname, &buildInfo); err != nil { 236 return err 237 } 238 } 239 if fi, err := os.Stat(buildLogPathname); err != nil { 240 return err 241 } else { 242 image := a.makeEntry(buildInfo, uint64(fi.Size()), fi.ModTime(), 243 dirname) 244 a.addEntry(image, dirname, false) 245 } 246 return nil 247 } 248 249 func (a *buildLogArchiver) makeAgeList() { 250 var imageList []*imageType 251 for _, imageStream := range a.imageStreams { 252 for _, image := range imageStream.images { 253 imageList = append(imageList, image) 254 } 255 } 256 // Sort so that oldest mtime is the first slice entry. 257 sort.Slice(imageList, func(i, j int) bool { 258 return imageList[i].modTime.Before(imageList[j].modTime) 259 }) 260 for _, image := range imageList { 261 image.ageListElement = a.ageList.PushBack(image) 262 } 263 } 264 265 func (a *buildLogArchiver) makeEntry(buildInfo BuildInfo, logSize uint64, 266 modTime time.Time, name string) *imageType { 267 image := &imageType{ 268 buildInfo: buildInfo, 269 logSize: roundUp(logSize, a.fileSizeIncrement), 270 modTime: modTime, 271 name: filepath.Base(name), 272 } 273 return image 274 }