Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package main | 5 package main |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "encoding/json" | 8 "encoding/json" |
| 9 "errors" | 9 "errors" |
| 10 "fmt" | 10 "fmt" |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 70 // Item represents a file or symlink referenced by an isolate file. | 70 // Item represents a file or symlink referenced by an isolate file. |
| 71 type Item struct { | 71 type Item struct { |
| 72 Path string | 72 Path string |
| 73 RelPath string | 73 RelPath string |
| 74 Size int64 | 74 Size int64 |
| 75 Mode os.FileMode | 75 Mode os.FileMode |
| 76 | 76 |
| 77 Digest isolated.HexDigest | 77 Digest isolated.HexDigest |
| 78 } | 78 } |
| 79 | 79 |
| 80 // itemGroup is a list of Items, plus a count of the aggregate size. | |
| 81 type itemGroup struct { | |
| 82 items []*Item | |
| 83 size int64 | |
| 84 } | |
| 85 | |
| 86 func (ig *itemGroup) AddItem(item *Item) { | |
| 87 ig.items = append(ig.items, item) | |
| 88 ig.size += item.Size | |
| 89 } | |
| 90 | |
| 91 // partitionWalker contains the state necessary to partition isolate deps by han dling multiple os.WalkFunc invocations. | |
| 92 type partitionWalker struct { | |
|
mithro
2017/06/27 07:48:10
partition is kind of a strange name. Maybe "groupB
mcgreevy
2017/06/28 01:25:50
I'll think about this name and get back to this po
mcgreevy
2017/06/29 05:35:13
Do you mean it's strange because "partition" has a
| |
| 93 // rootDir must be initialized before walkFn is called. | |
| 94 rootDir string | |
| 95 | |
| 96 parts partitionedDeps | |
| 97 } | |
| 98 | |
| 99 // partitionedDeps contains a list of items to be archived, partitioned into sym links and files categorized by size. | |
| 100 type partitionedDeps struct { | |
| 101 links itemGroup | |
| 102 archiveFiles itemGroup | |
| 103 indivFiles itemGroup | |
| 104 } | |
| 105 | |
| 106 // walkFn implements filepath.WalkFunc, for use traversing a directory hierarchy to be isolated. | |
| 107 // It accumulates files in pw.parts, partitioned into symlinks and files categor ized by size. | |
| 108 func (pw *partitionWalker) walkFn(path string, info os.FileInfo, err error) erro r { | |
| 109 if err != nil { | |
| 110 return err | |
| 111 } | |
| 112 if info.IsDir() { | |
| 113 return nil | |
| 114 } | |
| 115 | |
| 116 relPath, err := filepath.Rel(pw.rootDir, path) | |
| 117 if err != nil { | |
| 118 return err | |
| 119 } | |
| 120 | |
| 121 item := &Item{ | |
| 122 Path: path, | |
| 123 RelPath: relPath, | |
| 124 Mode: info.Mode(), | |
| 125 Size: info.Size(), | |
| 126 } | |
| 127 | |
| 128 switch { | |
| 129 case item.Mode&os.ModeSymlink == os.ModeSymlink: | |
| 130 pw.parts.links.AddItem(item) | |
| 131 case item.Size < archiveThreshold: | |
| 132 pw.parts.archiveFiles.AddItem(item) | |
| 133 default: | |
| 134 pw.parts.indivFiles.AddItem(item) | |
| 135 } | |
| 136 return nil | |
| 137 } | |
| 138 | |
| 139 // partitionDeps walks each of the deps, partioning the results into symlinks an d files categorized by size. | |
| 140 func partitionDeps(deps []string, rootDir string) (partitionedDeps, error) { | |
| 141 walker := partitionWalker{rootDir: rootDir} | |
| 142 for _, dep := range deps { | |
| 143 // Try to walk dep. If dep is a file (or symlink), the inner fun ction is called exactly once. | |
| 144 if err := filepath.Walk(filepath.Clean(dep), walker.walkFn); err != nil { | |
| 145 return partitionedDeps{}, err | |
| 146 } | |
| 147 } | |
| 148 return walker.parts, nil | |
| 149 } | |
| 150 | |
| 80 // main contains the core logic for experimental archive. | 151 // main contains the core logic for experimental archive. |
| 81 func (c *expArchiveRun) main() error { | 152 func (c *expArchiveRun) main() error { |
| 82 // TODO(djd): This func is long and has a lot of internal complexity (li ke, | 153 // TODO(djd): This func is long and has a lot of internal complexity (li ke, |
| 83 // such as, archiveCallback). Refactor. | 154 // such as, archiveCallback). Refactor. |
| 84 | 155 |
| 85 start := time.Now() | 156 start := time.Now() |
| 86 archiveOpts := &c.isolateFlags.ArchiveOptions | 157 archiveOpts := &c.isolateFlags.ArchiveOptions |
| 87 // Parse the incoming isolate file. | 158 // Parse the incoming isolate file. |
| 88 deps, rootDir, isol, err := isolate.ProcessIsolate(archiveOpts) | 159 deps, rootDir, isol, err := isolate.ProcessIsolate(archiveOpts) |
| 89 if err != nil { | 160 if err != nil { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 102 } | 173 } |
| 103 client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.i solatedFlags.Namespace, nil, nil) | 174 client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.i solatedFlags.Namespace, nil, nil) |
| 104 | 175 |
| 105 // Set up a checker and uploader. We limit the uploader to one concurren t | 176 // Set up a checker and uploader. We limit the uploader to one concurren t |
| 106 // upload, since the uploads are all coming from disk (with the exceptio n of | 177 // upload, since the uploads are all coming from disk (with the exceptio n of |
| 107 // the isolated JSON itself) and we only want a single goroutine reading from | 178 // the isolated JSON itself) and we only want a single goroutine reading from |
| 108 // disk at once. | 179 // disk at once. |
| 109 checker := NewChecker(ctx, client) | 180 checker := NewChecker(ctx, client) |
| 110 uploader := NewUploader(ctx, client, 1) | 181 uploader := NewUploader(ctx, client, 1) |
| 111 | 182 |
| 112 » // Walk each of the deps, partioning the results into symlinks and files categorised by size. | 183 » parts, err := partitionDeps(deps, rootDir) |
| 113 » var links, archiveFiles, indivFiles []*Item | 184 » if err != nil { |
| 114 » var archiveSize, indivSize int64 // Cumulative size of archived/individu al files. | 185 » » return fmt.Errorf("partitioning deps: %v", err) |
| 115 » for _, dep := range deps { | |
| 116 » » // Try to walk dep. If dep is a file (or symlink), the inner fun ction is called exactly once. | |
| 117 » » err := filepath.Walk(filepath.Clean(dep), func(path string, info os.FileInfo, err error) error { | |
| 118 » » » if err != nil { | |
| 119 » » » » return err | |
| 120 » » » } | |
| 121 » » » if info.IsDir() { | |
| 122 » » » » return nil | |
| 123 » » » } | |
| 124 | |
| 125 » » » relPath, err := filepath.Rel(rootDir, path) | |
| 126 » » » if err != nil { | |
| 127 » » » » return err | |
| 128 » » » } | |
| 129 | |
| 130 » » » item := &Item{ | |
| 131 » » » » Path: path, | |
| 132 » » » » RelPath: relPath, | |
| 133 » » » » Mode: info.Mode(), | |
| 134 » » » » Size: info.Size(), | |
| 135 » » » } | |
| 136 | |
| 137 » » » switch { | |
| 138 » » » case item.Mode&os.ModeSymlink == os.ModeSymlink: | |
| 139 » » » » links = append(links, item) | |
| 140 » » » case item.Size < archiveThreshold: | |
| 141 » » » » archiveFiles = append(archiveFiles, item) | |
| 142 » » » » archiveSize += item.Size | |
| 143 » » » default: | |
| 144 » » » » indivFiles = append(indivFiles, item) | |
| 145 » » » » indivSize += item.Size | |
| 146 » » » } | |
| 147 » » » return nil | |
| 148 » » }) | |
| 149 » » if err != nil { | |
| 150 » » » return err | |
| 151 » » } | |
| 152 } | 186 } |
| 153 | 187 |
| 154 // Construct a map of the files that constitute the isolate. | 188 // Construct a map of the files that constitute the isolate. |
| 155 files := make(map[string]isolated.File) | 189 files := make(map[string]isolated.File) |
| 156 | 190 |
| 157 » log.Printf("Isolate expanded to %d files (total size %s) and %d symlinks ", len(archiveFiles)+len(indivFiles), humanize.Bytes(uint64(archiveSize+indivSiz e)), len(links)) | 191 » numFiles := len(parts.archiveFiles.items) + len(parts.indivFiles.items) |
| 158 » log.Printf("\t%d files (%s) to be isolated individually", len(indivFiles ), humanize.Bytes(uint64(indivSize))) | 192 » filesSize := uint64(parts.archiveFiles.size + parts.indivFiles.size) |
| 159 » log.Printf("\t%d files (%s) to be isolated in archives", len(archiveFile s), humanize.Bytes(uint64(archiveSize))) | 193 » log.Printf("Isolate expanded to %d files (total size %s) and %d symlinks ", numFiles, humanize.Bytes(filesSize), len(parts.links.items)) |
| 194 » log.Printf("\t%d files (%s) to be isolated individually", len(parts.indi vFiles.items), humanize.Bytes(uint64(parts.indivFiles.size))) | |
| 195 » log.Printf("\t%d files (%s) to be isolated in archives", len(parts.archi veFiles.items), humanize.Bytes(uint64(parts.archiveFiles.size))) | |
| 160 | 196 |
| 161 // Handle the symlinks. | 197 // Handle the symlinks. |
| 162 » for _, item := range links { | 198 » for _, item := range parts.links.items { |
| 163 l, err := os.Readlink(item.Path) | 199 l, err := os.Readlink(item.Path) |
| 164 if err != nil { | 200 if err != nil { |
| 165 return fmt.Errorf("unable to resolve symlink for %q: %v" , item.Path, err) | 201 return fmt.Errorf("unable to resolve symlink for %q: %v" , item.Path, err) |
| 166 } | 202 } |
| 167 files[item.RelPath] = isolated.SymLink(l) | 203 files[item.RelPath] = isolated.SymLink(l) |
| 168 } | 204 } |
| 169 | 205 |
| 170 // Handle the small to-be-archived files. | 206 // Handle the small to-be-archived files. |
| 171 » bundles := ShardItems(archiveFiles, archiveMaxSize) | 207 » bundles := ShardItems(parts.archiveFiles.items, archiveMaxSize) |
| 172 log.Printf("\t%d TAR archives to be isolated", len(bundles)) | 208 log.Printf("\t%d TAR archives to be isolated", len(bundles)) |
| 173 | 209 |
| 174 for _, bundle := range bundles { | 210 for _, bundle := range bundles { |
| 175 bundle := bundle | 211 bundle := bundle |
| 176 digest, tarSize, err := bundle.Digest() | 212 digest, tarSize, err := bundle.Digest() |
| 177 if err != nil { | 213 if err != nil { |
| 178 return err | 214 return err |
| 179 } | 215 } |
| 180 | 216 |
| 181 log.Printf("Created tar archive %q (%s)", digest, humanize.Bytes (uint64(tarSize))) | 217 log.Printf("Created tar archive %q (%s)", digest, humanize.Bytes (uint64(tarSize))) |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 195 return | 231 return |
| 196 } | 232 } |
| 197 log.Printf("QUEUED %q for upload", item.RelPath) | 233 log.Printf("QUEUED %q for upload", item.RelPath) |
| 198 uploader.Upload(item.RelPath, bundle.Contents, ps, func( ) { | 234 uploader.Upload(item.RelPath, bundle.Contents, ps, func( ) { |
| 199 log.Printf("UPLOADED %q", item.RelPath) | 235 log.Printf("UPLOADED %q", item.RelPath) |
| 200 }) | 236 }) |
| 201 }) | 237 }) |
| 202 } | 238 } |
| 203 | 239 |
| 204 // Handle the large individually-uploaded files. | 240 // Handle the large individually-uploaded files. |
| 205 » for _, item := range indivFiles { | 241 » for _, item := range parts.indivFiles.items { |
| 206 d, err := hashFile(item.Path) | 242 d, err := hashFile(item.Path) |
| 207 if err != nil { | 243 if err != nil { |
| 208 return err | 244 return err |
| 209 } | 245 } |
| 210 item.Digest = d | 246 item.Digest = d |
| 211 files[item.RelPath] = isolated.BasicFile(item.Digest, int(item.M ode), item.Size) | 247 files[item.RelPath] = isolated.BasicFile(item.Digest, int(item.M ode), item.Size) |
| 212 checker.AddItem(item, false, func(item *Item, ps *isolatedclient .PushState) { | 248 checker.AddItem(item, false, func(item *Item, ps *isolatedclient .PushState) { |
| 213 if ps == nil { | 249 if ps == nil { |
| 214 return | 250 return |
| 215 } | 251 } |
| 216 log.Printf("QUEUED %q for upload", item.RelPath) | 252 log.Printf("QUEUED %q for upload", item.RelPath) |
| 217 uploader.UploadFile(item, ps, func() { | 253 uploader.UploadFile(item, ps, func() { |
| 218 log.Printf("UPLOADED %q", item.RelPath) | 254 log.Printf("UPLOADED %q", item.RelPath) |
| 219 }) | 255 }) |
| 220 }) | 256 }) |
| 221 } | 257 } |
| 222 | 258 |
| 223 // Marshal the isolated file into JSON, and create an Item to describe i t. | 259 // Marshal the isolated file into JSON, and create an Item to describe i t. |
| 224 isol.Files = files | 260 isol.Files = files |
| 225 » isolJSON, err := json.Marshal(isol) | 261 » var isolJSON []byte |
| 262 » isolJSON, err = json.Marshal(isol) | |
| 226 if err != nil { | 263 if err != nil { |
| 227 return err | 264 return err |
| 228 } | 265 } |
| 229 isolItem := &Item{ | 266 isolItem := &Item{ |
| 230 Path: archiveOpts.Isolated, | 267 Path: archiveOpts.Isolated, |
| 231 RelPath: filepath.Base(archiveOpts.Isolated), | 268 RelPath: filepath.Base(archiveOpts.Isolated), |
| 232 Digest: isolated.HashBytes(isolJSON), | 269 Digest: isolated.HashBytes(isolJSON), |
| 233 Size: int64(len(isolJSON)), | 270 Size: int64(len(isolJSON)), |
| 234 } | 271 } |
| 235 | 272 |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 332 } | 369 } |
| 333 | 370 |
| 334 func hashFile(path string) (isolated.HexDigest, error) { | 371 func hashFile(path string) (isolated.HexDigest, error) { |
| 335 f, err := os.Open(path) | 372 f, err := os.Open(path) |
| 336 if err != nil { | 373 if err != nil { |
| 337 return "", err | 374 return "", err |
| 338 } | 375 } |
| 339 defer f.Close() | 376 defer f.Close() |
| 340 return isolated.Hash(f) | 377 return isolated.Hash(f) |
| 341 } | 378 } |
| OLD | NEW |