github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/addImage.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "syscall" 12 "time" 13 14 imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client" 15 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 16 "github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner" 17 "github.com/Cloud-Foundations/Dominator/lib/filesystem/util" 18 "github.com/Cloud-Foundations/Dominator/lib/filter" 19 "github.com/Cloud-Foundations/Dominator/lib/format" 20 "github.com/Cloud-Foundations/Dominator/lib/goroutine" 21 "github.com/Cloud-Foundations/Dominator/lib/hash" 22 "github.com/Cloud-Foundations/Dominator/lib/image" 23 "github.com/Cloud-Foundations/Dominator/lib/image/packageutil" 24 objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client" 25 "github.com/Cloud-Foundations/Dominator/lib/srpc" 26 "github.com/Cloud-Foundations/Dominator/lib/tags" 27 "github.com/Cloud-Foundations/Dominator/lib/triggers" 28 proto "github.com/Cloud-Foundations/Dominator/proto/imaginator" 29 ) 30 31 const timeFormat = "2006-01-02:15:04:05" 32 33 var ( 34 errorTestTimedOut = errors.New("test timed out") 35 tmpFilter *filter.Filter 36 ) 37 38 type hasher struct { 39 cache *treeCache 40 objQ *objectclient.ObjectAdderQueue 41 } 42 43 func init() { 44 if filt, err := filter.New([]string{"/tmp/.*"}); err != nil { 45 panic(err) 46 } else { 47 tmpFilter = filt 48 } 49 } 50 51 func (h *hasher) Hash(reader io.Reader, length uint64) ( 52 hash.Hash, error) { 53 hash, err := h.objQ.Add(reader, length) 54 if err != nil { 55 return hash, errors.New("error sending image data: " + err.Error()) 56 } 57 return hash, nil 58 } 59 60 func (h *hasher) OpenAndHash(inode *filesystem.RegularInode, 61 pathName string) (bool, error) { 62 if len(h.cache.inodeTable) < 1 { 63 return false, nil 64 } 65 inum, ok := h.cache.pathToInode[pathName] 66 if !ok { 67 return false, nil 68 } 69 inodeData, ok := h.cache.inodeTable[inum] 70 if !ok { 71 return false, nil 72 } 73 if inode.Size != inodeData.size { 74 return false, nil 75 } 76 var stat syscall.Stat_t 77 if err := syscall.Stat(pathName, &stat); err != nil { 78 return false, err 79 } 80 if stat.Ino != inum { 81 return false, nil 82 } 83 if stat.Size != int64(inodeData.size) { 84 return false, nil 85 } 86 if stat.Ctim != inodeData.ctime { 87 return false, nil 88 } 89 inode.Hash = inodeData.hash 90 h.cache.numHits++ 91 h.cache.hitBytes += inodeData.size 92 return true, nil 93 } 94 95 func addImage(client srpc.ClientI, request proto.BuildImageRequest, 96 img *image.Image) (string, error) { 97 if request.ExpiresIn > 0 { 98 img.ExpiresAt = time.Now().Add(request.ExpiresIn) 99 } 100 name := makeImageName(request.StreamName) 101 if err := imageclient.AddImage(client, name, img); err != nil { 102 return "", errors.New("remote error: " + err.Error()) 103 } 104 return name, nil 105 } 106 107 func buildFileSystem(client srpc.ClientI, dirname string, 108 scanFilter *filter.Filter, cache *treeCache) ( 109 *filesystem.FileSystem, error) { 110 h := hasher{cache: cache} 111 var err error 112 h.objQ, err = objectclient.NewObjectAdderQueue(client) 113 if err != nil { 114 return nil, err 115 } 116 fs, err := buildFileSystemWithHasher(dirname, &h, scanFilter) 117 if err != nil { 118 h.objQ.Close() 119 return nil, err 120 } 121 err = h.objQ.Close() 122 if err != nil { 123 return nil, err 124 } 125 return fs, nil 126 } 127 128 func buildFileSystemWithHasher(dirname string, h *hasher, 129 scanFilter *filter.Filter) ( 130 *filesystem.FileSystem, error) { 131 fs, err := scanner.ScanFileSystem(dirname, nil, scanFilter, nil, h, nil) 132 if err != nil { 133 return nil, err 134 } 135 return &fs.FileSystem, nil 136 } 137 138 func listPackages(g *goroutine.Goroutine, rootDir string) ( 139 []image.Package, error) { 140 return packageutil.GetPackageList(func(cmd string, w io.Writer) error { 141 return runInTarget(g, nil, w, rootDir, nil, packagerPathname, cmd) 142 }) 143 } 144 145 func makeImageName(streamName string) string { 146 return path.Join(streamName, time.Now().Format(timeFormat)) 147 } 148 149 func packImage(g *goroutine.Goroutine, client srpc.ClientI, 150 request proto.BuildImageRequest, dirname string, scanFilter *filter.Filter, 151 cache *treeCache, computedFilesList []util.ComputedFile, 152 imageFilter *filter.Filter, rawTags tags.Tags, trig *triggers.Triggers, 153 copyMtimesFilter *filter.Filter, buildLog buildLogger) ( 154 *image.Image, error) { 155 if cache == nil { 156 cache = &treeCache{} 157 } 158 if g == nil { 159 var err error 160 g, err = newNamespaceTarget() 161 if err != nil { 162 return nil, err 163 } 164 defer g.Quit() 165 } 166 packages, err := listPackages(g, dirname) 167 if err != nil { 168 return nil, fmt.Errorf("error listing packages: %s", err) 169 } 170 if err := util.DeletedFilteredFiles(dirname, tmpFilter); err != nil { 171 return nil, err 172 } 173 fmt.Fprintln(buildLog, "Scanning file-system and uploading objects") 174 buildStartTime := time.Now() 175 fs, err := buildFileSystem(client, dirname, scanFilter, cache) 176 if err != nil { 177 return nil, fmt.Errorf("error building file-system: %s", err) 178 } 179 if err := util.SpliceComputedFiles(fs, computedFilesList); err != nil { 180 return nil, fmt.Errorf("error splicing computed files: %s", err) 181 } 182 fs.ComputeTotalDataBytes() 183 duration := time.Since(buildStartTime) 184 speed := uint64(float64(fs.TotalDataBytes-cache.hitBytes) / 185 duration.Seconds()) 186 fmt.Fprintf(buildLog, "Skipped %d unchanged objects (%s)\n", 187 cache.numHits, format.FormatBytes(cache.hitBytes)) 188 fmt.Fprintf(buildLog, 189 "Scanned file-system and uploaded %d objects (%s) in %s (%s/s)\n", 190 fs.NumRegularInodes-cache.numHits, 191 format.FormatBytes(fs.TotalDataBytes-cache.hitBytes), 192 format.Duration(duration), format.FormatBytes(speed)) 193 _, oldImage, err := getLatestImage(client, request.StreamName, "", nil, 194 buildLog) 195 if err != nil { 196 return nil, fmt.Errorf("error getting latest image: %s", err) 197 } else if oldImage != nil { 198 patchStartTime := time.Now() 199 util.CopyMtimesWithFilter(oldImage.FileSystem, fs, copyMtimesFilter) 200 fmt.Fprintf(buildLog, "Copied mtimes in %s\n", 201 format.Duration(time.Since(patchStartTime))) 202 } 203 if err := runTests(g, dirname, buildLog); err != nil { 204 return nil, err 205 } 206 objClient := objectclient.AttachObjectClient(client) 207 // Make a copy of the build log because AddObject() drains the buffer. 208 logReader := bytes.NewBuffer(buildLog.Bytes()) 209 hashVal, _, err := objClient.AddObject(logReader, uint64(logReader.Len()), 210 nil) 211 if err != nil { 212 return nil, err 213 } 214 if err := objClient.Close(); err != nil { 215 return nil, err 216 } 217 tgs := rawTags.Copy() 218 for key, value := range tgs { 219 newValue := expandExpression(value, func(name string) string { 220 return request.Variables[name] 221 }) 222 tgs[key] = newValue 223 } 224 img := &image.Image{ 225 BuildLog: &image.Annotation{Object: &hashVal}, 226 FileSystem: fs, 227 Filter: imageFilter, 228 Triggers: trig, 229 Packages: packages, 230 Tags: tgs, 231 } 232 if err := img.Verify(); err != nil { 233 return nil, err 234 } 235 return img, nil 236 } 237 238 func runTests(g *goroutine.Goroutine, rootDir string, 239 buildLog buildLogger) error { 240 var testProgrammes []string 241 err := filepath.Walk(filepath.Join(rootDir, "tests"), 242 func(path string, fi os.FileInfo, err error) error { 243 if fi == nil || !fi.Mode().IsRegular() || fi.Mode()&0100 == 0 { 244 return nil 245 } 246 testProgrammes = append(testProgrammes, path[len(rootDir):]) 247 return nil 248 }) 249 if err != nil { 250 return err 251 } 252 if len(testProgrammes) < 1 { 253 return nil 254 } 255 fmt.Fprintf(buildLog, "Running %d tests\n", len(testProgrammes)) 256 results := make(chan testResultType, 1) 257 for _, prog := range testProgrammes { 258 go func(prog string) { 259 results <- runTest(g, rootDir, prog) 260 }(prog) 261 } 262 numFailures := 0 263 for range testProgrammes { 264 result := <-results 265 io.Copy(buildLog, &result) 266 if result.err != nil { 267 fmt.Fprintf(buildLog, "error running: %s: %s\n", 268 result.prog, result.err) 269 numFailures++ 270 } else { 271 fmt.Fprintf(buildLog, "%s passed in %s\n", 272 result.prog, format.Duration(result.duration)) 273 } 274 fmt.Fprintln(buildLog) 275 } 276 if numFailures > 0 { 277 return fmt.Errorf("%d tests failed", numFailures) 278 } 279 return nil 280 } 281 282 func runTest(g *goroutine.Goroutine, rootDir, prog string) testResultType { 283 startTime := time.Now() 284 result := testResultType{ 285 buffer: make(chan byte, 4096), 286 prog: prog, 287 } 288 errChannel := make(chan error, 1) 289 timer := time.NewTimer(time.Second * 10) 290 go func() { 291 errChannel <- runInTarget(g, nil, &result, rootDir, nil, 292 packagerPathname, "run", prog) 293 }() 294 select { 295 case result.err = <-errChannel: 296 result.duration = time.Since(startTime) 297 case <-timer.C: 298 result.err = errorTestTimedOut 299 } 300 return result 301 } 302 303 func (w *testResultType) Read(p []byte) (int, error) { 304 for count := 0; count < len(p); count++ { 305 select { 306 case p[count] = <-w.buffer: 307 default: 308 return count, io.EOF 309 } 310 } 311 return len(p), nil 312 } 313 314 func (w *testResultType) Write(p []byte) (int, error) { 315 for index, ch := range p { 316 select { 317 case w.buffer <- ch: 318 default: 319 return index, io.ErrShortWrite 320 } 321 } 322 return len(p), nil 323 }