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
17package server
18
19import (
20	"io"
21	"time"
22
23	"github.com/containerd/containerd"
24	containerdio "github.com/containerd/containerd/cio"
25	"github.com/containerd/containerd/errdefs"
26	"github.com/containerd/containerd/log"
27	"github.com/containerd/nri"
28	v1 "github.com/containerd/nri/types/v1"
29	"github.com/pkg/errors"
30	"github.com/sirupsen/logrus"
31	"golang.org/x/net/context"
32	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
33
34	cio "github.com/containerd/containerd/pkg/cri/io"
35	containerstore "github.com/containerd/containerd/pkg/cri/store/container"
36	sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
37	ctrdutil "github.com/containerd/containerd/pkg/cri/util"
38	cioutil "github.com/containerd/containerd/pkg/ioutil"
39)
40
41// StartContainer starts the container.
42func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContainerRequest) (retRes *runtime.StartContainerResponse, retErr error) {
43	cntr, err := c.containerStore.Get(r.GetContainerId())
44	if err != nil {
45		return nil, errors.Wrapf(err, "an error occurred when try to find container %q", r.GetContainerId())
46	}
47
48	id := cntr.ID
49	meta := cntr.Metadata
50	container := cntr.Container
51	config := meta.Config
52
53	// Set starting state to prevent other start/remove operations against this container
54	// while it's being started.
55	if err := setContainerStarting(cntr); err != nil {
56		return nil, errors.Wrapf(err, "failed to set starting state for container %q", id)
57	}
58	defer func() {
59		if retErr != nil {
60			// Set container to exited if fail to start.
61			if err := cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
62				status.Pid = 0
63				status.FinishedAt = time.Now().UnixNano()
64				status.ExitCode = errorStartExitCode
65				status.Reason = errorStartReason
66				status.Message = retErr.Error()
67				return status, nil
68			}); err != nil {
69				log.G(ctx).WithError(err).Errorf("failed to set start failure state for container %q", id)
70			}
71		}
72		if err := resetContainerStarting(cntr); err != nil {
73			log.G(ctx).WithError(err).Errorf("failed to reset starting state for container %q", id)
74		}
75	}()
76
77	// Get sandbox config from sandbox store.
78	sandbox, err := c.sandboxStore.Get(meta.SandboxID)
79	if err != nil {
80		return nil, errors.Wrapf(err, "sandbox %q not found", meta.SandboxID)
81	}
82	sandboxID := meta.SandboxID
83	if sandbox.Status.Get().State != sandboxstore.StateReady {
84		return nil, errors.Errorf("sandbox container %q is not running", sandboxID)
85	}
86
87	// Recheck target container validity in Linux namespace options.
88	if linux := config.GetLinux(); linux != nil {
89		nsOpts := linux.GetSecurityContext().GetNamespaceOptions()
90		if nsOpts.GetPid() == runtime.NamespaceMode_TARGET {
91			_, err := c.validateTargetContainer(sandboxID, nsOpts.TargetId)
92			if err != nil {
93				return nil, errors.Wrap(err, "invalid target container")
94			}
95		}
96	}
97
98	ioCreation := func(id string) (_ containerdio.IO, err error) {
99		stdoutWC, stderrWC, err := c.createContainerLoggers(meta.LogPath, config.GetTty())
100		if err != nil {
101			return nil, errors.Wrap(err, "failed to create container loggers")
102		}
103		cntr.IO.AddOutput("log", stdoutWC, stderrWC)
104		cntr.IO.Pipe()
105		return cntr.IO, nil
106	}
107
108	ctrInfo, err := container.Info(ctx)
109	if err != nil {
110		return nil, errors.Wrap(err, "failed to get container info")
111	}
112
113	taskOpts := c.taskOpts(ctrInfo.Runtime.Name)
114	task, err := container.NewTask(ctx, ioCreation, taskOpts...)
115	if err != nil {
116		return nil, errors.Wrap(err, "failed to create containerd task")
117	}
118	defer func() {
119		if retErr != nil {
120			deferCtx, deferCancel := ctrdutil.DeferContext()
121			defer deferCancel()
122			// It's possible that task is deleted by event monitor.
123			if _, err := task.Delete(deferCtx, WithNRISandboxDelete(sandboxID), containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
124				log.G(ctx).WithError(err).Errorf("Failed to delete containerd task %q", id)
125			}
126		}
127	}()
128
129	// wait is a long running background request, no timeout needed.
130	exitCh, err := task.Wait(ctrdutil.NamespacedContext())
131	if err != nil {
132		return nil, errors.Wrap(err, "failed to wait for containerd task")
133	}
134	nric, err := nri.New()
135	if err != nil {
136		log.G(ctx).WithError(err).Error("unable to create nri client")
137	}
138	if nric != nil {
139		nriSB := &nri.Sandbox{
140			ID:     sandboxID,
141			Labels: sandbox.Config.Labels,
142		}
143		if _, err := nric.InvokeWithSandbox(ctx, task, v1.Create, nriSB); err != nil {
144			return nil, errors.Wrap(err, "nri invoke")
145		}
146	}
147
148	// Start containerd task.
149	if err := task.Start(ctx); err != nil {
150		return nil, errors.Wrapf(err, "failed to start containerd task %q", id)
151	}
152
153	// Update container start timestamp.
154	if err := cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
155		status.Pid = task.Pid()
156		status.StartedAt = time.Now().UnixNano()
157		return status, nil
158	}); err != nil {
159		return nil, errors.Wrapf(err, "failed to update container %q state", id)
160	}
161
162	// It handles the TaskExit event and update container state after this.
163	c.eventMonitor.startContainerExitMonitor(context.Background(), id, task.Pid(), exitCh)
164
165	return &runtime.StartContainerResponse{}, nil
166}
167
168// setContainerStarting sets the container into starting state. In starting state, the
169// container will not be removed or started again.
170func setContainerStarting(container containerstore.Container) error {
171	return container.Status.Update(func(status containerstore.Status) (containerstore.Status, error) {
172		// Return error if container is not in created state.
173		if status.State() != runtime.ContainerState_CONTAINER_CREATED {
174			return status, errors.Errorf("container is in %s state", criContainerStateToString(status.State()))
175		}
176		// Do not start the container when there is a removal in progress.
177		if status.Removing {
178			return status, errors.New("container is in removing state, can't be started")
179		}
180		if status.Starting {
181			return status, errors.New("container is already in starting state")
182		}
183		status.Starting = true
184		return status, nil
185	})
186}
187
188// resetContainerStarting resets the container starting state on start failure. So
189// that we could remove the container later.
190func resetContainerStarting(container containerstore.Container) error {
191	return container.Status.Update(func(status containerstore.Status) (containerstore.Status, error) {
192		status.Starting = false
193		return status, nil
194	})
195}
196
197// createContainerLoggers creates container loggers and return write closer for stdout and stderr.
198func (c *criService) createContainerLoggers(logPath string, tty bool) (stdout io.WriteCloser, stderr io.WriteCloser, err error) {
199	if logPath != "" {
200		// Only generate container log when log path is specified.
201		f, err := openLogFile(logPath)
202		if err != nil {
203			return nil, nil, errors.Wrap(err, "failed to create and open log file")
204		}
205		defer func() {
206			if err != nil {
207				f.Close()
208			}
209		}()
210		var stdoutCh, stderrCh <-chan struct{}
211		wc := cioutil.NewSerialWriteCloser(f)
212		stdout, stdoutCh = cio.NewCRILogger(logPath, wc, cio.Stdout, c.config.MaxContainerLogLineSize)
213		// Only redirect stderr when there is no tty.
214		if !tty {
215			stderr, stderrCh = cio.NewCRILogger(logPath, wc, cio.Stderr, c.config.MaxContainerLogLineSize)
216		}
217		go func() {
218			if stdoutCh != nil {
219				<-stdoutCh
220			}
221			if stderrCh != nil {
222				<-stderrCh
223			}
224			logrus.Debugf("Finish redirecting log file %q, closing it", logPath)
225			f.Close()
226		}()
227	} else {
228		stdout = cio.NewDiscardLogger()
229		stderr = cio.NewDiscardLogger()
230	}
231	return
232}
233