| /* |
| Copyright The containerd Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package server |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| |
| containerstore "github.com/containerd/containerd/v2/internal/cri/store/container" |
| "github.com/containerd/containerd/v2/internal/cri/util" |
| "github.com/containerd/errdefs" |
| "github.com/containerd/log" |
| |
| runtimespec "github.com/opencontainers/runtime-spec/specs-go" |
| runtime "k8s.io/cri-api/pkg/apis/runtime/v1" |
| ) |
| |
| // ContainerStatus inspects the container and returns the status. |
| func (c *criService) ContainerStatus(ctx context.Context, r *runtime.ContainerStatusRequest) (*runtime.ContainerStatusResponse, error) { |
| container, err := c.containerStore.Get(r.GetContainerId()) |
| if err != nil { |
| return nil, fmt.Errorf("an error occurred when try to find container %q: %w", r.GetContainerId(), err) |
| } |
| |
| // TODO(random-liu): Clean up the following logic in CRI. |
| // Current assumption: |
| // * ImageSpec in container config is image ID. |
| // * ImageSpec in container status is image tag. |
| // * ImageRef in container status is repo digest. |
| spec := container.Config.GetImage() |
| imageRef := container.ImageRef |
| image, err := c.GetImage(imageRef) |
| if err != nil { |
| if !errdefs.IsNotFound(err) { |
| return nil, fmt.Errorf("failed to get image %q: %w", imageRef, err) |
| } |
| } else { |
| repoTags, repoDigests := util.ParseImageReferences(image.References) |
| if len(repoTags) > 0 { |
| // Based on current behavior of dockershim, this field should be |
| // image tag. |
| spec = &runtime.ImageSpec{Image: repoTags[0]} |
| } |
| if len(repoDigests) > 0 { |
| // Based on the CRI definition, this field will be consumed by user. |
| imageRef = repoDigests[0] |
| } |
| } |
| status, err := toCRIContainerStatus(ctx, container, spec, imageRef) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get ContainerStatus: %w", err) |
| } |
| if status.GetCreatedAt() == 0 { |
| // CRI doesn't allow CreatedAt == 0. |
| info, err := container.Container.Info(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get CreatedAt in %q state: %w", status.State, err) |
| } |
| status.CreatedAt = info.CreatedAt.UnixNano() |
| } |
| |
| info, err := toCRIContainerInfo(ctx, container, r.GetVerbose()) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get verbose container info: %w", err) |
| } |
| |
| return &runtime.ContainerStatusResponse{ |
| Status: status, |
| Info: info, |
| }, nil |
| } |
| |
| // toCRIContainerStatus converts internal container object to CRI container status. |
| func toCRIContainerStatus(ctx context.Context, container containerstore.Container, spec *runtime.ImageSpec, imageRef string) (*runtime.ContainerStatus, error) { |
| meta := container.Metadata |
| status := container.Status.Get() |
| reason := status.Reason |
| if status.State() == runtime.ContainerState_CONTAINER_EXITED && reason == "" { |
| if status.ExitCode == 0 { |
| reason = completeExitReason |
| } else { |
| reason = errorExitReason |
| } |
| } |
| |
| // If container is in the created state, not set started and finished unix timestamps |
| var st, ft int64 |
| switch status.State() { |
| case runtime.ContainerState_CONTAINER_RUNNING: |
| // If container is in the running state, set started unix timestamps |
| st = status.StartedAt |
| case runtime.ContainerState_CONTAINER_EXITED, runtime.ContainerState_CONTAINER_UNKNOWN: |
| st, ft = status.StartedAt, status.FinishedAt |
| } |
| |
| runtimeUser, err := toCRIContainerUser(ctx, container) |
| if err != nil { |
| log.G(ctx).WithField("Id", meta.ID).WithError(err).Debug("failed to get ContainerUser. returning an empty ContainerUser") |
| runtimeUser = &runtime.ContainerUser{} |
| } |
| |
| return &runtime.ContainerStatus{ |
| Id: meta.ID, |
| Metadata: meta.Config.GetMetadata(), |
| State: status.State(), |
| CreatedAt: status.CreatedAt, |
| StartedAt: st, |
| FinishedAt: ft, |
| ExitCode: status.ExitCode, |
| Image: spec, |
| ImageRef: imageRef, |
| Reason: reason, |
| Message: status.Message, |
| Labels: meta.Config.GetLabels(), |
| Annotations: meta.Config.GetAnnotations(), |
| Mounts: meta.Config.GetMounts(), |
| LogPath: meta.LogPath, |
| Resources: status.Resources, |
| User: runtimeUser, |
| }, nil |
| } |
| |
| // ContainerInfo is extra information for a container. |
| type ContainerInfo struct { |
| // TODO(random-liu): Add sandboxID in CRI container status. |
| SandboxID string `json:"sandboxID"` |
| Pid uint32 `json:"pid"` |
| Removing bool `json:"removing"` |
| SnapshotKey string `json:"snapshotKey"` |
| Snapshotter string `json:"snapshotter"` |
| RuntimeType string `json:"runtimeType"` |
| RuntimeOptions interface{} `json:"runtimeOptions"` |
| Config *runtime.ContainerConfig `json:"config"` |
| RuntimeSpec *runtimespec.Spec `json:"runtimeSpec"` |
| } |
| |
| // toCRIContainerInfo converts internal container object information to CRI container status response info map. |
| func toCRIContainerInfo(ctx context.Context, container containerstore.Container, verbose bool) (map[string]string, error) { |
| if !verbose { |
| return nil, nil |
| } |
| |
| meta := container.Metadata |
| status := container.Status.Get() |
| |
| // TODO(random-liu): Change CRI status info to use array instead of map. |
| ci := &ContainerInfo{ |
| SandboxID: container.SandboxID, |
| Pid: status.Pid, |
| Removing: status.Removing, |
| Config: meta.Config, |
| } |
| |
| var err error |
| ci.RuntimeSpec, err = container.Container.Spec(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get container runtime spec: %w", err) |
| } |
| |
| ctrInfo, err := container.Container.Info(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get container info: %w", err) |
| } |
| ci.SnapshotKey = ctrInfo.SnapshotKey |
| ci.Snapshotter = ctrInfo.Snapshotter |
| |
| runtimeOptions, err := getRuntimeOptions(ctrInfo) |
| if err != nil { |
| return nil, fmt.Errorf("failed to get runtime options: %w", err) |
| } |
| ci.RuntimeType = ctrInfo.Runtime.Name |
| ci.RuntimeOptions = runtimeOptions |
| |
| infoBytes, err := json.Marshal(ci) |
| if err != nil { |
| return nil, fmt.Errorf("failed to marshal info %v: %w", ci, err) |
| } |
| return map[string]string{ |
| "info": string(infoBytes), |
| }, nil |
| } |