| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The LUCI 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 package main |
| 16 |
| 17 import ( |
| 18 "fmt" |
| 19 "log" |
| 20 "os" |
| 21 "path/filepath" |
| 22 |
| 23 humanize "github.com/dustin/go-humanize" |
| 24 "github.com/luci/luci-go/client/internal/common" |
| 25 "github.com/luci/luci-go/client/isolate" |
| 26 "github.com/luci/luci-go/common/isolated" |
| 27 ) |
| 28 |
| 29 // TarringArchiver archives the files specified by an isolate file to the server
, |
| 30 // Small files are combining into tar archives before uploading. |
| 31 type TarringArchiver struct { |
| 32 checker Checker |
| 33 uploader Uploader |
| 34 } |
| 35 |
| 36 // NewTarringArchiver constructs a TarringArchiver. |
| 37 func NewTarringArchiver(checker Checker, uploader Uploader) *TarringArchiver { |
| 38 return &TarringArchiver{checker: checker, uploader: uploader} |
| 39 } |
| 40 |
| 41 // Archive uploads a single isolate. |
| 42 func (ta *TarringArchiver) Archive(archiveOpts *isolate.ArchiveOptions) (Isolate
dSummary, error) { |
| 43 // Parse the incoming isolate file. |
| 44 deps, rootDir, isol, err := isolate.ProcessIsolate(archiveOpts) |
| 45 if err != nil { |
| 46 return IsolatedSummary{}, fmt.Errorf("failed to process isolate:
%v", err) |
| 47 } |
| 48 log.Printf("Isolate %s referenced %d deps", archiveOpts.Isolate, len(dep
s)) |
| 49 |
| 50 parts, err := partitionDeps(deps, rootDir, archiveOpts.Blacklist) |
| 51 if err != nil { |
| 52 return IsolatedSummary{}, fmt.Errorf("partitioning deps: %v", er
r) |
| 53 } |
| 54 |
| 55 log.Printf("Isolate %s expanded to the following items to be isolated:\n
%s", archiveOpts.Isolate, parts) |
| 56 |
| 57 tracker := NewUploadTracker(ta.checker, ta.uploader, isol) |
| 58 if err := tracker.UploadDeps(parts); err != nil { |
| 59 return IsolatedSummary{}, err |
| 60 } |
| 61 return tracker.Finalize(archiveOpts.Isolated) |
| 62 } |
| 63 |
| 64 // Item represents a file or symlink referenced by an isolate file. |
| 65 type Item struct { |
| 66 Path string |
| 67 RelPath string |
| 68 Size int64 |
| 69 Mode os.FileMode |
| 70 |
| 71 Digest isolated.HexDigest |
| 72 } |
| 73 |
| 74 // itemGroup is a list of Items, plus a count of the aggregate size. |
| 75 type itemGroup struct { |
| 76 items []*Item |
| 77 totalSize int64 |
| 78 } |
| 79 |
| 80 func (ig *itemGroup) AddItem(item *Item) { |
| 81 ig.items = append(ig.items, item) |
| 82 ig.totalSize += item.Size |
| 83 } |
| 84 |
| 85 // partitioningWalker contains the state necessary to partition isolate deps by
handling multiple os.WalkFunc invocations. |
| 86 type partitioningWalker struct { |
| 87 // fsView must be initialized before walkFn is called. |
| 88 fsView common.FilesystemView |
| 89 |
| 90 parts partitionedDeps |
| 91 } |
| 92 |
| 93 // partitionedDeps contains a list of items to be archived, partitioned into sym
links and files categorized by size. |
| 94 type partitionedDeps struct { |
| 95 links itemGroup |
| 96 filesToArchive itemGroup |
| 97 indivFiles itemGroup |
| 98 } |
| 99 |
| 100 func (parts partitionedDeps) String() string { |
| 101 str := fmt.Sprintf(" %d symlinks\n", len(parts.links.items)) |
| 102 str += fmt.Sprintf(" %d individual files (total size: %s)\n", len(parts
.indivFiles.items), humanize.Bytes(uint64(parts.indivFiles.totalSize))) |
| 103 str += fmt.Sprintf(" %d files in archives (total size %s)", len(parts.f
ilesToArchive.items), humanize.Bytes(uint64(parts.filesToArchive.totalSize))) |
| 104 return str |
| 105 } |
| 106 |
| 107 // walkFn implements filepath.WalkFunc, for use traversing a directory hierarchy
to be isolated. |
| 108 // It accumulates files in pw.parts, partitioned into symlinks and files categor
ized by size. |
| 109 func (pw *partitioningWalker) walkFn(path string, info os.FileInfo, err error) e
rror { |
| 110 if err != nil { |
| 111 return err |
| 112 } |
| 113 |
| 114 relPath, err := pw.fsView.RelativePath(path) |
| 115 if err != nil { |
| 116 return err |
| 117 } |
| 118 |
| 119 if relPath == "" { // empty string indicates skip. |
| 120 return common.WalkFuncSkipFile(info) |
| 121 } |
| 122 |
| 123 if info.IsDir() { |
| 124 return nil |
| 125 } |
| 126 |
| 127 item := &Item{ |
| 128 Path: path, |
| 129 RelPath: relPath, |
| 130 Mode: info.Mode(), |
| 131 Size: info.Size(), |
| 132 } |
| 133 |
| 134 switch { |
| 135 case item.Mode&os.ModeSymlink == os.ModeSymlink: |
| 136 pw.parts.links.AddItem(item) |
| 137 case item.Size < archiveThreshold: |
| 138 pw.parts.filesToArchive.AddItem(item) |
| 139 default: |
| 140 pw.parts.indivFiles.AddItem(item) |
| 141 } |
| 142 return nil |
| 143 } |
| 144 |
| 145 // partitionDeps walks each of the deps, partioning the results into symlinks an
d files categorized by size. |
| 146 func partitionDeps(deps []string, rootDir string, blacklist []string) (partition
edDeps, error) { |
| 147 fsView, err := common.NewFilesystemView(rootDir, blacklist) |
| 148 if err != nil { |
| 149 return partitionedDeps{}, err |
| 150 } |
| 151 |
| 152 walker := partitioningWalker{fsView: fsView} |
| 153 for _, dep := range deps { |
| 154 // Try to walk dep. If dep is a file (or symlink), the inner fun
ction is called exactly once. |
| 155 if err := filepath.Walk(filepath.Clean(dep), walker.walkFn); err
!= nil { |
| 156 return partitionedDeps{}, err |
| 157 } |
| 158 } |
| 159 return walker.parts, nil |
| 160 } |
| OLD | NEW |