github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/api/fs.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strconv" 8 "sync" 9 "time" 10 ) 11 12 const ( 13 // OriginStart and OriginEnd are the available parameters for the origin 14 // argument when streaming a file. They respectively offset from the start 15 // and end of a file. 16 OriginStart = "start" 17 OriginEnd = "end" 18 ) 19 20 // AllocFileInfo holds information about a file inside the AllocDir 21 type AllocFileInfo struct { 22 Name string 23 IsDir bool 24 Size int64 25 FileMode string 26 ModTime time.Time 27 } 28 29 // StreamFrame is used to frame data of a file when streaming 30 type StreamFrame struct { 31 Offset int64 `json:",omitempty"` 32 Data []byte `json:",omitempty"` 33 File string `json:",omitempty"` 34 FileEvent string `json:",omitempty"` 35 } 36 37 // IsHeartbeat returns if the frame is a heartbeat frame 38 func (s *StreamFrame) IsHeartbeat() bool { 39 return len(s.Data) == 0 && s.FileEvent == "" && s.File == "" && s.Offset == 0 40 } 41 42 // AllocFS is used to introspect an allocation directory on a Nomad client 43 type AllocFS struct { 44 client *Client 45 } 46 47 // AllocFS returns an handle to the AllocFS endpoints 48 func (c *Client) AllocFS() *AllocFS { 49 return &AllocFS{client: c} 50 } 51 52 // List is used to list the files at a given path of an allocation directory 53 func (a *AllocFS) List(alloc *Allocation, path string, q *QueryOptions) ([]*AllocFileInfo, *QueryMeta, error) { 54 nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q) 55 if err != nil { 56 return nil, nil, err 57 } 58 59 if q == nil { 60 q = &QueryOptions{} 61 } 62 if q.Params == nil { 63 q.Params = make(map[string]string) 64 } 65 66 q.Params["path"] = path 67 68 var resp []*AllocFileInfo 69 qm, err := nodeClient.query(fmt.Sprintf("/v1/client/fs/ls/%s", alloc.ID), &resp, q) 70 if err != nil { 71 return nil, nil, err 72 } 73 74 return resp, qm, nil 75 } 76 77 // Stat is used to stat a file at a given path of an allocation directory 78 func (a *AllocFS) Stat(alloc *Allocation, path string, q *QueryOptions) (*AllocFileInfo, *QueryMeta, error) { 79 nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q) 80 if err != nil { 81 return nil, nil, err 82 } 83 84 if q == nil { 85 q = &QueryOptions{} 86 } 87 if q.Params == nil { 88 q.Params = make(map[string]string) 89 } 90 91 q.Params["path"] = path 92 93 var resp AllocFileInfo 94 qm, err := nodeClient.query(fmt.Sprintf("/v1/client/fs/stat/%s", alloc.ID), &resp, q) 95 if err != nil { 96 return nil, nil, err 97 } 98 return &resp, qm, nil 99 } 100 101 // ReadAt is used to read bytes at a given offset until limit at the given path 102 // in an allocation directory. If limit is <= 0, there is no limit. 103 func (a *AllocFS) ReadAt(alloc *Allocation, path string, offset int64, limit int64, q *QueryOptions) (io.ReadCloser, error) { 104 nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q) 105 if err != nil { 106 return nil, err 107 } 108 109 if q == nil { 110 q = &QueryOptions{} 111 } 112 if q.Params == nil { 113 q.Params = make(map[string]string) 114 } 115 116 q.Params["path"] = path 117 q.Params["offset"] = strconv.FormatInt(offset, 10) 118 q.Params["limit"] = strconv.FormatInt(limit, 10) 119 120 r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/readat/%s", alloc.ID), q) 121 if err != nil { 122 return nil, err 123 } 124 return r, nil 125 } 126 127 // Cat is used to read contents of a file at the given path in an allocation 128 // directory 129 func (a *AllocFS) Cat(alloc *Allocation, path string, q *QueryOptions) (io.ReadCloser, error) { 130 nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q) 131 if err != nil { 132 return nil, err 133 } 134 135 if q == nil { 136 q = &QueryOptions{} 137 } 138 if q.Params == nil { 139 q.Params = make(map[string]string) 140 } 141 142 q.Params["path"] = path 143 144 r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/cat/%s", alloc.ID), q) 145 if err != nil { 146 return nil, err 147 } 148 return r, nil 149 } 150 151 // Stream streams the content of a file blocking on EOF. 152 // The parameters are: 153 // * path: path to file to stream. 154 // * offset: The offset to start streaming data at. 155 // * origin: Either "start" or "end" and defines from where the offset is applied. 156 // * cancel: A channel that when closed, streaming will end. 157 // 158 // The return value is a channel that will emit StreamFrames as they are read. 159 func (a *AllocFS) Stream(alloc *Allocation, path, origin string, offset int64, 160 cancel <-chan struct{}, q *QueryOptions) (<-chan *StreamFrame, <-chan error) { 161 162 errCh := make(chan error, 1) 163 nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q) 164 if err != nil { 165 errCh <- err 166 return nil, errCh 167 } 168 169 if q == nil { 170 q = &QueryOptions{} 171 } 172 if q.Params == nil { 173 q.Params = make(map[string]string) 174 } 175 176 q.Params["path"] = path 177 q.Params["offset"] = strconv.FormatInt(offset, 10) 178 q.Params["origin"] = origin 179 180 r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/stream/%s", alloc.ID), q) 181 if err != nil { 182 errCh <- err 183 return nil, errCh 184 } 185 186 // Create the output channel 187 frames := make(chan *StreamFrame, 10) 188 189 go func() { 190 // Close the body 191 defer r.Close() 192 193 // Create a decoder 194 dec := json.NewDecoder(r) 195 196 for { 197 // Check if we have been cancelled 198 select { 199 case <-cancel: 200 return 201 default: 202 } 203 204 // Decode the next frame 205 var frame StreamFrame 206 if err := dec.Decode(&frame); err != nil { 207 errCh <- err 208 close(frames) 209 return 210 } 211 212 // Discard heartbeat frames 213 if frame.IsHeartbeat() { 214 continue 215 } 216 217 frames <- &frame 218 } 219 }() 220 221 return frames, errCh 222 } 223 224 // Logs streams the content of a tasks logs blocking on EOF. 225 // The parameters are: 226 // * allocation: the allocation to stream from. 227 // * follow: Whether the logs should be followed. 228 // * task: the tasks name to stream logs for. 229 // * logType: Either "stdout" or "stderr" 230 // * origin: Either "start" or "end" and defines from where the offset is applied. 231 // * offset: The offset to start streaming data at. 232 // * cancel: A channel that when closed, streaming will end. 233 // 234 // The return value is a channel that will emit StreamFrames as they are read. 235 func (a *AllocFS) Logs(alloc *Allocation, follow bool, task, logType, origin string, 236 offset int64, cancel <-chan struct{}, q *QueryOptions) (<-chan *StreamFrame, <-chan error) { 237 238 errCh := make(chan error, 1) 239 nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q) 240 if err != nil { 241 errCh <- err 242 return nil, errCh 243 } 244 245 if q == nil { 246 q = &QueryOptions{} 247 } 248 if q.Params == nil { 249 q.Params = make(map[string]string) 250 } 251 252 q.Params["follow"] = strconv.FormatBool(follow) 253 q.Params["task"] = task 254 q.Params["type"] = logType 255 q.Params["origin"] = origin 256 q.Params["offset"] = strconv.FormatInt(offset, 10) 257 258 r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/logs/%s", alloc.ID), q) 259 if err != nil { 260 errCh <- err 261 return nil, errCh 262 } 263 264 // Create the output channel 265 frames := make(chan *StreamFrame, 10) 266 267 go func() { 268 // Close the body 269 defer r.Close() 270 271 // Create a decoder 272 dec := json.NewDecoder(r) 273 274 for { 275 // Check if we have been cancelled 276 select { 277 case <-cancel: 278 return 279 default: 280 } 281 282 // Decode the next frame 283 var frame StreamFrame 284 if err := dec.Decode(&frame); err != nil { 285 errCh <- err 286 close(frames) 287 return 288 } 289 290 // Discard heartbeat frames 291 if frame.IsHeartbeat() { 292 continue 293 } 294 295 frames <- &frame 296 } 297 }() 298 299 return frames, errCh 300 } 301 302 // FrameReader is used to convert a stream of frames into a read closer. 303 type FrameReader struct { 304 frames <-chan *StreamFrame 305 errCh <-chan error 306 cancelCh chan struct{} 307 308 closedLock sync.Mutex 309 closed bool 310 311 unblockTime time.Duration 312 313 frame *StreamFrame 314 frameOffset int 315 316 byteOffset int 317 } 318 319 // NewFrameReader takes a channel of frames and returns a FrameReader which 320 // implements io.ReadCloser 321 func NewFrameReader(frames <-chan *StreamFrame, errCh <-chan error, cancelCh chan struct{}) *FrameReader { 322 return &FrameReader{ 323 frames: frames, 324 errCh: errCh, 325 cancelCh: cancelCh, 326 } 327 } 328 329 // SetUnblockTime sets the time to unblock and return zero bytes read. If the 330 // duration is unset or is zero or less, the read will block til data is read. 331 func (f *FrameReader) SetUnblockTime(d time.Duration) { 332 f.unblockTime = d 333 } 334 335 // Offset returns the offset into the stream. 336 func (f *FrameReader) Offset() int { 337 return f.byteOffset 338 } 339 340 // Read reads the data of the incoming frames into the bytes buffer. Returns EOF 341 // when there are no more frames. 342 func (f *FrameReader) Read(p []byte) (n int, err error) { 343 f.closedLock.Lock() 344 closed := f.closed 345 f.closedLock.Unlock() 346 if closed { 347 return 0, io.EOF 348 } 349 350 if f.frame == nil { 351 var unblock <-chan time.Time 352 if f.unblockTime.Nanoseconds() > 0 { 353 unblock = time.After(f.unblockTime) 354 } 355 356 select { 357 case frame, ok := <-f.frames: 358 if !ok { 359 return 0, io.EOF 360 } 361 f.frame = frame 362 363 // Store the total offset into the file 364 f.byteOffset = int(f.frame.Offset) 365 case <-unblock: 366 return 0, nil 367 case err := <-f.errCh: 368 return 0, err 369 case <-f.cancelCh: 370 return 0, io.EOF 371 } 372 } 373 374 // Copy the data out of the frame and update our offset 375 n = copy(p, f.frame.Data[f.frameOffset:]) 376 f.frameOffset += n 377 378 // Clear the frame and its offset once we have read everything 379 if len(f.frame.Data) == f.frameOffset { 380 f.frame = nil 381 f.frameOffset = 0 382 } 383 384 return n, nil 385 } 386 387 // Close cancels the stream of frames 388 func (f *FrameReader) Close() error { 389 f.closedLock.Lock() 390 defer f.closedLock.Unlock() 391 if f.closed { 392 return nil 393 } 394 395 close(f.cancelCh) 396 f.closed = true 397 return nil 398 }