github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 !errors.Is(err, os.ErrNotExist) { 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.Is(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 log.G(ctx).WithField("id", s.ID()).WithError(shimErr).Debug("failed to delete task") 230 if !errors.Is(shimErr, ttrpc.ErrClosed) { 231 shimErr = errdefs.FromGRPC(shimErr) 232 if !errdefs.IsNotFound(shimErr) { 233 return nil, shimErr 234 } 235 } 236 } 237 // remove self from the runtime task list 238 // this seems dirty but it cleans up the API across runtimes, tasks, and the service 239 s.rtTasks.Delete(ctx, s.ID()) 240 if err := s.waitShutdown(ctx); err != nil { 241 log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to shutdown shim") 242 } 243 s.Close() 244 if err := s.bundle.Delete(); err != nil { 245 log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete bundle") 246 } 247 if shimErr != nil { 248 return nil, shimErr 249 } 250 return &runtime.Exit{ 251 Status: response.ExitStatus, 252 Timestamp: response.ExitedAt, 253 Pid: response.Pid, 254 }, nil 255 } 256 257 func (s *shim) Create(ctx context.Context, opts runtime.CreateOpts) (runtime.Task, error) { 258 topts := opts.TaskOptions 259 if topts == nil { 260 topts = opts.RuntimeOptions 261 } 262 request := &task.CreateTaskRequest{ 263 ID: s.ID(), 264 Bundle: s.bundle.Path, 265 Stdin: opts.IO.Stdin, 266 Stdout: opts.IO.Stdout, 267 Stderr: opts.IO.Stderr, 268 Terminal: opts.IO.Terminal, 269 Checkpoint: opts.Checkpoint, 270 Options: topts, 271 } 272 for _, m := range opts.Rootfs { 273 request.Rootfs = append(request.Rootfs, &types.Mount{ 274 Type: m.Type, 275 Source: m.Source, 276 Options: m.Options, 277 }) 278 } 279 response, err := s.task.Create(ctx, request) 280 if err != nil { 281 return nil, errdefs.FromGRPC(err) 282 } 283 s.taskPid = int(response.Pid) 284 return s, nil 285 } 286 287 func (s *shim) Pause(ctx context.Context) error { 288 if _, err := s.task.Pause(ctx, &task.PauseRequest{ 289 ID: s.ID(), 290 }); err != nil { 291 return errdefs.FromGRPC(err) 292 } 293 return nil 294 } 295 296 func (s *shim) Resume(ctx context.Context) error { 297 if _, err := s.task.Resume(ctx, &task.ResumeRequest{ 298 ID: s.ID(), 299 }); err != nil { 300 return errdefs.FromGRPC(err) 301 } 302 return nil 303 } 304 305 func (s *shim) Start(ctx context.Context) error { 306 response, err := s.task.Start(ctx, &task.StartRequest{ 307 ID: s.ID(), 308 }) 309 if err != nil { 310 return errdefs.FromGRPC(err) 311 } 312 s.taskPid = int(response.Pid) 313 return nil 314 } 315 316 func (s *shim) Kill(ctx context.Context, signal uint32, all bool) error { 317 if _, err := s.task.Kill(ctx, &task.KillRequest{ 318 ID: s.ID(), 319 Signal: signal, 320 All: all, 321 }); err != nil { 322 return errdefs.FromGRPC(err) 323 } 324 return nil 325 } 326 327 func (s *shim) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) { 328 if err := identifiers.Validate(id); err != nil { 329 return nil, errors.Wrapf(err, "invalid exec id %s", id) 330 } 331 request := &task.ExecProcessRequest{ 332 ID: s.ID(), 333 ExecID: id, 334 Stdin: opts.IO.Stdin, 335 Stdout: opts.IO.Stdout, 336 Stderr: opts.IO.Stderr, 337 Terminal: opts.IO.Terminal, 338 Spec: opts.Spec, 339 } 340 if _, err := s.task.Exec(ctx, request); err != nil { 341 return nil, errdefs.FromGRPC(err) 342 } 343 return &process{ 344 id: id, 345 shim: s, 346 }, nil 347 } 348 349 func (s *shim) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) { 350 resp, err := s.task.Pids(ctx, &task.PidsRequest{ 351 ID: s.ID(), 352 }) 353 if err != nil { 354 return nil, errdefs.FromGRPC(err) 355 } 356 var processList []runtime.ProcessInfo 357 for _, p := range resp.Processes { 358 processList = append(processList, runtime.ProcessInfo{ 359 Pid: p.Pid, 360 Info: p.Info, 361 }) 362 } 363 return processList, nil 364 } 365 366 func (s *shim) ResizePty(ctx context.Context, size runtime.ConsoleSize) error { 367 _, err := s.task.ResizePty(ctx, &task.ResizePtyRequest{ 368 ID: s.ID(), 369 Width: size.Width, 370 Height: size.Height, 371 }) 372 if err != nil { 373 return errdefs.FromGRPC(err) 374 } 375 return nil 376 } 377 378 func (s *shim) CloseIO(ctx context.Context) error { 379 _, err := s.task.CloseIO(ctx, &task.CloseIORequest{ 380 ID: s.ID(), 381 Stdin: true, 382 }) 383 if err != nil { 384 return errdefs.FromGRPC(err) 385 } 386 return nil 387 } 388 389 func (s *shim) Wait(ctx context.Context) (*runtime.Exit, error) { 390 response, err := s.task.Wait(ctx, &task.WaitRequest{ 391 ID: s.ID(), 392 }) 393 if err != nil { 394 return nil, errdefs.FromGRPC(err) 395 } 396 return &runtime.Exit{ 397 Pid: uint32(s.taskPid), 398 Timestamp: response.ExitedAt, 399 Status: response.ExitStatus, 400 }, nil 401 } 402 403 func (s *shim) Checkpoint(ctx context.Context, path string, options *ptypes.Any) error { 404 request := &task.CheckpointTaskRequest{ 405 ID: s.ID(), 406 Path: path, 407 Options: options, 408 } 409 if _, err := s.task.Checkpoint(ctx, request); err != nil { 410 return errdefs.FromGRPC(err) 411 } 412 return nil 413 } 414 415 func (s *shim) Update(ctx context.Context, resources *ptypes.Any) error { 416 if _, err := s.task.Update(ctx, &task.UpdateTaskRequest{ 417 ID: s.ID(), 418 Resources: resources, 419 }); err != nil { 420 return errdefs.FromGRPC(err) 421 } 422 return nil 423 } 424 425 func (s *shim) Stats(ctx context.Context) (*ptypes.Any, error) { 426 response, err := s.task.Stats(ctx, &task.StatsRequest{ 427 ID: s.ID(), 428 }) 429 if err != nil { 430 return nil, errdefs.FromGRPC(err) 431 } 432 return response.Stats, nil 433 } 434 435 func (s *shim) Process(ctx context.Context, id string) (runtime.Process, error) { 436 return &process{ 437 id: id, 438 shim: s, 439 }, nil 440 } 441 442 func (s *shim) State(ctx context.Context) (runtime.State, error) { 443 response, err := s.task.State(ctx, &task.StateRequest{ 444 ID: s.ID(), 445 }) 446 if err != nil { 447 if !errors.Is(err, ttrpc.ErrClosed) { 448 return runtime.State{}, errdefs.FromGRPC(err) 449 } 450 return runtime.State{}, errdefs.ErrNotFound 451 } 452 var status runtime.Status 453 switch response.Status { 454 case tasktypes.StatusCreated: 455 status = runtime.CreatedStatus 456 case tasktypes.StatusRunning: 457 status = runtime.RunningStatus 458 case tasktypes.StatusStopped: 459 status = runtime.StoppedStatus 460 case tasktypes.StatusPaused: 461 status = runtime.PausedStatus 462 case tasktypes.StatusPausing: 463 status = runtime.PausingStatus 464 } 465 return runtime.State{ 466 Pid: response.Pid, 467 Status: status, 468 Stdin: response.Stdin, 469 Stdout: response.Stdout, 470 Stderr: response.Stderr, 471 Terminal: response.Terminal, 472 ExitStatus: response.ExitStatus, 473 ExitedAt: response.ExitedAt, 474 }, nil 475 }