blob: a1bedb8d238cb5ac4db545647dc21d68c3d7c600 [file] [log] [blame]
Jiri Simsa51d78fc2014-07-09 18:34:08 -07001// Package binary provides a client-side library for the binary
2// repository.
3//
4// TODO(jsimsa): Implement parallel download and upload.
5package binary
6
7import (
8 "bytes"
9 "crypto/md5"
10 "encoding/hex"
11 "io"
12 "io/ioutil"
13 "os"
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070014 "time"
Jiri Simsa51d78fc2014-07-09 18:34:08 -070015
Jiri Simsa519c5072014-09-17 21:37:57 -070016 "veyron.io/veyron/veyron2/context"
Jiri Simsa519c5072014-09-17 21:37:57 -070017 "veyron.io/veyron/veyron2/services/mgmt/binary"
18 "veyron.io/veyron/veyron2/services/mgmt/repository"
19 "veyron.io/veyron/veyron2/verror"
20 "veyron.io/veyron/veyron2/vlog"
Jiri Simsa51d78fc2014-07-09 18:34:08 -070021)
22
23var (
24 errOperationFailed = verror.Internalf("operation failed")
Tilak Sharma492e8e92014-09-18 10:58:14 -070025 errNotExist = verror.NoExistf("binary does not exist")
Jiri Simsa51d78fc2014-07-09 18:34:08 -070026)
27
28const (
29 nAttempts = 2
30 partSize = 1 << 22
31 subpartSize = 1 << 12
32)
33
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -080034func Delete(ctx context.T, name string) error {
35 ctx, cancel := ctx.WithTimeout(time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070036 defer cancel()
Todd Wang702385a2014-11-07 01:54:08 -080037 if err := repository.BinaryClient(name).Delete(ctx); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -070038 vlog.Errorf("Delete() failed: %v", err)
39 return err
40 }
41 return nil
42}
43
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070044func download(ctx context.T, w io.WriteSeeker, von string) error {
Todd Wang702385a2014-11-07 01:54:08 -080045 client := repository.BinaryClient(von)
Robin Thellend64178ed2014-11-20 13:16:22 -080046 // TODO(rthellend): Use the media type.
47 parts, _, err := client.Stat(ctx)
Jiri Simsa51d78fc2014-07-09 18:34:08 -070048 if err != nil {
49 vlog.Errorf("Stat() failed: %v", err)
50 return err
51 }
52 for _, part := range parts {
53 if part.Checksum == binary.MissingChecksum {
54 return errNotExist
55 }
56 }
57 offset, whence := int64(0), 0
58 for i, part := range parts {
59 success := false
60 download:
61 for j := 0; !success && j < nAttempts; j++ {
62 if _, err := w.Seek(offset, whence); err != nil {
63 vlog.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
64 continue
65 }
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070066 stream, err := client.Download(ctx, int32(i))
Jiri Simsa51d78fc2014-07-09 18:34:08 -070067 if err != nil {
68 vlog.Errorf("Download(%v) failed: %v", i, err)
69 continue
70 }
71 h, nreceived := md5.New(), 0
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -070072 rStream := stream.RecvStream()
73 for rStream.Advance() {
74 bytes := rStream.Value()
Jiri Simsa51d78fc2014-07-09 18:34:08 -070075 if _, err := w.Write(bytes); err != nil {
76 vlog.Errorf("Write() failed: %v", err)
77 stream.Cancel()
78 continue download
79 }
80 h.Write(bytes)
81 nreceived += len(bytes)
82 }
Shyam Jayaramanc4aed6e2014-07-22 14:25:06 -070083
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -070084 if err := rStream.Err(); err != nil {
85 vlog.Errorf("Advance() failed: %v", err)
Shyam Jayaramanc4aed6e2014-07-22 14:25:06 -070086 stream.Cancel()
87 continue download
88
89 }
Jiri Simsa51d78fc2014-07-09 18:34:08 -070090 if err := stream.Finish(); err != nil {
91 vlog.Errorf("Finish() failed: %v", err)
92 continue
93 }
94 if expected, got := part.Checksum, hex.EncodeToString(h.Sum(nil)); expected != got {
95 vlog.Errorf("Unexpected checksum: expected %v, got %v", expected, got)
96 continue
97 }
98 if expected, got := part.Size, int64(nreceived); expected != got {
99 vlog.Errorf("Unexpected size: expected %v, got %v", expected, got)
100 continue
101 }
102 success = true
103 }
104 if !success {
105 return errOperationFailed
106 }
107 offset += part.Size
108 }
109 return nil
110}
111
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800112func Download(ctx context.T, von string) ([]byte, error) {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700113 dir, prefix := "", ""
114 file, err := ioutil.TempFile(dir, prefix)
115 if err != nil {
116 vlog.Errorf("TempFile(%v, %v) failed: %v", dir, prefix, err)
117 return nil, errOperationFailed
118 }
119 defer os.Remove(file.Name())
120 defer file.Close()
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800121 ctx, cancel := ctx.WithTimeout(time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700122 defer cancel()
123 if err := download(ctx, file, von); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700124 return nil, errOperationFailed
125 }
126 bytes, err := ioutil.ReadFile(file.Name())
127 if err != nil {
128 vlog.Errorf("ReadFile(%v) failed: %v", file.Name(), err)
129 return nil, errOperationFailed
130 }
131 return bytes, nil
132}
133
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800134func DownloadToFile(ctx context.T, von, path string) error {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700135 dir, prefix := "", ""
136 file, err := ioutil.TempFile(dir, prefix)
137 if err != nil {
138 vlog.Errorf("TempFile(%v, %v) failed: %v", dir, prefix, err)
139 return errOperationFailed
140 }
141 defer file.Close()
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800142 ctx, cancel := ctx.WithTimeout(time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700143 defer cancel()
144 if err := download(ctx, file, von); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700145 if err := os.Remove(file.Name()); err != nil {
146 vlog.Errorf("Remove(%v) failed: %v", file.Name(), err)
147 }
148 return errOperationFailed
149 }
150 perm := os.FileMode(0700)
151 if err := file.Chmod(perm); err != nil {
152 vlog.Errorf("Chmod(%v) failed: %v", perm, err)
153 if err := os.Remove(file.Name()); err != nil {
154 vlog.Errorf("Remove(%v) failed: %v", file.Name(), err)
155 }
156 return errOperationFailed
157 }
158 if err := os.Rename(file.Name(), path); err != nil {
159 vlog.Errorf("Rename(%v, %v) failed: %v", file.Name(), path, err)
160 if err := os.Remove(file.Name()); err != nil {
161 vlog.Errorf("Remove(%v) failed: %v", file.Name(), err)
162 }
163 return errOperationFailed
164 }
165 return nil
166}
167
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700168func upload(ctx context.T, r io.ReadSeeker, von string) error {
Todd Wang702385a2014-11-07 01:54:08 -0800169 client := repository.BinaryClient(von)
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700170 offset, whence := int64(0), 2
171 size, err := r.Seek(offset, whence)
172 if err != nil {
173 vlog.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
174 return errOperationFailed
175 }
176 nparts := (size-1)/partSize + 1
Robin Thellend64178ed2014-11-20 13:16:22 -0800177 // TODO(rthellend): Determine the actual media type.
178 mediaType := "application/octet-stream"
179 if err := client.Create(ctx, int32(nparts), mediaType); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700180 vlog.Errorf("Create() failed: %v", err)
181 return err
182 }
183 for i := 0; int64(i) < nparts; i++ {
184 success := false
185 upload:
186 for j := 0; !success && j < nAttempts; j++ {
187 offset, whence := int64(i*partSize), 0
188 if _, err := r.Seek(offset, whence); err != nil {
189 vlog.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
190 continue
191 }
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700192 stream, err := client.Upload(ctx, int32(i))
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700193 if err != nil {
194 vlog.Errorf("Upload(%v) failed: %v", i, err)
195 continue
196 }
197 buffer := make([]byte, partSize)
198 if int64(i+1) == nparts {
199 buffer = buffer[:(size % partSize)]
200 }
201 nread := 0
202 for nread < len(buffer) {
203 n, err := r.Read(buffer[nread:])
204 nread += n
205 if err != nil && (err != io.EOF || nread < len(buffer)) {
206 vlog.Errorf("Read() failed: %v", err)
207 stream.Cancel()
208 continue upload
209 }
210 }
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -0700211 sender := stream.SendStream()
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700212 for from := 0; from < len(buffer); from += subpartSize {
213 to := from + subpartSize
214 if to > len(buffer) {
215 to = len(buffer)
216 }
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -0700217 if err := sender.Send(buffer[from:to]); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700218 vlog.Errorf("Send() failed: %v", err)
219 stream.Cancel()
220 continue upload
221 }
222 }
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -0700223 if err := sender.Close(); err != nil {
224 vlog.Errorf("Close() failed: %v", err)
Robin Thellend64178ed2014-11-20 13:16:22 -0800225 parts, _, statErr := client.Stat(ctx)
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700226 if statErr != nil {
227 vlog.Errorf("Stat() failed: %v", statErr)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700228 if deleteErr := client.Delete(ctx); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700229 vlog.Errorf("Delete() failed: %v", deleteErr)
230 }
231 return err
232 }
233 if parts[i].Checksum == binary.MissingChecksum {
234 stream.Cancel()
235 continue
236 }
237 }
238 if err := stream.Finish(); err != nil {
239 vlog.Errorf("Finish() failed: %v", err)
Robin Thellend64178ed2014-11-20 13:16:22 -0800240 parts, _, statErr := client.Stat(ctx)
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700241 if statErr != nil {
242 vlog.Errorf("Stat() failed: %v", statErr)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700243 if deleteErr := client.Delete(ctx); err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700244 vlog.Errorf("Delete() failed: %v", deleteErr)
245 }
246 return err
247 }
248 if parts[i].Checksum == binary.MissingChecksum {
249 continue
250 }
251 }
252 success = true
253 }
254 if !success {
255 return errOperationFailed
256 }
257 }
258 return nil
259}
260
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800261func Upload(ctx context.T, von string, data []byte) error {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700262 buffer := bytes.NewReader(data)
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800263 ctx, cancel := ctx.WithTimeout(time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700264 defer cancel()
265 return upload(ctx, buffer, von)
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700266}
267
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800268func UploadFromFile(ctx context.T, von, path string) error {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700269 file, err := os.Open(path)
270 defer file.Close()
271 if err != nil {
272 vlog.Errorf("Open(%v) failed: %v", err)
273 return errOperationFailed
274 }
Matt Rosencrantzc2ed03e2014-11-25 15:40:48 -0800275 ctx, cancel := ctx.WithTimeout(time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700276 defer cancel()
277 return upload(ctx, file, von)
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700278}