github.com/demonoid81/containerd@v1.3.4/runtime/v2/shim.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v2 18 19 import ( 20 "context" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "time" 26 27 eventstypes "github.com/containerd/containerd/api/events" 28 "github.com/containerd/containerd/api/types" 29 tasktypes "github.com/containerd/containerd/api/types/task" 30 "github.com/containerd/containerd/errdefs" 31 "github.com/containerd/containerd/events/exchange" 32 "github.com/containerd/containerd/identifiers" 33 "github.com/containerd/containerd/log" 34 "github.com/containerd/containerd/namespaces" 35 "github.com/containerd/containerd/pkg/timeout" 36 "github.com/containerd/containerd/runtime" 37 client "github.com/containerd/containerd/runtime/v2/shim" 38 "github.com/containerd/containerd/runtime/v2/task" 39 "github.com/containerd/ttrpc" 40 ptypes "github.com/gogo/protobuf/types" 41 "github.com/pkg/errors" 42 "github.com/sirupsen/logrus" 43 ) 44 45 const ( 46 loadTimeout = "io.containerd.timeout.shim.load" 47 cleanupTimeout = "io.containerd.timeout.shim.cleanup" 48 shutdownTimeout = "io.containerd.timeout.shim.shutdown" 49 ) 50 51 func init() { 52 timeout.Set(loadTimeout, 5*time.Second) 53 timeout.Set(cleanupTimeout, 5*time.Second) 54 timeout.Set(shutdownTimeout, 3*time.Second) 55 } 56 57 func loadAddress(path string) (string, error) { 58 data, err := ioutil.ReadFile(path) 59 if err != nil { 60 return "", err 61 } 62 return string(data), nil 63 } 64 65 func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt *runtime.TaskList, onClose func()) (_ *shim, err error) { 66 address, err := loadAddress(filepath.Join(bundle.Path, "address")) 67 if err != nil { 68 return nil, err 69 } 70 conn, err := client.Connect(address, client.AnonReconnectDialer) 71 if err != nil { 72 return nil, err 73 } 74 defer func() { 75 if err != nil { 76 conn.Close() 77 } 78 }() 79 f, err := openShimLog(ctx, bundle, client.AnonReconnectDialer) 80 if err != nil { 81 return nil, errors.Wrap(err, "open shim log pipe") 82 } 83 defer func() { 84 if err != nil { 85 f.Close() 86 } 87 }() 88 // open the log pipe and block until the writer is ready 89 // this helps with synchronization of the shim 90 // copy the shim's logs to containerd's output 91 go func() { 92 defer f.Close() 93 if _, err := io.Copy(os.Stderr, f); err != nil { 94 // When using a multi-container shim the 2nd to Nth container in the 95 // shim will not have a separate log pipe. Ignore the failure log 96 // message here when the shim connect times out. 97 if !os.IsNotExist(errors.Cause(err)) { 98 log.G(ctx).WithError(err).Error("copy shim log") 99 } 100 } 101 }() 102 103 client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose)) 104 defer func() { 105 if err != nil { 106 client.Close() 107 } 108 }() 109 s := &shim{ 110 client: client, 111 task: task.NewTaskClient(client), 112 bundle: bundle, 113 events: events, 114 rtTasks: rt, 115 } 116 ctx, cancel := timeout.WithContext(ctx, loadTimeout) 117 defer cancel() 118 if err := s.Connect(ctx); err != nil { 119 return nil, err 120 } 121 return s, nil 122 } 123 124 func cleanupAfterDeadShim(ctx context.Context, id, ns string, events *exchange.Exchange, binaryCall *binary) { 125 ctx = namespaces.WithNamespace(ctx, ns) 126 ctx, cancel := timeout.WithContext(ctx, cleanupTimeout) 127 defer cancel() 128 129 log.G(ctx).WithFields(logrus.Fields{ 130 "id": id, 131 "namespace": ns, 132 }).Warn("cleaning up after shim disconnected") 133 response, err := binaryCall.Delete(ctx) 134 if err != nil { 135 log.G(ctx).WithError(err).WithFields(logrus.Fields{ 136 "id": id, 137 "namespace": ns, 138 }).Warn("failed to clean up after shim disconnected") 139 } 140 141 var ( 142 pid uint32 143 exitStatus uint32 144 exitedAt time.Time 145 ) 146 if response != nil { 147 pid = response.Pid 148 exitStatus = response.Status 149 exitedAt = response.Timestamp 150 } else { 151 exitStatus = 255 152 exitedAt = time.Now() 153 } 154 events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{ 155 ContainerID: id, 156 ID: id, 157 Pid: pid, 158 ExitStatus: exitStatus, 159 ExitedAt: exitedAt, 160 }) 161 162 events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{ 163 ContainerID: id, 164 Pid: pid, 165 ExitStatus: exitStatus, 166 ExitedAt: exitedAt, 167 }) 168 } 169 170 type shim struct { 171 bundle *Bundle 172 client *ttrpc.Client 173 task task.TaskService 174 taskPid int 175 events *exchange.Exchange 176 rtTasks *runtime.TaskList 177 } 178 179 func (s *shim) Connect(ctx context.Context) error { 180 response, err := s.task.Connect(ctx, &task.ConnectRequest{ 181 ID: s.ID(), 182 }) 183 if err != nil { 184 return err 185 } 186 s.taskPid = int(response.TaskPid) 187 return nil 188 } 189 190 func (s *shim) Shutdown(ctx context.Context) error { 191 _, err := s.task.Shutdown(ctx, &task.ShutdownRequest{ 192 ID: s.ID(), 193 }) 194 if err != nil && errors.Cause(err) != ttrpc.ErrClosed { 195 return errdefs.FromGRPC(err) 196 } 197 return nil 198 } 199 200 func (s *shim) waitShutdown(ctx context.Context) error { 201 ctx, cancel := timeout.WithContext(ctx, shutdownTimeout) 202 defer cancel() 203 return s.Shutdown(ctx) 204 } 205 206 // ID of the shim/task 207 func (s *shim) ID() string { 208 return s.bundle.ID 209 } 210 211 // PID of the task 212 func (s *shim) PID() uint32 { 213 return uint32(s.taskPid) 214 } 215 216 func (s *shim) Namespace() string { 217 return s.bundle.Namespace 218 } 219 220 func (s *shim) Close() error { 221 return s.client.Close() 222 } 223 224 func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) { 225 response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{ 226 ID: s.ID(), 227 }) 228 if shimErr != nil { 229 shimErr = errdefs.FromGRPC(shimErr) 230 if !errdefs.IsNotFound(shimErr) { 231 return nil, shimErr 232 } 233 } 234 // remove self from the runtime task list 235 // this seems dirty but it cleans up the API across runtimes, tasks, and the service 236 s.rtTasks.Delete(ctx, s.ID()) 237 if err := s.waitShutdown(ctx); err != nil { 238 log.G(ctx).WithError(err).Error("failed to shutdown shim") 239 } 240 s.Close() 241 if err := s.bundle.Delete(); err != nil { 242 log.G(ctx).WithError(err).Error("failed to delete bundle") 243 } 244 if shimErr != nil { 245 return nil, shimErr 246 } 247 return &runtime.Exit{ 248 Status: response.ExitStatus, 249 Timestamp: response.ExitedAt, 250 Pid: response.Pid, 251 }, nil 252 } 253 254 func (s *shim) Create(ctx context.Context, opts runtime.CreateOpts) (runtime.Task, error) { 255 topts := opts.TaskOptions 256 if topts == nil { 257 topts = opts.RuntimeOptions 258 } 259 request := &task.CreateTaskRequest{ 260 ID: s.ID(), 261 Bundle: s.bundle.Path, 262 Stdin: opts.IO.Stdin, 263 Stdout: opts.IO.Stdout, 264 Stderr: opts.IO.Stderr, 265 Terminal: opts.IO.Terminal, 266 Checkpoint: opts.Checkpoint, 267 Options: topts, 268 } 269 for _, m := range opts.Rootfs { 270 request.Rootfs = append(request.Rootfs, &types.Mount{ 271 Type: m.Type, 272 Source: m.Source, 273 Options: m.Options, 274 }) 275 } 276 response, err := s.task.Create(ctx, request) 277 if err != nil { 278 return nil, errdefs.FromGRPC(err) 279 } 280 s.taskPid = int(response.Pid) 281 return s, nil 282 } 283 284 func (s *shim) Pause(ctx context.Context) error { 285 if _, err := s.task.Pause(ctx, &task.PauseRequest{ 286 ID: s.ID(), 287 }); err != nil { 288 return errdefs.FromGRPC(err) 289 } 290 return nil 291 } 292 293 func (s *shim) Resume(ctx context.Context) error { 294 if _, err := s.task.Resume(ctx, &task.ResumeRequest{ 295 ID: s.ID(), 296 }); err != nil { 297 return errdefs.FromGRPC(err) 298 } 299 return nil 300 } 301 302 func (s *shim) Start(ctx context.Context) error { 303 response, err := s.task.Start(ctx, &task.StartRequest{ 304 ID: s.ID(), 305 }) 306 if err != nil { 307 return errdefs.FromGRPC(err) 308 } 309 s.taskPid = int(response.Pid) 310 return nil 311 } 312 313 func (s *shim) Kill(ctx context.Context, signal uint32, all bool) error { 314 if _, err := s.task.Kill(ctx, &task.KillRequest{ 315 ID: s.ID(), 316 Signal: signal, 317 All: all, 318 }); err != nil { 319 return errdefs.FromGRPC(err) 320 } 321 return nil 322 } 323 324 func (s *shim) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) { 325 if err := identifiers.Validate(id); err != nil { 326 return nil, errors.Wrapf(err, "invalid exec id %s", id) 327 } 328 request := &task.ExecProcessRequest{ 329 ID: s.ID(), 330 ExecID: id, 331 Stdin: opts.IO.Stdin, 332 Stdout: opts.IO.Stdout, 333 Stderr: opts.IO.Stderr, 334 Terminal: opts.IO.Terminal, 335 Spec: opts.Spec, 336 } 337 if _, err := s.task.Exec(ctx, request); err != nil { 338 return nil, errdefs.FromGRPC(err) 339 } 340 return &process{ 341 id: id, 342 shim: s, 343 }, nil 344 } 345 346 func (s *shim) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) { 347 resp, err := s.task.Pids(ctx, &task.PidsRequest{ 348 ID: s.ID(), 349 }) 350 if err != nil { 351 return nil, errdefs.FromGRPC(err) 352 } 353 var processList []runtime.ProcessInfo 354 for _, p := range resp.Processes { 355 processList = append(processList, runtime.ProcessInfo{ 356 Pid: p.Pid, 357 Info: p.Info, 358 }) 359 } 360 return processList, nil 361 } 362 363 func (s *shim) ResizePty(ctx context.Context, size runtime.ConsoleSize) error { 364 _, err := s.task.ResizePty(ctx, &task.ResizePtyRequest{ 365 ID: s.ID(), 366 Width: size.Width, 367 Height: size.Height, 368 }) 369 if err != nil { 370 return errdefs.FromGRPC(err) 371 } 372 return nil 373 } 374 375 func (s *shim) CloseIO(ctx context.Context) error { 376 _, err := s.task.CloseIO(ctx, &task.CloseIORequest{ 377 ID: s.ID(), 378 Stdin: true, 379 }) 380 if err != nil { 381 return errdefs.FromGRPC(err) 382 } 383 return nil 384 } 385 386 func (s *shim) Wait(ctx context.Context) (*runtime.Exit, error) { 387 response, err := s.task.Wait(ctx, &task.WaitRequest{ 388 ID: s.ID(), 389 }) 390 if err != nil { 391 return nil, errdefs.FromGRPC(err) 392 } 393 return &runtime.Exit{ 394 Pid: uint32(s.taskPid), 395 Timestamp: response.ExitedAt, 396 Status: response.ExitStatus, 397 }, nil 398 } 399 400 func (s *shim) Checkpoint(ctx context.Context, path string, options *ptypes.Any) error { 401 request := &task.CheckpointTaskRequest{ 402 ID: s.ID(), 403 Path: path, 404 Options: options, 405 } 406 if _, err := s.task.Checkpoint(ctx, request); err != nil { 407 return errdefs.FromGRPC(err) 408 } 409 return nil 410 } 411 412 func (s *shim) Update(ctx context.Context, resources *ptypes.Any) error { 413 if _, err := s.task.Update(ctx, &task.UpdateTaskRequest{ 414 ID: s.ID(), 415 Resources: resources, 416 }); err != nil { 417 return errdefs.FromGRPC(err) 418 } 419 return nil 420 } 421 422 func (s *shim) Stats(ctx context.Context) (*ptypes.Any, error) { 423 response, err := s.task.Stats(ctx, &task.StatsRequest{ 424 ID: s.ID(), 425 }) 426 if err != nil { 427 return nil, errdefs.FromGRPC(err) 428 } 429 return response.Stats, nil 430 } 431 432 func (s *shim) Process(ctx context.Context, id string) (runtime.Process, error) { 433 return &process{ 434 id: id, 435 shim: s, 436 }, nil 437 } 438 439 func (s *shim) State(ctx context.Context) (runtime.State, error) { 440 response, err := s.task.State(ctx, &task.StateRequest{ 441 ID: s.ID(), 442 }) 443 if err != nil { 444 if errors.Cause(err) != ttrpc.ErrClosed { 445 return runtime.State{}, errdefs.FromGRPC(err) 446 } 447 return runtime.State{}, errdefs.ErrNotFound 448 } 449 var status runtime.Status 450 switch response.Status { 451 case tasktypes.StatusCreated: 452 status = runtime.CreatedStatus 453 case tasktypes.StatusRunning: 454 status = runtime.RunningStatus 455 case tasktypes.StatusStopped: 456 status = runtime.StoppedStatus 457 case tasktypes.StatusPaused: 458 status = runtime.PausedStatus 459 case tasktypes.StatusPausing: 460 status = runtime.PausingStatus 461 } 462 return runtime.State{ 463 Pid: response.Pid, 464 Status: status, 465 Stdin: response.Stdin, 466 Stdout: response.Stdout, 467 Stderr: response.Stderr, 468 Terminal: response.Terminal, 469 ExitStatus: response.ExitStatus, 470 ExitedAt: response.ExitedAt, 471 }, nil 472 }