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