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	"encoding/json"
21	goruntime "runtime"
22
23	"github.com/containerd/containerd"
24	"github.com/containerd/containerd/errdefs"
25	cni "github.com/containerd/go-cni"
26	runtimespec "github.com/opencontainers/runtime-spec/specs-go"
27	"github.com/pkg/errors"
28	"golang.org/x/net/context"
29	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
30
31	sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
32)
33
34// PodSandboxStatus returns the status of the PodSandbox.
35func (c *criService) PodSandboxStatus(ctx context.Context, r *runtime.PodSandboxStatusRequest) (*runtime.PodSandboxStatusResponse, error) {
36	sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
37	if err != nil {
38		return nil, errors.Wrap(err, "an error occurred when try to find sandbox")
39	}
40
41	ip, additionalIPs, err := c.getIPs(sandbox)
42	if err != nil {
43		return nil, errors.Wrap(err, "failed to get sandbox ip")
44	}
45	status := toCRISandboxStatus(sandbox.Metadata, sandbox.Status.Get(), ip, additionalIPs)
46	if status.GetCreatedAt() == 0 {
47		// CRI doesn't allow CreatedAt == 0.
48		info, err := sandbox.Container.Info(ctx)
49		if err != nil {
50			return nil, errors.Wrapf(err, "failed to get CreatedAt for sandbox container in %q state", status.State)
51		}
52		status.CreatedAt = info.CreatedAt.UnixNano()
53	}
54	if !r.GetVerbose() {
55		return &runtime.PodSandboxStatusResponse{Status: status}, nil
56	}
57
58	// Generate verbose information.
59	info, err := toCRISandboxInfo(ctx, sandbox)
60	if err != nil {
61		return nil, errors.Wrap(err, "failed to get verbose sandbox container info")
62	}
63
64	return &runtime.PodSandboxStatusResponse{
65		Status: status,
66		Info:   info,
67	}, nil
68}
69
70func (c *criService) getIPs(sandbox sandboxstore.Sandbox) (string, []string, error) {
71	config := sandbox.Config
72
73	if goruntime.GOOS != "windows" &&
74		config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE {
75		// For sandboxes using the node network we are not
76		// responsible for reporting the IP.
77		return "", nil, nil
78	}
79
80	if closed, err := sandbox.NetNS.Closed(); err != nil {
81		return "", nil, errors.Wrap(err, "check network namespace closed")
82	} else if closed {
83		return "", nil, nil
84	}
85
86	return sandbox.IP, sandbox.AdditionalIPs, nil
87}
88
89// toCRISandboxStatus converts sandbox metadata into CRI pod sandbox status.
90func toCRISandboxStatus(meta sandboxstore.Metadata, status sandboxstore.Status, ip string, additionalIPs []string) *runtime.PodSandboxStatus {
91	// Set sandbox state to NOTREADY by default.
92	state := runtime.PodSandboxState_SANDBOX_NOTREADY
93	if status.State == sandboxstore.StateReady {
94		state = runtime.PodSandboxState_SANDBOX_READY
95	}
96	nsOpts := meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions()
97	var ips []*runtime.PodIP
98	for _, additionalIP := range additionalIPs {
99		ips = append(ips, &runtime.PodIP{Ip: additionalIP})
100	}
101	return &runtime.PodSandboxStatus{
102		Id:        meta.ID,
103		Metadata:  meta.Config.GetMetadata(),
104		State:     state,
105		CreatedAt: status.CreatedAt.UnixNano(),
106		Network: &runtime.PodSandboxNetworkStatus{
107			Ip:            ip,
108			AdditionalIps: ips,
109		},
110		Linux: &runtime.LinuxPodSandboxStatus{
111			Namespaces: &runtime.Namespace{
112				Options: &runtime.NamespaceOption{
113					Network: nsOpts.GetNetwork(),
114					Pid:     nsOpts.GetPid(),
115					Ipc:     nsOpts.GetIpc(),
116				},
117			},
118		},
119		Labels:         meta.Config.GetLabels(),
120		Annotations:    meta.Config.GetAnnotations(),
121		RuntimeHandler: meta.RuntimeHandler,
122	}
123}
124
125// SandboxInfo is extra information for sandbox.
126// TODO (mikebrow): discuss predefining constants structures for some or all of these field names in CRI
127type SandboxInfo struct {
128	Pid         uint32 `json:"pid"`
129	Status      string `json:"processStatus"`
130	NetNSClosed bool   `json:"netNamespaceClosed"`
131	Image       string `json:"image"`
132	SnapshotKey string `json:"snapshotKey"`
133	Snapshotter string `json:"snapshotter"`
134	// Note: a new field `RuntimeHandler` has been added into the CRI PodSandboxStatus struct, and
135	// should be set. This `RuntimeHandler` field will be deprecated after containerd 1.3 (tracked
136	// in https://github.com/containerd/cri/issues/1064).
137	RuntimeHandler string                    `json:"runtimeHandler"` // see the Note above
138	RuntimeType    string                    `json:"runtimeType"`
139	RuntimeOptions interface{}               `json:"runtimeOptions"`
140	Config         *runtime.PodSandboxConfig `json:"config"`
141	RuntimeSpec    *runtimespec.Spec         `json:"runtimeSpec"`
142	CNIResult      *cni.Result               `json:"cniResult"`
143}
144
145// toCRISandboxInfo converts internal container object information to CRI sandbox status response info map.
146func toCRISandboxInfo(ctx context.Context, sandbox sandboxstore.Sandbox) (map[string]string, error) {
147	container := sandbox.Container
148	task, err := container.Task(ctx, nil)
149	if err != nil && !errdefs.IsNotFound(err) {
150		return nil, errors.Wrap(err, "failed to get sandbox container task")
151	}
152
153	var processStatus containerd.ProcessStatus
154	if task != nil {
155		taskStatus, err := task.Status(ctx)
156		if err != nil {
157			return nil, errors.Wrap(err, "failed to get task status")
158		}
159
160		processStatus = taskStatus.Status
161	}
162
163	si := &SandboxInfo{
164		Pid:            sandbox.Status.Get().Pid,
165		RuntimeHandler: sandbox.RuntimeHandler,
166		Status:         string(processStatus),
167		Config:         sandbox.Config,
168		CNIResult:      sandbox.CNIResult,
169	}
170
171	if si.Status == "" {
172		// If processStatus is empty, it means that the task is deleted. Apply "deleted"
173		// status which does not exist in containerd.
174		si.Status = "deleted"
175	}
176
177	if sandbox.NetNS != nil {
178		// Add network closed information if sandbox is not using host network.
179		closed, err := sandbox.NetNS.Closed()
180		if err != nil {
181			return nil, errors.Wrap(err, "failed to check network namespace closed")
182		}
183		si.NetNSClosed = closed
184	}
185
186	spec, err := container.Spec(ctx)
187	if err != nil {
188		return nil, errors.Wrap(err, "failed to get sandbox container runtime spec")
189	}
190	si.RuntimeSpec = spec
191
192	ctrInfo, err := container.Info(ctx)
193	if err != nil {
194		return nil, errors.Wrap(err, "failed to get sandbox container info")
195	}
196	// Do not use config.SandboxImage because the configuration might
197	// be changed during restart. It may not reflect the actual image
198	// used by the sandbox container.
199	si.Image = ctrInfo.Image
200	si.SnapshotKey = ctrInfo.SnapshotKey
201	si.Snapshotter = ctrInfo.Snapshotter
202
203	runtimeOptions, err := getRuntimeOptions(ctrInfo)
204	if err != nil {
205		return nil, errors.Wrap(err, "failed to get runtime options")
206	}
207	si.RuntimeType = ctrInfo.Runtime.Name
208	si.RuntimeOptions = runtimeOptions
209
210	infoBytes, err := json.Marshal(si)
211	if err != nil {
212		return nil, errors.Wrapf(err, "failed to marshal info %v", si)
213	}
214	return map[string]string{
215		"info": string(infoBytes),
216	}, nil
217}
218