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