| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. | 1 // Copyright 2015 The LUCI Authors. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with 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 | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 package main | 15 package main |
| 16 | 16 |
| 17 import ( | 17 import ( |
| 18 "encoding/json" | 18 "encoding/json" |
| 19 "errors" | 19 "errors" |
| 20 "fmt" | 20 "fmt" |
| 21 "io" | 21 "io" |
| 22 "log" | 22 "log" |
| 23 "os" | 23 "os" |
| 24 "path/filepath" | |
| 25 "time" | 24 "time" |
| 26 | 25 |
| 27 humanize "github.com/dustin/go-humanize" | |
| 28 "github.com/golang/protobuf/proto" | 26 "github.com/golang/protobuf/proto" |
| 29 "github.com/maruel/subcommands" | 27 "github.com/maruel/subcommands" |
| 30 "golang.org/x/net/context" | 28 "golang.org/x/net/context" |
| 31 | 29 |
| 32 "github.com/luci/luci-go/client/internal/common" | |
| 33 "github.com/luci/luci-go/client/isolate" | |
| 34 "github.com/luci/luci-go/common/auth" | 30 "github.com/luci/luci-go/common/auth" |
| 35 logpb "github.com/luci/luci-go/common/eventlog/proto" | 31 logpb "github.com/luci/luci-go/common/eventlog/proto" |
| 36 "github.com/luci/luci-go/common/isolated" | 32 "github.com/luci/luci-go/common/isolated" |
| 37 "github.com/luci/luci-go/common/isolatedclient" | 33 "github.com/luci/luci-go/common/isolatedclient" |
| 38 ) | 34 ) |
| 39 | 35 |
| 40 const ( | 36 const ( |
| 41 // archiveThreshold is the size (in bytes) used to determine whether to
add | 37 // archiveThreshold is the size (in bytes) used to determine whether to
add |
| 42 // files to a tar archive before uploading. Files smaller than this size
will | 38 // files to a tar archive before uploading. Files smaller than this size
will |
| 43 // be combined into archives before being uploaded to the server. | 39 // be combined into archives before being uploaded to the server. |
| (...skipping 26 matching lines...) Expand all Loading... |
| 70 | 66 |
| 71 // expArchiveRun contains the logic for the experimental archive subcommand. | 67 // expArchiveRun contains the logic for the experimental archive subcommand. |
| 72 // It implements subcommand.CommandRun | 68 // It implements subcommand.CommandRun |
| 73 type expArchiveRun struct { | 69 type expArchiveRun struct { |
| 74 commonServerFlags // Provides the GetFlags method. | 70 commonServerFlags // Provides the GetFlags method. |
| 75 isolateFlags isolateFlags | 71 isolateFlags isolateFlags |
| 76 loggingFlags loggingFlags | 72 loggingFlags loggingFlags |
| 77 dumpJSON string | 73 dumpJSON string |
| 78 } | 74 } |
| 79 | 75 |
| 80 // Item represents a file or symlink referenced by an isolate file. | |
| 81 type Item struct { | |
| 82 Path string | |
| 83 RelPath string | |
| 84 Size int64 | |
| 85 Mode os.FileMode | |
| 86 | |
| 87 Digest isolated.HexDigest | |
| 88 } | |
| 89 | |
| 90 // itemGroup is a list of Items, plus a count of the aggregate size. | |
| 91 type itemGroup struct { | |
| 92 items []*Item | |
| 93 totalSize int64 | |
| 94 } | |
| 95 | |
| 96 func (ig *itemGroup) AddItem(item *Item) { | |
| 97 ig.items = append(ig.items, item) | |
| 98 ig.totalSize += item.Size | |
| 99 } | |
| 100 | |
| 101 // partitioningWalker contains the state necessary to partition isolate deps by
handling multiple os.WalkFunc invocations. | |
| 102 type partitioningWalker struct { | |
| 103 // fsView must be initialized before walkFn is called. | |
| 104 fsView common.FilesystemView | |
| 105 | |
| 106 parts partitionedDeps | |
| 107 } | |
| 108 | |
| 109 // partitionedDeps contains a list of items to be archived, partitioned into sym
links and files categorized by size. | |
| 110 type partitionedDeps struct { | |
| 111 links itemGroup | |
| 112 filesToArchive itemGroup | |
| 113 indivFiles itemGroup | |
| 114 } | |
| 115 | |
| 116 // walkFn implements filepath.WalkFunc, for use traversing a directory hierarchy
to be isolated. | |
| 117 // It accumulates files in pw.parts, partitioned into symlinks and files categor
ized by size. | |
| 118 func (pw *partitioningWalker) walkFn(path string, info os.FileInfo, err error) e
rror { | |
| 119 if err != nil { | |
| 120 return err | |
| 121 } | |
| 122 | |
| 123 relPath, err := pw.fsView.RelativePath(path) | |
| 124 if err != nil { | |
| 125 return err | |
| 126 } | |
| 127 | |
| 128 if relPath == "" { // empty string indicates skip. | |
| 129 return common.WalkFuncSkipFile(info) | |
| 130 } | |
| 131 | |
| 132 if info.IsDir() { | |
| 133 return nil | |
| 134 } | |
| 135 | |
| 136 item := &Item{ | |
| 137 Path: path, | |
| 138 RelPath: relPath, | |
| 139 Mode: info.Mode(), | |
| 140 Size: info.Size(), | |
| 141 } | |
| 142 | |
| 143 switch { | |
| 144 case item.Mode&os.ModeSymlink == os.ModeSymlink: | |
| 145 pw.parts.links.AddItem(item) | |
| 146 case item.Size < archiveThreshold: | |
| 147 pw.parts.filesToArchive.AddItem(item) | |
| 148 default: | |
| 149 pw.parts.indivFiles.AddItem(item) | |
| 150 } | |
| 151 return nil | |
| 152 } | |
| 153 | |
| 154 // partitionDeps walks each of the deps, partioning the results into symlinks an
d files categorized by size. | |
| 155 func partitionDeps(deps []string, rootDir string, blacklist []string) (partition
edDeps, error) { | |
| 156 fsView, err := common.NewFilesystemView(rootDir, blacklist) | |
| 157 if err != nil { | |
| 158 return partitionedDeps{}, err | |
| 159 } | |
| 160 | |
| 161 walker := partitioningWalker{fsView: fsView} | |
| 162 for _, dep := range deps { | |
| 163 // Try to walk dep. If dep is a file (or symlink), the inner fun
ction is called exactly once. | |
| 164 if err := filepath.Walk(filepath.Clean(dep), walker.walkFn); err
!= nil { | |
| 165 return partitionedDeps{}, err | |
| 166 } | |
| 167 } | |
| 168 return walker.parts, nil | |
| 169 } | |
| 170 | |
| 171 // main contains the core logic for experimental archive. | 76 // main contains the core logic for experimental archive. |
| 172 func (c *expArchiveRun) main() error { | 77 func (c *expArchiveRun) main() error { |
| 173 // TODO(djd): This func is long and has a lot of internal complexity (li
ke, | |
| 174 // such as, archiveCallback). Refactor. | |
| 175 | |
| 176 start := time.Now() | 78 start := time.Now() |
| 177 archiveOpts := &c.isolateFlags.ArchiveOptions | 79 archiveOpts := &c.isolateFlags.ArchiveOptions |
| 178 // Parse the incoming isolate file. | |
| 179 deps, rootDir, isol, err := isolate.ProcessIsolate(archiveOpts) | |
| 180 if err != nil { | |
| 181 return fmt.Errorf("failed to process isolate: %v", err) | |
| 182 } | |
| 183 log.Printf("Isolate referenced %d deps", len(deps)) | |
| 184 | 80 |
| 185 // Set up a background context which is cancelled when this function ret
urns. | 81 // Set up a background context which is cancelled when this function ret
urns. |
| 186 ctx, cancel := context.WithCancel(context.Background()) | 82 ctx, cancel := context.WithCancel(context.Background()) |
| 187 defer cancel() | 83 defer cancel() |
| 188 | 84 |
| 189 // Create the isolated client which connects to the isolate server. | 85 // Create the isolated client which connects to the isolate server. |
| 190 authCl, err := c.createAuthClient() | 86 authCl, err := c.createAuthClient() |
| 191 if err != nil { | 87 if err != nil { |
| 192 return err | 88 return err |
| 193 } | 89 } |
| 194 client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.i
solatedFlags.Namespace, nil, nil) | 90 client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.i
solatedFlags.Namespace, nil, nil) |
| 195 | 91 |
| 196 // Set up a checker and uploader. We limit the uploader to one concurren
t | 92 // Set up a checker and uploader. We limit the uploader to one concurren
t |
| 197 // upload, since the uploads are all coming from disk (with the exceptio
n of | 93 // upload, since the uploads are all coming from disk (with the exceptio
n of |
| 198 // the isolated JSON itself) and we only want a single goroutine reading
from | 94 // the isolated JSON itself) and we only want a single goroutine reading
from |
| 199 // disk at once. | 95 // disk at once. |
| 200 checker := NewChecker(ctx, client) | 96 checker := NewChecker(ctx, client) |
| 201 uploader := NewUploader(ctx, client, 1) | 97 uploader := NewUploader(ctx, client, 1) |
| 98 archiver := NewTarringArchiver(checker, uploader) |
| 202 | 99 |
| 203 » parts, err := partitionDeps(deps, rootDir, c.isolateFlags.ArchiveOptions
.Blacklist) | 100 » isolSummary, err := archiver.Archive(archiveOpts) |
| 204 » if err != nil { | |
| 205 » » return fmt.Errorf("partitioning deps: %v", err) | |
| 206 » } | |
| 207 | |
| 208 » numFiles := len(parts.filesToArchive.items) + len(parts.indivFiles.items
) | |
| 209 » filesSize := uint64(parts.filesToArchive.totalSize + parts.indivFiles.to
talSize) | |
| 210 » log.Printf("Isolate expanded to %d files (total size %s) and %d symlinks
", numFiles, humanize.Bytes(filesSize), len(parts.links.items)) | |
| 211 » log.Printf("\t%d files (%s) to be isolated individually", len(parts.indi
vFiles.items), humanize.Bytes(uint64(parts.indivFiles.totalSize))) | |
| 212 » log.Printf("\t%d files (%s) to be isolated in archives", len(parts.files
ToArchive.items), humanize.Bytes(uint64(parts.filesToArchive.totalSize))) | |
| 213 | |
| 214 » tracker := NewUploadTracker(checker, uploader, isol) | |
| 215 » if err := tracker.UploadDeps(parts); err != nil { | |
| 216 » » return err | |
| 217 » } | |
| 218 » isolSummary, err := tracker.Finalize(archiveOpts.Isolated) | |
| 219 » if err != nil { | |
| 220 » » return err | |
| 221 » } | |
| 222 | |
| 223 printSummary(isolSummary) | 101 printSummary(isolSummary) |
| 224 if c.dumpJSON != "" { | 102 if c.dumpJSON != "" { |
| 225 f, err := os.OpenFile(c.dumpJSON, os.O_RDWR|os.O_CREATE|os.O_TRU
NC, 0644) | 103 f, err := os.OpenFile(c.dumpJSON, os.O_RDWR|os.O_CREATE|os.O_TRU
NC, 0644) |
| 226 if err != nil { | 104 if err != nil { |
| 227 return err | 105 return err |
| 228 } | 106 } |
| 229 writeSummaryJSON(f, isolSummary) | 107 writeSummaryJSON(f, isolSummary) |
| 230 f.Close() | 108 f.Close() |
| 231 } | 109 } |
| 232 | 110 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 292 } | 170 } |
| 293 | 171 |
| 294 func hashFile(path string) (isolated.HexDigest, error) { | 172 func hashFile(path string) (isolated.HexDigest, error) { |
| 295 f, err := os.Open(path) | 173 f, err := os.Open(path) |
| 296 if err != nil { | 174 if err != nil { |
| 297 return "", err | 175 return "", err |
| 298 } | 176 } |
| 299 defer f.Close() | 177 defer f.Close() |
| 300 return isolated.Hash(f) | 178 return isolated.Hash(f) |
| 301 } | 179 } |
| OLD | NEW |