github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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/nomad/acl" 13 cstructs "github.com/hashicorp/nomad/client/structs" 14 "github.com/hashicorp/nomad/helper" 15 "github.com/hashicorp/nomad/helper/uuid" 16 "github.com/hashicorp/nomad/nomad/structs" 17 nstructs "github.com/hashicorp/nomad/nomad/structs" 18 "github.com/hashicorp/nomad/plugins/drivers" 19 "github.com/ugorji/go/codec" 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 // Check submit job permissions 53 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 54 return err 55 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilitySubmitJob) { 56 return nstructs.ErrPermissionDenied 57 } 58 59 if !a.c.CollectAllocation(args.AllocID) { 60 // Could not find alloc 61 return nstructs.NewErrUnknownAllocation(args.AllocID) 62 } 63 64 return nil 65 } 66 67 // Signal is used to send a signal to an allocation's tasks on a client. 68 func (a *Allocations) Signal(args *nstructs.AllocSignalRequest, reply *nstructs.GenericResponse) error { 69 defer metrics.MeasureSince([]string{"client", "allocations", "signal"}, time.Now()) 70 71 // Check alloc-lifecycle permissions 72 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 73 return err 74 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) { 75 return nstructs.ErrPermissionDenied 76 } 77 78 return a.c.SignalAllocation(args.AllocID, args.Task, args.Signal) 79 } 80 81 // Restart is used to trigger a restart of an allocation or a subtask on a client. 82 func (a *Allocations) Restart(args *nstructs.AllocRestartRequest, reply *nstructs.GenericResponse) error { 83 defer metrics.MeasureSince([]string{"client", "allocations", "restart"}, time.Now()) 84 85 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 86 return err 87 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) { 88 return nstructs.ErrPermissionDenied 89 } 90 91 return a.c.RestartAllocation(args.AllocID, args.TaskName) 92 } 93 94 // Stats is used to collect allocation statistics 95 func (a *Allocations) Stats(args *cstructs.AllocStatsRequest, reply *cstructs.AllocStatsResponse) error { 96 defer metrics.MeasureSince([]string{"client", "allocations", "stats"}, time.Now()) 97 98 // Check read job permissions 99 if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { 100 return err 101 } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadJob) { 102 return nstructs.ErrPermissionDenied 103 } 104 105 clientStats := a.c.StatsReporter() 106 aStats, err := clientStats.GetAllocStats(args.AllocID) 107 if err != nil { 108 return err 109 } 110 111 stats, err := aStats.LatestAllocStats(args.Task) 112 if err != nil { 113 return err 114 } 115 116 reply.Stats = stats 117 return nil 118 } 119 120 // exec is used to execute command in a running task 121 func (a *Allocations) exec(conn io.ReadWriteCloser) { 122 defer metrics.MeasureSince([]string{"client", "allocations", "exec"}, time.Now()) 123 defer conn.Close() 124 125 execID := uuid.Generate() 126 decoder := codec.NewDecoder(conn, structs.MsgpackHandle) 127 encoder := codec.NewEncoder(conn, structs.MsgpackHandle) 128 129 code, err := a.execImpl(encoder, decoder, execID) 130 if err != nil { 131 a.c.logger.Info("task exec session ended with an error", "error", err, "code", code) 132 handleStreamResultError(err, code, encoder) 133 return 134 } 135 136 a.c.logger.Info("task exec session ended", "exec_id", execID) 137 } 138 139 func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, execID string) (code *int64, err error) { 140 141 // Decode the arguments 142 var req cstructs.AllocExecRequest 143 if err := decoder.Decode(&req); err != nil { 144 return helper.Int64ToPtr(500), err 145 } 146 147 if a.c.GetConfig().DisableRemoteExec { 148 return nil, structs.ErrPermissionDenied 149 } 150 151 aclObj, token, err := a.c.resolveTokenAndACL(req.QueryOptions.AuthToken) 152 { 153 // log access 154 tokenName, tokenID := "", "" 155 if token != nil { 156 tokenName, tokenID = token.Name, token.AccessorID 157 } 158 159 a.c.logger.Info("task exec session starting", 160 "exec_id", execID, 161 "alloc_id", req.AllocID, 162 "task", req.Task, 163 "command", req.Cmd, 164 "tty", req.Tty, 165 "access_token_name", tokenName, 166 "access_token_id", tokenID, 167 ) 168 } 169 170 // Check read permissions 171 if err != nil { 172 return nil, err 173 } else if aclObj != nil { 174 exec := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityAllocExec) 175 if !exec { 176 return nil, structs.ErrPermissionDenied 177 } 178 } 179 180 // Validate the arguments 181 if req.AllocID == "" { 182 return helper.Int64ToPtr(400), allocIDNotPresentErr 183 } 184 if req.Task == "" { 185 return helper.Int64ToPtr(400), taskNotPresentErr 186 } 187 if len(req.Cmd) == 0 { 188 return helper.Int64ToPtr(400), errors.New("command is not present") 189 } 190 191 ar, err := a.c.getAllocRunner(req.AllocID) 192 if err != nil { 193 code := helper.Int64ToPtr(500) 194 if structs.IsErrUnknownAllocation(err) { 195 code = helper.Int64ToPtr(404) 196 } 197 198 return code, err 199 } 200 201 capabilities, err := ar.GetTaskDriverCapabilities(req.Task) 202 if err != nil { 203 code := helper.Int64ToPtr(500) 204 if structs.IsErrUnknownAllocation(err) { 205 code = helper.Int64ToPtr(404) 206 } 207 208 return code, err 209 } 210 211 // check node access 212 if aclObj != nil && capabilities.FSIsolation == drivers.FSIsolationNone { 213 exec := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityAllocNodeExec) 214 if !exec { 215 return nil, structs.ErrPermissionDenied 216 } 217 } 218 219 allocState, err := a.c.GetAllocState(req.AllocID) 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 that the task is there 230 taskState := allocState.TaskStates[req.Task] 231 if taskState == nil { 232 return helper.Int64ToPtr(400), fmt.Errorf("unknown task name %q", req.Task) 233 } 234 235 if taskState.StartedAt.IsZero() { 236 return helper.Int64ToPtr(404), fmt.Errorf("task %q not started yet.", req.Task) 237 } 238 239 ctx, cancel := context.WithCancel(context.Background()) 240 defer cancel() 241 242 h := ar.GetTaskExecHandler(req.Task) 243 if h == nil { 244 return helper.Int64ToPtr(404), fmt.Errorf("task %q is not running.", req.Task) 245 } 246 247 err = h(ctx, req.Cmd, req.Tty, newExecStream(decoder, encoder)) 248 if err != nil { 249 code := helper.Int64ToPtr(500) 250 return code, err 251 } 252 253 return nil, nil 254 } 255 256 // newExecStream returns a new exec stream as expected by drivers that interpolate with RPC streaming format 257 func newExecStream(decoder *codec.Decoder, encoder *codec.Encoder) drivers.ExecTaskStream { 258 buf := new(bytes.Buffer) 259 return &execStream{ 260 decoder: decoder, 261 262 buf: buf, 263 encoder: encoder, 264 frameCodec: codec.NewEncoder(buf, structs.JsonHandle), 265 } 266 } 267 268 type execStream struct { 269 decoder *codec.Decoder 270 271 encoder *codec.Encoder 272 buf *bytes.Buffer 273 frameCodec *codec.Encoder 274 } 275 276 // Send sends driver output response across RPC mechanism using cstructs.StreamErrWrapper 277 func (s *execStream) Send(m *drivers.ExecTaskStreamingResponseMsg) error { 278 s.buf.Reset() 279 s.frameCodec.Reset(s.buf) 280 281 s.frameCodec.MustEncode(m) 282 return s.encoder.Encode(cstructs.StreamErrWrapper{ 283 Payload: s.buf.Bytes(), 284 }) 285 } 286 287 // Recv returns next exec user input from the RPC to be passed to driver exec handler 288 func (s *execStream) Recv() (*drivers.ExecTaskStreamingRequestMsg, error) { 289 req := drivers.ExecTaskStreamingRequestMsg{} 290 err := s.decoder.Decode(&req) 291 return &req, err 292 }