github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/alloc_endpoint.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "time" 10 11 metrics "github.com/armon/go-metrics" 12 "github.com/hashicorp/go-msgpack/codec" 13 "github.com/hashicorp/nomad/acl" 14 cstructs "github.com/hashicorp/nomad/client/structs" 15 "github.com/hashicorp/nomad/helper" 16 "github.com/hashicorp/nomad/helper/uuid" 17 "github.com/hashicorp/nomad/nomad/structs" 18 nstructs "github.com/hashicorp/nomad/nomad/structs" 19 "github.com/hashicorp/nomad/plugins/drivers" 20 ) 21 22 // Allocations endpoint is used for interacting with client allocations 23 type Allocations struct { 24 c *Client 25 } 26 27 func NewAllocationsEndpoint(c *Client) *Allocations { 28 a := &Allocations{c: c} 29 a.c.streamingRpcs.Register("Allocations.Exec", a.exec) 30 return a 31 } 32 33 // GarbageCollectAll is used to garbage collect all allocations on a client. 34 func (a *Allocations) GarbageCollectAll(args *nstructs.NodeSpecificRequest, reply *nstructs.GenericResponse) error { 35 defer metrics.MeasureSince([]string{"client", "allocations", "garbage_collect_all"}, time.Now()) 36 37 // Check node write permissions 38 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 39 return err 40 } else if aclObj != nil && !aclObj.AllowNodeWrite() { 41 return nstructs.ErrPermissionDenied 42 } 43 44 a.c.CollectAllAllocs() 45 return nil 46 } 47 48 // GarbageCollect is used to garbage collect an allocation on a client. 49 func (a *Allocations) GarbageCollect(args *nstructs.AllocSpecificRequest, reply *nstructs.GenericResponse) error { 50 defer metrics.MeasureSince([]string{"client", "allocations", "garbage_collect"}, time.Now()) 51 52 alloc, err := a.c.GetAlloc(args.AllocID) 53 if err != nil { 54 return err 55 } 56 57 // Check namespace submit job permission. 58 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 59 return err 60 } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilitySubmitJob) { 61 return nstructs.ErrPermissionDenied 62 } 63 64 if !a.c.CollectAllocation(args.AllocID) { 65 return fmt.Errorf("No such allocation on client, or allocation not eligible for GC") 66 } 67 68 return nil 69 } 70 71 // Signal is used to send a signal to an allocation's tasks on a client. 72 func (a *Allocations) Signal(args *nstructs.AllocSignalRequest, reply *nstructs.GenericResponse) error { 73 defer metrics.MeasureSince([]string{"client", "allocations", "signal"}, time.Now()) 74 75 alloc, err := a.c.GetAlloc(args.AllocID) 76 if err != nil { 77 return err 78 } 79 80 // Check namespace alloc-lifecycle permission. 81 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 82 return err 83 } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) { 84 return nstructs.ErrPermissionDenied 85 } 86 87 return a.c.SignalAllocation(args.AllocID, args.Task, args.Signal) 88 } 89 90 // Restart is used to trigger a restart of an allocation or a subtask on a client. 91 func (a *Allocations) Restart(args *nstructs.AllocRestartRequest, reply *nstructs.GenericResponse) error { 92 defer metrics.MeasureSince([]string{"client", "allocations", "restart"}, time.Now()) 93 94 alloc, err := a.c.GetAlloc(args.AllocID) 95 if err != nil { 96 return err 97 } 98 99 // Check namespace alloc-lifecycle permission. 100 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 101 return err 102 } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) { 103 return nstructs.ErrPermissionDenied 104 } 105 106 return a.c.RestartAllocation(args.AllocID, args.TaskName) 107 } 108 109 // Stats is used to collect allocation statistics 110 func (a *Allocations) Stats(args *cstructs.AllocStatsRequest, reply *cstructs.AllocStatsResponse) error { 111 defer metrics.MeasureSince([]string{"client", "allocations", "stats"}, time.Now()) 112 113 alloc, err := a.c.GetAlloc(args.AllocID) 114 if err != nil { 115 return err 116 } 117 118 // Check read-job permission. 119 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 120 return err 121 } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) { 122 return nstructs.ErrPermissionDenied 123 } 124 125 clientStats := a.c.StatsReporter() 126 aStats, err := clientStats.GetAllocStats(args.AllocID) 127 if err != nil { 128 return err 129 } 130 131 stats, err := aStats.LatestAllocStats(args.Task) 132 if err != nil { 133 return err 134 } 135 136 reply.Stats = stats 137 return nil 138 } 139 140 // exec is used to execute command in a running task 141 func (a *Allocations) exec(conn io.ReadWriteCloser) { 142 defer metrics.MeasureSince([]string{"client", "allocations", "exec"}, time.Now()) 143 defer conn.Close() 144 145 execID := uuid.Generate() 146 decoder := codec.NewDecoder(conn, structs.MsgpackHandle) 147 encoder := codec.NewEncoder(conn, structs.MsgpackHandle) 148 149 code, err := a.execImpl(encoder, decoder, execID) 150 if err != nil { 151 a.c.logger.Info("task exec session ended with an error", "error", err, "code", code) 152 handleStreamResultError(err, code, encoder) 153 return 154 } 155 156 a.c.logger.Info("task exec session ended", "exec_id", execID) 157 } 158 159 func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, execID string) (code *int64, err error) { 160 161 // Decode the arguments 162 var req cstructs.AllocExecRequest 163 if err := decoder.Decode(&req); err != nil { 164 return helper.Int64ToPtr(500), err 165 } 166 167 if a.c.GetConfig().DisableRemoteExec { 168 return nil, structs.ErrPermissionDenied 169 } 170 171 if req.AllocID == "" { 172 return helper.Int64ToPtr(400), allocIDNotPresentErr 173 } 174 ar, err := a.c.getAllocRunner(req.AllocID) 175 if err != nil { 176 code := helper.Int64ToPtr(500) 177 if structs.IsErrUnknownAllocation(err) { 178 code = helper.Int64ToPtr(404) 179 } 180 181 return code, err 182 } 183 alloc := ar.Alloc() 184 185 aclObj, token, err := a.c.resolveTokenAndACL(req.QueryOptions.AuthToken) 186 { 187 // log access 188 tokenName, tokenID := "", "" 189 if token != nil { 190 tokenName, tokenID = token.Name, token.AccessorID 191 } 192 193 a.c.logger.Info("task exec session starting", 194 "exec_id", execID, 195 "alloc_id", req.AllocID, 196 "task", req.Task, 197 "command", req.Cmd, 198 "tty", req.Tty, 199 "access_token_name", tokenName, 200 "access_token_id", tokenID, 201 ) 202 } 203 204 // Check alloc-exec permission. 205 if err != nil { 206 return nil, err 207 } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocExec) { 208 return nil, structs.ErrPermissionDenied 209 } 210 211 // Validate the arguments 212 if req.Task == "" { 213 return helper.Int64ToPtr(400), taskNotPresentErr 214 } 215 if len(req.Cmd) == 0 { 216 return helper.Int64ToPtr(400), errors.New("command is not present") 217 } 218 219 capabilities, err := ar.GetTaskDriverCapabilities(req.Task) 220 if err != nil { 221 code := helper.Int64ToPtr(500) 222 if structs.IsErrUnknownAllocation(err) { 223 code = helper.Int64ToPtr(404) 224 } 225 226 return code, err 227 } 228 229 // check node access 230 if aclObj != nil && capabilities.FSIsolation == drivers.FSIsolationNone { 231 exec := aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocNodeExec) 232 if !exec { 233 return nil, structs.ErrPermissionDenied 234 } 235 } 236 237 allocState, err := a.c.GetAllocState(req.AllocID) 238 if err != nil { 239 code := helper.Int64ToPtr(500) 240 if structs.IsErrUnknownAllocation(err) { 241 code = helper.Int64ToPtr(404) 242 } 243 244 return code, err 245 } 246 247 // Check that the task is there 248 taskState := allocState.TaskStates[req.Task] 249 if taskState == nil { 250 return helper.Int64ToPtr(400), fmt.Errorf("unknown task name %q", req.Task) 251 } 252 253 if taskState.StartedAt.IsZero() { 254 return helper.Int64ToPtr(404), fmt.Errorf("task %q not started yet.", req.Task) 255 } 256 257 ctx, cancel := context.WithCancel(context.Background()) 258 defer cancel() 259 260 h := ar.GetTaskExecHandler(req.Task) 261 if h == nil { 262 return helper.Int64ToPtr(404), fmt.Errorf("task %q is not running.", req.Task) 263 } 264 265 err = h(ctx, req.Cmd, req.Tty, newExecStream(decoder, encoder)) 266 if err != nil { 267 code := helper.Int64ToPtr(500) 268 return code, err 269 } 270 271 return nil, nil 272 } 273 274 // newExecStream returns a new exec stream as expected by drivers that interpolate with RPC streaming format 275 func newExecStream(decoder *codec.Decoder, encoder *codec.Encoder) drivers.ExecTaskStream { 276 buf := new(bytes.Buffer) 277 return &execStream{ 278 decoder: decoder, 279 280 buf: buf, 281 encoder: encoder, 282 frameCodec: codec.NewEncoder(buf, structs.JsonHandle), 283 } 284 } 285 286 type execStream struct { 287 decoder *codec.Decoder 288 289 encoder *codec.Encoder 290 buf *bytes.Buffer 291 frameCodec *codec.Encoder 292 } 293 294 // Send sends driver output response across RPC mechanism using cstructs.StreamErrWrapper 295 func (s *execStream) Send(m *drivers.ExecTaskStreamingResponseMsg) error { 296 s.buf.Reset() 297 s.frameCodec.Reset(s.buf) 298 299 s.frameCodec.MustEncode(m) 300 return s.encoder.Encode(cstructs.StreamErrWrapper{ 301 Payload: s.buf.Bytes(), 302 }) 303 } 304 305 // Recv returns next exec user input from the RPC to be passed to driver exec handler 306 func (s *execStream) Recv() (*drivers.ExecTaskStreamingRequestMsg, error) { 307 req := drivers.ExecTaskStreamingRequestMsg{} 308 err := s.decoder.Decode(&req) 309 return &req, err 310 }