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