github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/client_fs_endpoint.go (about) 1 package nomad 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net" 8 "strings" 9 "time" 10 11 metrics "github.com/armon/go-metrics" 12 "github.com/hashicorp/nomad/acl" 13 cstructs "github.com/hashicorp/nomad/client/structs" 14 "github.com/hashicorp/nomad/helper" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/ugorji/go/codec" 17 ) 18 19 // FileSystem endpoint is used for accessing the logs and filesystem of 20 // allocations from a Node. 21 type FileSystem struct { 22 srv *Server 23 } 24 25 func (f *FileSystem) register() { 26 f.srv.streamingRpcs.Register("FileSystem.Logs", f.logs) 27 f.srv.streamingRpcs.Register("FileSystem.Stream", f.stream) 28 } 29 30 // handleStreamResultError is a helper for sending an error with a potential 31 // error code. The transmission of the error is ignored if the error has been 32 // generated by the closing of the underlying transport. 33 func (f *FileSystem) handleStreamResultError(err error, code *int64, encoder *codec.Encoder) { 34 // Nothing to do as the conn is closed 35 if err == io.EOF || strings.Contains(err.Error(), "closed") { 36 return 37 } 38 39 // Attempt to send the error 40 encoder.Encode(&cstructs.StreamErrWrapper{ 41 Error: cstructs.NewRpcError(err, code), 42 }) 43 } 44 45 // forwardRegionStreamingRpc is used to make a streaming RPC to a different 46 // region. It looks up the allocation in the remote region to determine what 47 // remote server can route the request. 48 func (f *FileSystem) forwardRegionStreamingRpc(conn io.ReadWriteCloser, 49 encoder *codec.Encoder, args interface{}, method, allocID string, qo *structs.QueryOptions) { 50 // Request the allocation from the target region 51 allocReq := &structs.AllocSpecificRequest{ 52 AllocID: allocID, 53 QueryOptions: *qo, 54 } 55 var allocResp structs.SingleAllocResponse 56 if err := f.srv.forwardRegion(qo.RequestRegion(), "Alloc.GetAlloc", allocReq, &allocResp); err != nil { 57 f.handleStreamResultError(err, nil, encoder) 58 return 59 } 60 61 if allocResp.Alloc == nil { 62 f.handleStreamResultError(structs.NewErrUnknownAllocation(allocID), helper.Int64ToPtr(404), encoder) 63 return 64 } 65 66 // Determine the Server that has a connection to the node. 67 srv, err := f.srv.serverWithNodeConn(allocResp.Alloc.NodeID, qo.RequestRegion()) 68 if err != nil { 69 var code *int64 70 if structs.IsErrNoNodeConn(err) { 71 code = helper.Int64ToPtr(404) 72 } 73 f.handleStreamResultError(err, code, encoder) 74 return 75 } 76 77 // Get a connection to the server 78 srvConn, err := f.srv.streamingRpc(srv, method) 79 if err != nil { 80 f.handleStreamResultError(err, nil, encoder) 81 return 82 } 83 defer srvConn.Close() 84 85 // Send the request. 86 outEncoder := codec.NewEncoder(srvConn, structs.MsgpackHandle) 87 if err := outEncoder.Encode(args); err != nil { 88 f.handleStreamResultError(err, nil, encoder) 89 return 90 } 91 92 structs.Bridge(conn, srvConn) 93 } 94 95 // List is used to list the contents of an allocation's directory. 96 func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListResponse) error { 97 // We only allow stale reads since the only potentially stale information is 98 // the Node registration and the cost is fairly high for adding another hope 99 // in the forwarding chain. 100 args.QueryOptions.AllowStale = true 101 102 // Potentially forward to a different region. 103 if done, err := f.srv.forward("FileSystem.List", args, args, reply); done { 104 return err 105 } 106 defer metrics.MeasureSince([]string{"nomad", "file_system", "list"}, time.Now()) 107 108 // Check filesystem read permissions 109 if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil { 110 return err 111 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) { 112 return structs.ErrPermissionDenied 113 } 114 115 // Verify the arguments. 116 if args.AllocID == "" { 117 return errors.New("missing allocation ID") 118 } 119 120 // Lookup the allocation 121 snap, err := f.srv.State().Snapshot() 122 if err != nil { 123 return err 124 } 125 126 alloc, err := snap.AllocByID(nil, args.AllocID) 127 if err != nil { 128 return err 129 } 130 if alloc == nil { 131 return structs.NewErrUnknownAllocation(args.AllocID) 132 } 133 134 // Make sure Node is valid and new enough to support RPC 135 _, err = getNodeForRpc(snap, alloc.NodeID) 136 if err != nil { 137 return err 138 } 139 140 // Get the connection to the client 141 state, ok := f.srv.getNodeConn(alloc.NodeID) 142 if !ok { 143 return findNodeConnAndForward(f.srv, alloc.NodeID, "FileSystem.List", args, reply) 144 } 145 146 // Make the RPC 147 return NodeRpc(state.Session, "FileSystem.List", args, reply) 148 } 149 150 // Stat is used to stat a file in the allocation's directory. 151 func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatResponse) error { 152 // We only allow stale reads since the only potentially stale information is 153 // the Node registration and the cost is fairly high for adding another hope 154 // in the forwarding chain. 155 args.QueryOptions.AllowStale = true 156 157 // Potentially forward to a different region. 158 if done, err := f.srv.forward("FileSystem.Stat", args, args, reply); done { 159 return err 160 } 161 defer metrics.MeasureSince([]string{"nomad", "file_system", "stat"}, time.Now()) 162 163 // Check filesystem read permissions 164 if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil { 165 return err 166 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) { 167 return structs.ErrPermissionDenied 168 } 169 170 // Verify the arguments. 171 if args.AllocID == "" { 172 return errors.New("missing allocation ID") 173 } 174 175 // Lookup the allocation 176 snap, err := f.srv.State().Snapshot() 177 if err != nil { 178 return err 179 } 180 181 alloc, err := snap.AllocByID(nil, args.AllocID) 182 if err != nil { 183 return err 184 } 185 if alloc == nil { 186 return structs.NewErrUnknownAllocation(args.AllocID) 187 } 188 189 // Make sure Node is valid and new enough to support RPC 190 _, err = getNodeForRpc(snap, alloc.NodeID) 191 if err != nil { 192 return err 193 } 194 195 // Get the connection to the client 196 state, ok := f.srv.getNodeConn(alloc.NodeID) 197 if !ok { 198 return findNodeConnAndForward(f.srv, alloc.NodeID, "FileSystem.Stat", args, reply) 199 } 200 201 // Make the RPC 202 return NodeRpc(state.Session, "FileSystem.Stat", args, reply) 203 } 204 205 // stream is is used to stream the contents of file in an allocation's 206 // directory. 207 func (f *FileSystem) stream(conn io.ReadWriteCloser) { 208 defer conn.Close() 209 defer metrics.MeasureSince([]string{"nomad", "file_system", "stream"}, time.Now()) 210 211 // Decode the arguments 212 var args cstructs.FsStreamRequest 213 decoder := codec.NewDecoder(conn, structs.MsgpackHandle) 214 encoder := codec.NewEncoder(conn, structs.MsgpackHandle) 215 216 if err := decoder.Decode(&args); err != nil { 217 f.handleStreamResultError(err, helper.Int64ToPtr(500), encoder) 218 return 219 } 220 221 // Check if we need to forward to a different region 222 if r := args.RequestRegion(); r != f.srv.Region() { 223 f.forwardRegionStreamingRpc(conn, encoder, &args, "FileSystem.Stream", 224 args.AllocID, &args.QueryOptions) 225 return 226 } 227 228 // Check node read permissions 229 if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil { 230 f.handleStreamResultError(err, nil, encoder) 231 return 232 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) { 233 f.handleStreamResultError(structs.ErrPermissionDenied, nil, encoder) 234 return 235 } 236 237 // Verify the arguments. 238 if args.AllocID == "" { 239 f.handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder) 240 return 241 } 242 243 // Retrieve the allocation 244 snap, err := f.srv.State().Snapshot() 245 if err != nil { 246 f.handleStreamResultError(err, nil, encoder) 247 return 248 } 249 250 alloc, err := snap.AllocByID(nil, args.AllocID) 251 if err != nil { 252 f.handleStreamResultError(err, nil, encoder) 253 return 254 } 255 if alloc == nil { 256 f.handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder) 257 return 258 } 259 nodeID := alloc.NodeID 260 261 // Make sure Node is valid and new enough to support RPC 262 node, err := snap.NodeByID(nil, nodeID) 263 if err != nil { 264 f.handleStreamResultError(err, helper.Int64ToPtr(500), encoder) 265 return 266 } 267 268 if node == nil { 269 err := fmt.Errorf("Unknown node %q", nodeID) 270 f.handleStreamResultError(err, helper.Int64ToPtr(400), encoder) 271 return 272 } 273 274 if err := nodeSupportsRpc(node); err != nil { 275 f.handleStreamResultError(err, helper.Int64ToPtr(400), encoder) 276 return 277 } 278 279 // Get the connection to the client either by forwarding to another server 280 // or creating a direct stream 281 var clientConn net.Conn 282 state, ok := f.srv.getNodeConn(nodeID) 283 if !ok { 284 // Determine the Server that has a connection to the node. 285 srv, err := f.srv.serverWithNodeConn(nodeID, f.srv.Region()) 286 if err != nil { 287 var code *int64 288 if structs.IsErrNoNodeConn(err) { 289 code = helper.Int64ToPtr(404) 290 } 291 f.handleStreamResultError(err, code, encoder) 292 } 293 294 // Get a connection to the server 295 conn, err := f.srv.streamingRpc(srv, "FileSystem.Stream") 296 if err != nil { 297 f.handleStreamResultError(err, nil, encoder) 298 return 299 } 300 301 clientConn = conn 302 } else { 303 stream, err := NodeStreamingRpc(state.Session, "FileSystem.Stream") 304 if err != nil { 305 f.handleStreamResultError(err, nil, encoder) 306 return 307 } 308 clientConn = stream 309 } 310 defer clientConn.Close() 311 312 // Send the request. 313 outEncoder := codec.NewEncoder(clientConn, structs.MsgpackHandle) 314 if err := outEncoder.Encode(args); err != nil { 315 f.handleStreamResultError(err, nil, encoder) 316 return 317 } 318 319 structs.Bridge(conn, clientConn) 320 return 321 } 322 323 // logs is used to access an task's logs for a given allocation 324 func (f *FileSystem) logs(conn io.ReadWriteCloser) { 325 defer conn.Close() 326 defer metrics.MeasureSince([]string{"nomad", "file_system", "logs"}, time.Now()) 327 328 // Decode the arguments 329 var args cstructs.FsLogsRequest 330 decoder := codec.NewDecoder(conn, structs.MsgpackHandle) 331 encoder := codec.NewEncoder(conn, structs.MsgpackHandle) 332 333 if err := decoder.Decode(&args); err != nil { 334 f.handleStreamResultError(err, helper.Int64ToPtr(500), encoder) 335 return 336 } 337 338 // Check if we need to forward to a different region 339 if r := args.RequestRegion(); r != f.srv.Region() { 340 f.forwardRegionStreamingRpc(conn, encoder, &args, "FileSystem.Logs", 341 args.AllocID, &args.QueryOptions) 342 return 343 } 344 345 // Check node read permissions 346 if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil { 347 f.handleStreamResultError(err, nil, encoder) 348 return 349 } else if aclObj != nil { 350 readfs := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityReadFS) 351 logs := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityReadLogs) 352 if !readfs && !logs { 353 f.handleStreamResultError(structs.ErrPermissionDenied, nil, encoder) 354 return 355 } 356 } 357 358 // Verify the arguments. 359 if args.AllocID == "" { 360 f.handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder) 361 return 362 } 363 364 // Retrieve the allocation 365 snap, err := f.srv.State().Snapshot() 366 if err != nil { 367 f.handleStreamResultError(err, nil, encoder) 368 return 369 } 370 371 alloc, err := snap.AllocByID(nil, args.AllocID) 372 if err != nil { 373 f.handleStreamResultError(err, nil, encoder) 374 return 375 } 376 if alloc == nil { 377 f.handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder) 378 return 379 } 380 nodeID := alloc.NodeID 381 382 // Make sure Node is valid and new enough to support RPC 383 node, err := snap.NodeByID(nil, nodeID) 384 if err != nil { 385 f.handleStreamResultError(err, helper.Int64ToPtr(500), encoder) 386 return 387 } 388 389 if node == nil { 390 err := fmt.Errorf("Unknown node %q", nodeID) 391 f.handleStreamResultError(err, helper.Int64ToPtr(400), encoder) 392 return 393 } 394 395 if err := nodeSupportsRpc(node); err != nil { 396 f.handleStreamResultError(err, helper.Int64ToPtr(400), encoder) 397 return 398 } 399 400 // Get the connection to the client either by forwarding to another server 401 // or creating a direct stream 402 var clientConn net.Conn 403 state, ok := f.srv.getNodeConn(nodeID) 404 if !ok { 405 // Determine the Server that has a connection to the node. 406 srv, err := f.srv.serverWithNodeConn(nodeID, f.srv.Region()) 407 if err != nil { 408 var code *int64 409 if structs.IsErrNoNodeConn(err) { 410 code = helper.Int64ToPtr(404) 411 } 412 f.handleStreamResultError(err, code, encoder) 413 return 414 } 415 416 // Get a connection to the server 417 conn, err := f.srv.streamingRpc(srv, "FileSystem.Logs") 418 if err != nil { 419 f.handleStreamResultError(err, nil, encoder) 420 return 421 } 422 423 clientConn = conn 424 } else { 425 stream, err := NodeStreamingRpc(state.Session, "FileSystem.Logs") 426 if err != nil { 427 f.handleStreamResultError(err, nil, encoder) 428 return 429 } 430 clientConn = stream 431 } 432 defer clientConn.Close() 433 434 // Send the request. 435 outEncoder := codec.NewEncoder(clientConn, structs.MsgpackHandle) 436 if err := outEncoder.Encode(args); err != nil { 437 f.handleStreamResultError(err, nil, encoder) 438 return 439 } 440 441 structs.Bridge(conn, clientConn) 442 return 443 }