container.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /*
  2. Copyright The containerd Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package containerd
  14. import (
  15. "context"
  16. "encoding/json"
  17. "os"
  18. "path/filepath"
  19. "strings"
  20. "github.com/containerd/containerd/api/services/tasks/v1"
  21. "github.com/containerd/containerd/api/types"
  22. tasktypes "github.com/containerd/containerd/api/types/task"
  23. "github.com/containerd/containerd/cio"
  24. "github.com/containerd/containerd/containers"
  25. "github.com/containerd/containerd/errdefs"
  26. "github.com/containerd/containerd/images"
  27. "github.com/containerd/containerd/oci"
  28. "github.com/containerd/containerd/runtime/v2/runc/options"
  29. "github.com/containerd/typeurl"
  30. prototypes "github.com/gogo/protobuf/types"
  31. ver "github.com/opencontainers/image-spec/specs-go"
  32. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  33. "github.com/opencontainers/selinux/go-selinux/label"
  34. "github.com/pkg/errors"
  35. )
  36. const (
  37. checkpointImageNameLabel = "org.opencontainers.image.ref.name"
  38. checkpointRuntimeNameLabel = "io.containerd.checkpoint.runtime"
  39. checkpointSnapshotterNameLabel = "io.containerd.checkpoint.snapshotter"
  40. )
  41. // Container is a metadata object for container resources and task creation
  42. type Container interface {
  43. // ID identifies the container
  44. ID() string
  45. // Info returns the underlying container record type
  46. Info(context.Context, ...InfoOpts) (containers.Container, error)
  47. // Delete removes the container
  48. Delete(context.Context, ...DeleteOpts) error
  49. // NewTask creates a new task based on the container metadata
  50. NewTask(context.Context, cio.Creator, ...NewTaskOpts) (Task, error)
  51. // Spec returns the OCI runtime specification
  52. Spec(context.Context) (*oci.Spec, error)
  53. // Task returns the current task for the container
  54. //
  55. // If cio.Attach options are passed the client will reattach to the IO for the running
  56. // task. If no task exists for the container a NotFound error is returned
  57. //
  58. // Clients must make sure that only one reader is attached to the task and consuming
  59. // the output from the task's fifos
  60. Task(context.Context, cio.Attach) (Task, error)
  61. // Image returns the image that the container is based on
  62. Image(context.Context) (Image, error)
  63. // Labels returns the labels set on the container
  64. Labels(context.Context) (map[string]string, error)
  65. // SetLabels sets the provided labels for the container and returns the final label set
  66. SetLabels(context.Context, map[string]string) (map[string]string, error)
  67. // Extensions returns the extensions set on the container
  68. Extensions(context.Context) (map[string]prototypes.Any, error)
  69. // Update a container
  70. Update(context.Context, ...UpdateContainerOpts) error
  71. // Checkpoint creates a checkpoint image of the current container
  72. Checkpoint(context.Context, string, ...CheckpointOpts) (Image, error)
  73. }
  74. func containerFromRecord(client *Client, c containers.Container) *container {
  75. return &container{
  76. client: client,
  77. id: c.ID,
  78. metadata: c,
  79. }
  80. }
  81. var _ = (Container)(&container{})
  82. type container struct {
  83. client *Client
  84. id string
  85. metadata containers.Container
  86. }
  87. // ID returns the container's unique id
  88. func (c *container) ID() string {
  89. return c.id
  90. }
  91. func (c *container) Info(ctx context.Context, opts ...InfoOpts) (containers.Container, error) {
  92. i := &InfoConfig{
  93. // default to refreshing the container's local metadata
  94. Refresh: true,
  95. }
  96. for _, o := range opts {
  97. o(i)
  98. }
  99. if i.Refresh {
  100. metadata, err := c.get(ctx)
  101. if err != nil {
  102. return c.metadata, err
  103. }
  104. c.metadata = metadata
  105. }
  106. return c.metadata, nil
  107. }
  108. func (c *container) Extensions(ctx context.Context) (map[string]prototypes.Any, error) {
  109. r, err := c.get(ctx)
  110. if err != nil {
  111. return nil, err
  112. }
  113. return r.Extensions, nil
  114. }
  115. func (c *container) Labels(ctx context.Context) (map[string]string, error) {
  116. r, err := c.get(ctx)
  117. if err != nil {
  118. return nil, err
  119. }
  120. return r.Labels, nil
  121. }
  122. func (c *container) SetLabels(ctx context.Context, labels map[string]string) (map[string]string, error) {
  123. container := containers.Container{
  124. ID: c.id,
  125. Labels: labels,
  126. }
  127. var paths []string
  128. // mask off paths so we only muck with the labels encountered in labels.
  129. // Labels not in the passed in argument will be left alone.
  130. for k := range labels {
  131. paths = append(paths, strings.Join([]string{"labels", k}, "."))
  132. }
  133. r, err := c.client.ContainerService().Update(ctx, container, paths...)
  134. if err != nil {
  135. return nil, err
  136. }
  137. return r.Labels, nil
  138. }
  139. // Spec returns the current OCI specification for the container
  140. func (c *container) Spec(ctx context.Context) (*oci.Spec, error) {
  141. r, err := c.get(ctx)
  142. if err != nil {
  143. return nil, err
  144. }
  145. var s oci.Spec
  146. if err := json.Unmarshal(r.Spec.Value, &s); err != nil {
  147. return nil, err
  148. }
  149. return &s, nil
  150. }
  151. // Delete deletes an existing container
  152. // an error is returned if the container has running tasks
  153. func (c *container) Delete(ctx context.Context, opts ...DeleteOpts) error {
  154. if _, err := c.loadTask(ctx, nil); err == nil {
  155. return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot delete running task %v", c.id)
  156. }
  157. r, err := c.get(ctx)
  158. if err != nil {
  159. return err
  160. }
  161. for _, o := range opts {
  162. if err := o(ctx, c.client, r); err != nil {
  163. return err
  164. }
  165. }
  166. return c.client.ContainerService().Delete(ctx, c.id)
  167. }
  168. func (c *container) Task(ctx context.Context, attach cio.Attach) (Task, error) {
  169. return c.loadTask(ctx, attach)
  170. }
  171. // Image returns the image that the container is based on
  172. func (c *container) Image(ctx context.Context) (Image, error) {
  173. r, err := c.get(ctx)
  174. if err != nil {
  175. return nil, err
  176. }
  177. if r.Image == "" {
  178. return nil, errors.Wrap(errdefs.ErrNotFound, "container not created from an image")
  179. }
  180. i, err := c.client.ImageService().Get(ctx, r.Image)
  181. if err != nil {
  182. return nil, errors.Wrapf(err, "failed to get image %s for container", r.Image)
  183. }
  184. return NewImage(c.client, i), nil
  185. }
  186. func (c *container) NewTask(ctx context.Context, ioCreate cio.Creator, opts ...NewTaskOpts) (_ Task, err error) {
  187. i, err := ioCreate(c.id)
  188. if err != nil {
  189. return nil, err
  190. }
  191. defer func() {
  192. if err != nil && i != nil {
  193. i.Cancel()
  194. i.Close()
  195. }
  196. }()
  197. cfg := i.Config()
  198. request := &tasks.CreateTaskRequest{
  199. ContainerID: c.id,
  200. Terminal: cfg.Terminal,
  201. Stdin: cfg.Stdin,
  202. Stdout: cfg.Stdout,
  203. Stderr: cfg.Stderr,
  204. }
  205. r, err := c.get(ctx)
  206. if err != nil {
  207. return nil, err
  208. }
  209. if r.SnapshotKey != "" {
  210. if r.Snapshotter == "" {
  211. return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "unable to resolve rootfs mounts without snapshotter on container")
  212. }
  213. // get the rootfs from the snapshotter and add it to the request
  214. s, err := c.client.getSnapshotter(ctx, r.Snapshotter)
  215. if err != nil {
  216. return nil, err
  217. }
  218. mounts, err := s.Mounts(ctx, r.SnapshotKey)
  219. if err != nil {
  220. return nil, err
  221. }
  222. spec, err := c.Spec(ctx)
  223. if err != nil {
  224. return nil, err
  225. }
  226. for _, m := range mounts {
  227. if spec.Linux != nil && spec.Linux.MountLabel != "" {
  228. context := label.FormatMountLabel("", spec.Linux.MountLabel)
  229. if context != "" {
  230. m.Options = append(m.Options, context)
  231. }
  232. }
  233. request.Rootfs = append(request.Rootfs, &types.Mount{
  234. Type: m.Type,
  235. Source: m.Source,
  236. Options: m.Options,
  237. })
  238. }
  239. }
  240. info := TaskInfo{
  241. runtime: r.Runtime.Name,
  242. }
  243. for _, o := range opts {
  244. if err := o(ctx, c.client, &info); err != nil {
  245. return nil, err
  246. }
  247. }
  248. if info.RootFS != nil {
  249. for _, m := range info.RootFS {
  250. request.Rootfs = append(request.Rootfs, &types.Mount{
  251. Type: m.Type,
  252. Source: m.Source,
  253. Options: m.Options,
  254. })
  255. }
  256. }
  257. if info.Options != nil {
  258. any, err := typeurl.MarshalAny(info.Options)
  259. if err != nil {
  260. return nil, err
  261. }
  262. request.Options = any
  263. }
  264. t := &task{
  265. client: c.client,
  266. io: i,
  267. id: c.id,
  268. }
  269. if info.Checkpoint != nil {
  270. request.Checkpoint = info.Checkpoint
  271. }
  272. response, err := c.client.TaskService().Create(ctx, request)
  273. if err != nil {
  274. return nil, errdefs.FromGRPC(err)
  275. }
  276. t.pid = response.Pid
  277. return t, nil
  278. }
  279. func (c *container) Update(ctx context.Context, opts ...UpdateContainerOpts) error {
  280. // fetch the current container config before updating it
  281. r, err := c.get(ctx)
  282. if err != nil {
  283. return err
  284. }
  285. for _, o := range opts {
  286. if err := o(ctx, c.client, &r); err != nil {
  287. return err
  288. }
  289. }
  290. if _, err := c.client.ContainerService().Update(ctx, r); err != nil {
  291. return errdefs.FromGRPC(err)
  292. }
  293. return nil
  294. }
  295. func (c *container) Checkpoint(ctx context.Context, ref string, opts ...CheckpointOpts) (Image, error) {
  296. index := &ocispec.Index{
  297. Versioned: ver.Versioned{
  298. SchemaVersion: 2,
  299. },
  300. Annotations: make(map[string]string),
  301. }
  302. copts := &options.CheckpointOptions{
  303. Exit: false,
  304. OpenTcp: false,
  305. ExternalUnixSockets: false,
  306. Terminal: false,
  307. FileLocks: true,
  308. EmptyNamespaces: nil,
  309. }
  310. info, err := c.Info(ctx)
  311. if err != nil {
  312. return nil, err
  313. }
  314. img, err := c.Image(ctx)
  315. if err != nil {
  316. return nil, err
  317. }
  318. ctx, done, err := c.client.WithLease(ctx)
  319. if err != nil {
  320. return nil, err
  321. }
  322. defer done(ctx)
  323. // add image name to manifest
  324. index.Annotations[checkpointImageNameLabel] = img.Name()
  325. // add runtime info to index
  326. index.Annotations[checkpointRuntimeNameLabel] = info.Runtime.Name
  327. // add snapshotter info to index
  328. index.Annotations[checkpointSnapshotterNameLabel] = info.Snapshotter
  329. // process remaining opts
  330. for _, o := range opts {
  331. if err := o(ctx, c.client, &info, index, copts); err != nil {
  332. err = errdefs.FromGRPC(err)
  333. if !errdefs.IsAlreadyExists(err) {
  334. return nil, err
  335. }
  336. }
  337. }
  338. desc, err := writeIndex(ctx, index, c.client, c.ID()+"index")
  339. if err != nil {
  340. return nil, err
  341. }
  342. i := images.Image{
  343. Name: ref,
  344. Target: desc,
  345. }
  346. checkpoint, err := c.client.ImageService().Create(ctx, i)
  347. if err != nil {
  348. return nil, err
  349. }
  350. return NewImage(c.client, checkpoint), nil
  351. }
  352. func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
  353. response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
  354. ContainerID: c.id,
  355. })
  356. if err != nil {
  357. err = errdefs.FromGRPC(err)
  358. if errdefs.IsNotFound(err) {
  359. return nil, errors.Wrapf(err, "no running task found")
  360. }
  361. return nil, err
  362. }
  363. var i cio.IO
  364. if ioAttach != nil && response.Process.Status != tasktypes.StatusUnknown {
  365. // Do not attach IO for task in unknown state, because there
  366. // are no fifo paths anyway.
  367. if i, err = attachExistingIO(response, ioAttach); err != nil {
  368. return nil, err
  369. }
  370. }
  371. t := &task{
  372. client: c.client,
  373. io: i,
  374. id: response.Process.ID,
  375. pid: response.Process.Pid,
  376. }
  377. return t, nil
  378. }
  379. func (c *container) get(ctx context.Context) (containers.Container, error) {
  380. return c.client.ContainerService().Get(ctx, c.id)
  381. }
  382. // get the existing fifo paths from the task information stored by the daemon
  383. func attachExistingIO(response *tasks.GetResponse, ioAttach cio.Attach) (cio.IO, error) {
  384. fifoSet := loadFifos(response)
  385. return ioAttach(fifoSet)
  386. }
  387. // loadFifos loads the containers fifos
  388. func loadFifos(response *tasks.GetResponse) *cio.FIFOSet {
  389. path := getFifoDir([]string{
  390. response.Process.Stdin,
  391. response.Process.Stdout,
  392. response.Process.Stderr,
  393. })
  394. closer := func() error {
  395. return os.RemoveAll(path)
  396. }
  397. return cio.NewFIFOSet(cio.Config{
  398. Stdin: response.Process.Stdin,
  399. Stdout: response.Process.Stdout,
  400. Stderr: response.Process.Stderr,
  401. Terminal: response.Process.Terminal,
  402. }, closer)
  403. }
  404. // getFifoDir looks for any non-empty path for a stdio fifo
  405. // and returns the dir for where it is located
  406. func getFifoDir(paths []string) string {
  407. for _, p := range paths {
  408. if p != "" {
  409. return filepath.Dir(p)
  410. }
  411. }
  412. return ""
  413. }