github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/manager/runtime.go (about)

     1  /*
     2  Copyright 2016-2018 Mirantis
     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  
    17  package manager
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"time"
    23  
    24  	cnitypes "github.com/containernetworking/cni/pkg/types"
    25  	cnicurrent "github.com/containernetworking/cni/pkg/types/current"
    26  	"github.com/golang/glog"
    27  	"github.com/jonboulle/clockwork"
    28  	"golang.org/x/net/context"
    29  	kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
    30  
    31  	"github.com/Mirantis/virtlet/pkg/cni"
    32  	"github.com/Mirantis/virtlet/pkg/libvirttools"
    33  	"github.com/Mirantis/virtlet/pkg/metadata"
    34  	"github.com/Mirantis/virtlet/pkg/metadata/types"
    35  	"github.com/Mirantis/virtlet/pkg/tapmanager"
    36  )
    37  
    38  const (
    39  	runtimeAPIVersion = "0.1.0"
    40  	runtimeName       = "virtlet"
    41  	runtimeVersion    = "0.1.0"
    42  )
    43  
    44  // StreamServer denotes a server that handles Attach and PortForward requests.
    45  type StreamServer interface {
    46  	GetAttach(req *kubeapi.AttachRequest) (*kubeapi.AttachResponse, error)
    47  	GetPortForward(req *kubeapi.PortForwardRequest) (*kubeapi.PortForwardResponse, error)
    48  }
    49  
    50  // GCHandler performs GC when a container is deleted.
    51  type GCHandler interface {
    52  	GC() error
    53  }
    54  
    55  // VirtletRuntimeService handles CRI runtime service calls.
    56  type VirtletRuntimeService struct {
    57  	virtTool      *libvirttools.VirtualizationTool
    58  	metadataStore metadata.Store
    59  	fdManager     tapmanager.FDManager
    60  	streamServer  StreamServer
    61  	gcHandler     GCHandler
    62  	clock         clockwork.Clock
    63  }
    64  
    65  // NewVirtletRuntimeService returns a new instance of VirtletRuntimeService.
    66  func NewVirtletRuntimeService(
    67  	virtTool *libvirttools.VirtualizationTool,
    68  	metadataStore metadata.Store,
    69  	fdManager tapmanager.FDManager,
    70  	streamServer StreamServer,
    71  	gcHandler GCHandler,
    72  	clock clockwork.Clock) *VirtletRuntimeService {
    73  	if clock == nil {
    74  		clock = clockwork.NewRealClock()
    75  	}
    76  	return &VirtletRuntimeService{
    77  		virtTool:      virtTool,
    78  		metadataStore: metadataStore,
    79  		fdManager:     fdManager,
    80  		streamServer:  streamServer,
    81  		gcHandler:     gcHandler,
    82  		clock:         clock,
    83  	}
    84  }
    85  
    86  // Version implements Version method of CRI.
    87  func (v *VirtletRuntimeService) Version(ctx context.Context, in *kubeapi.VersionRequest) (*kubeapi.VersionResponse, error) {
    88  	vRuntimeAPIVersion := runtimeAPIVersion
    89  	vRuntimeName := runtimeName
    90  	vRuntimeVersion := runtimeVersion
    91  	return &kubeapi.VersionResponse{
    92  		Version:           vRuntimeAPIVersion,
    93  		RuntimeName:       vRuntimeName,
    94  		RuntimeVersion:    vRuntimeVersion,
    95  		RuntimeApiVersion: vRuntimeVersion,
    96  	}, nil
    97  }
    98  
    99  //
   100  // Sandboxes
   101  //
   102  
   103  // RunPodSandbox implements RunPodSandbox method of CRI.
   104  func (v *VirtletRuntimeService) RunPodSandbox(ctx context.Context, in *kubeapi.RunPodSandboxRequest) (response *kubeapi.RunPodSandboxResponse, retErr error) {
   105  	config := in.GetConfig()
   106  	if config == nil {
   107  		return nil, errors.New("no pod sandbox config passed to RunPodSandbox")
   108  	}
   109  	podName := "<no metadata>"
   110  	if config.Metadata != nil {
   111  		podName = config.Metadata.Name
   112  	}
   113  	if err := validatePodSandboxConfig(config); err != nil {
   114  		return nil, err
   115  	}
   116  	podID := config.Metadata.Uid
   117  	podNs := config.Metadata.Namespace
   118  
   119  	// Check if sandbox already exists, it may happen when virtlet restarts and kubelet "thinks" that sandbox disappered
   120  	sandbox := v.metadataStore.PodSandbox(podID)
   121  	sandboxInfo, err := sandbox.Retrieve()
   122  	if err == nil && sandboxInfo != nil {
   123  		if sandboxInfo.State == types.PodSandboxState_SANDBOX_READY {
   124  			return &kubeapi.RunPodSandboxResponse{
   125  				PodSandboxId: podID,
   126  			}, nil
   127  		}
   128  	}
   129  
   130  	state := kubeapi.PodSandboxState_SANDBOX_READY
   131  	pnd := &tapmanager.PodNetworkDesc{
   132  		PodID:   podID,
   133  		PodNs:   podNs,
   134  		PodName: podName,
   135  	}
   136  	// Mimic kubelet's method of handling nameservers.
   137  	// As of k8s 1.5.2, kubelet doesn't use any nameserver information from CNI.
   138  	// (TODO: recheck this for 1.6)
   139  	// CNI is used just to configure the network namespace and CNI DNS
   140  	// info is ignored. Instead of this, DnsConfig from PodSandboxConfig
   141  	// is used to configure container's resolv.conf.
   142  	if config.DnsConfig != nil {
   143  		pnd.DNS = &cnitypes.DNS{
   144  			Nameservers: config.DnsConfig.Servers,
   145  			Search:      config.DnsConfig.Searches,
   146  			Options:     config.DnsConfig.Options,
   147  		}
   148  	}
   149  
   150  	fdPayload := &tapmanager.GetFDPayload{Description: pnd}
   151  	csnBytes, err := v.fdManager.AddFDs(podID, fdPayload)
   152  	// The reason for defer here is that it is also necessary to ReleaseFDs if AddFDs fail
   153  	// Try to clean up CNI netns (this may be necessary e.g. in case of multiple CNI plugins with CNI Genie)
   154  	defer func() {
   155  		if retErr != nil {
   156  			// Try to clean up CNI netns if we couldn't add the pod to the metadata store or if AddFDs call wasn't
   157  			// successful to avoid leaking resources
   158  			if fdErr := v.fdManager.ReleaseFDs(podID); fdErr != nil {
   159  				glog.Errorf("Error removing pod %s (%s) from CNI network: %v", podName, podID, fdErr)
   160  			}
   161  		}
   162  	}()
   163  	if err != nil {
   164  		return nil, fmt.Errorf("Error adding pod %s (%s) to CNI network: %v", podName, podID, err)
   165  	}
   166  
   167  	psi, err := metadata.NewPodSandboxInfo(
   168  		CRIPodSandboxConfigToPodSandboxConfig(config),
   169  		csnBytes, types.PodSandboxState(state), v.clock)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	sandbox = v.metadataStore.PodSandbox(config.Metadata.Uid)
   175  	if err := sandbox.Save(
   176  		func(c *types.PodSandboxInfo) (*types.PodSandboxInfo, error) {
   177  			return psi, nil
   178  		},
   179  	); err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	return &kubeapi.RunPodSandboxResponse{
   184  		PodSandboxId: podID,
   185  	}, nil
   186  }
   187  
   188  // StopPodSandbox implements StopPodSandbox method of CRI.
   189  func (v *VirtletRuntimeService) StopPodSandbox(ctx context.Context, in *kubeapi.StopPodSandboxRequest) (*kubeapi.StopPodSandboxResponse, error) {
   190  	sandbox := v.metadataStore.PodSandbox(in.PodSandboxId)
   191  	switch sandboxInfo, err := sandbox.Retrieve(); {
   192  	case err != nil:
   193  		return nil, err
   194  	case sandboxInfo == nil:
   195  		return nil, fmt.Errorf("sandbox %q not found in Virtlet metadata store", in.PodSandboxId)
   196  	// check if the sandbox is already stopped
   197  	case sandboxInfo.State != types.PodSandboxState_SANDBOX_NOTREADY:
   198  		if err := sandbox.Save(
   199  			func(c *types.PodSandboxInfo) (*types.PodSandboxInfo, error) {
   200  				// make sure the pod is not removed during the call
   201  				if c != nil {
   202  					c.State = types.PodSandboxState_SANDBOX_NOTREADY
   203  				}
   204  				return c, nil
   205  			},
   206  		); err != nil {
   207  			return nil, err
   208  		}
   209  
   210  		if err := v.fdManager.ReleaseFDs(in.PodSandboxId); err != nil {
   211  			glog.Errorf("Error releasing tap fd for the pod %q: %v", in.PodSandboxId, err)
   212  		}
   213  	}
   214  
   215  	response := &kubeapi.StopPodSandboxResponse{}
   216  	return response, nil
   217  }
   218  
   219  // RemovePodSandbox method implements RemovePodSandbox from CRI.
   220  func (v *VirtletRuntimeService) RemovePodSandbox(ctx context.Context, in *kubeapi.RemovePodSandboxRequest) (*kubeapi.RemovePodSandboxResponse, error) {
   221  	podSandboxID := in.PodSandboxId
   222  
   223  	if err := v.metadataStore.PodSandbox(podSandboxID).Save(
   224  		func(c *types.PodSandboxInfo) (*types.PodSandboxInfo, error) {
   225  			return nil, nil
   226  		},
   227  	); err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	response := &kubeapi.RemovePodSandboxResponse{}
   232  	return response, nil
   233  }
   234  
   235  // PodSandboxStatus method implements PodSandboxStatus from CRI.
   236  func (v *VirtletRuntimeService) PodSandboxStatus(ctx context.Context, in *kubeapi.PodSandboxStatusRequest) (*kubeapi.PodSandboxStatusResponse, error) {
   237  	podSandboxID := in.PodSandboxId
   238  
   239  	sandbox := v.metadataStore.PodSandbox(podSandboxID)
   240  	sandboxInfo, err := sandbox.Retrieve()
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	if sandboxInfo == nil {
   245  		return nil, fmt.Errorf("sandbox %q not found in Virtlet metadata store", podSandboxID)
   246  	}
   247  	status := PodSandboxInfoToCRIPodSandboxStatus(sandboxInfo)
   248  
   249  	var cniResult *cnicurrent.Result
   250  	if sandboxInfo.ContainerSideNetwork != nil {
   251  		cniResult = sandboxInfo.ContainerSideNetwork.Result
   252  	}
   253  
   254  	ip := cni.GetPodIP(cniResult)
   255  	if ip != "" {
   256  		status.Network = &kubeapi.PodSandboxNetworkStatus{Ip: ip}
   257  	}
   258  
   259  	response := &kubeapi.PodSandboxStatusResponse{Status: status}
   260  	return response, nil
   261  }
   262  
   263  // ListPodSandbox method implements ListPodSandbox from CRI.
   264  func (v *VirtletRuntimeService) ListPodSandbox(ctx context.Context, in *kubeapi.ListPodSandboxRequest) (*kubeapi.ListPodSandboxResponse, error) {
   265  	filter := CRIPodSandboxFilterToPodSandboxFilter(in.GetFilter())
   266  	sandboxes, err := v.metadataStore.ListPodSandboxes(filter)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	var podSandboxList []*kubeapi.PodSandbox
   271  	for _, sandbox := range sandboxes {
   272  		sandboxInfo, err := sandbox.Retrieve()
   273  		if err != nil {
   274  			glog.Errorf("Error retrieving pod sandbox %q", sandbox.GetID())
   275  		}
   276  		if sandboxInfo != nil {
   277  			podSandboxList = append(podSandboxList, PodSandboxInfoToCRIPodSandbox(sandboxInfo))
   278  		}
   279  	}
   280  	response := &kubeapi.ListPodSandboxResponse{Items: podSandboxList}
   281  	return response, nil
   282  }
   283  
   284  //
   285  // Containers
   286  //
   287  
   288  // CreateContainer method implements CreateContainer from CRI.
   289  func (v *VirtletRuntimeService) CreateContainer(ctx context.Context, in *kubeapi.CreateContainerRequest) (*kubeapi.CreateContainerResponse, error) {
   290  	config := in.GetConfig()
   291  	podSandboxID := in.PodSandboxId
   292  	name := config.GetMetadata().Name
   293  
   294  	sandboxInfo, err := v.metadataStore.PodSandbox(podSandboxID).Retrieve()
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  	if sandboxInfo == nil {
   299  		return nil, fmt.Errorf("sandbox %q not in Virtlet metadata store", podSandboxID)
   300  	}
   301  
   302  	// Was a container already started in this sandbox?
   303  	// NOTE: there is no distinction between lack of key and other types of
   304  	// errors when accessing boltdb. This will be changed when we switch to
   305  	// storing whole marshaled sandbox metadata as json.
   306  	curContainers, err := v.metadataStore.ListPodContainers(podSandboxID)
   307  	if err != nil {
   308  		glog.V(3).Infof("Error retrieving pod %q containers", podSandboxID)
   309  	} else {
   310  		for _, container := range curContainers {
   311  			// TODO: check container name; if it's the same, update the network config
   312  			glog.V(3).Infof("CreateContainer: there's already a container in the sandbox (id: %s)", container.GetID())
   313  			//err := v.updateContainer(sandboxInfo, container.GetID())
   314  			err := v.virtTool.UpdateContainerNetwork(container.GetID(), sandboxInfo.ContainerSideNetwork)
   315  			if err != nil {
   316  				return nil, err
   317  			}
   318  			response := &kubeapi.CreateContainerResponse{ContainerId: container.GetID()}
   319  			return response, nil
   320  		}
   321  	}
   322  
   323  	fdKey := podSandboxID
   324  	vmConfig, err := GetVMConfig(in, sandboxInfo.ContainerSideNetwork)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	if sandboxInfo.ContainerSideNetwork == nil || sandboxInfo.ContainerSideNetwork.Result == nil {
   329  		fdKey = ""
   330  	}
   331  
   332  	uuid, err := v.virtTool.CreateContainer(vmConfig, fdKey)
   333  	if err != nil {
   334  		glog.Errorf("Error creating container %s: %v", name, err)
   335  		return nil, err
   336  	}
   337  
   338  	response := &kubeapi.CreateContainerResponse{ContainerId: uuid}
   339  	return response, nil
   340  }
   341  
   342  // StartContainer method implements StartContainer from CRI.
   343  func (v *VirtletRuntimeService) StartContainer(ctx context.Context, in *kubeapi.StartContainerRequest) (*kubeapi.StartContainerResponse, error) {
   344  	info, err := v.virtTool.ContainerInfo(in.ContainerId)
   345  	if err == nil && info != nil && info.State == types.ContainerState_CONTAINER_RUNNING {
   346  		glog.V(2).Infof("StartContainer: Container %s is already running", in.ContainerId)
   347  		response := &kubeapi.StartContainerResponse{}
   348  		return response, nil
   349  	}
   350  
   351  	if err := v.virtTool.StartContainer(in.ContainerId); err != nil {
   352  		return nil, err
   353  	}
   354  	response := &kubeapi.StartContainerResponse{}
   355  	return response, nil
   356  }
   357  
   358  // StopContainer method implements StopContainer from CRI.
   359  func (v *VirtletRuntimeService) StopContainer(ctx context.Context, in *kubeapi.StopContainerRequest) (*kubeapi.StopContainerResponse, error) {
   360  	if err := v.virtTool.StopContainer(in.ContainerId, time.Duration(in.Timeout)*time.Second); err != nil {
   361  		return nil, err
   362  	}
   363  	response := &kubeapi.StopContainerResponse{}
   364  	return response, nil
   365  }
   366  
   367  // RemoveContainer method implements RemoveContainer from CRI.
   368  func (v *VirtletRuntimeService) RemoveContainer(ctx context.Context, in *kubeapi.RemoveContainerRequest) (*kubeapi.RemoveContainerResponse, error) {
   369  	if err := v.virtTool.RemoveContainer(in.ContainerId); err != nil {
   370  		return nil, err
   371  	}
   372  
   373  	if err := v.gcHandler.GC(); err != nil {
   374  		return nil, fmt.Errorf("GC error: %v", err)
   375  	}
   376  
   377  	response := &kubeapi.RemoveContainerResponse{}
   378  	return response, nil
   379  }
   380  
   381  // ListContainers method implements ListContainers from CRI.
   382  func (v *VirtletRuntimeService) ListContainers(ctx context.Context, in *kubeapi.ListContainersRequest) (*kubeapi.ListContainersResponse, error) {
   383  	filter := CRIContainerFilterToContainerFilter(in.GetFilter())
   384  	containers, err := v.virtTool.ListContainers(filter)
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  	var r []*kubeapi.Container
   389  	for _, c := range containers {
   390  		r = append(r, ContainerInfoToCRIContainer(c))
   391  	}
   392  	response := &kubeapi.ListContainersResponse{Containers: r}
   393  	return response, nil
   394  }
   395  
   396  // ContainerStatus method implements ContainerStatus from CRI.
   397  func (v *VirtletRuntimeService) ContainerStatus(ctx context.Context, in *kubeapi.ContainerStatusRequest) (*kubeapi.ContainerStatusResponse, error) {
   398  	info, err := v.virtTool.ContainerInfo(in.ContainerId)
   399  	if err != nil {
   400  		return nil, err
   401  	}
   402  
   403  	response := &kubeapi.ContainerStatusResponse{Status: ContainerInfoToCRIContainerStatus(info)}
   404  	return response, nil
   405  }
   406  
   407  // ExecSync is a placeholder for an unimplemented CRI method.
   408  func (v *VirtletRuntimeService) ExecSync(context.Context, *kubeapi.ExecSyncRequest) (*kubeapi.ExecSyncResponse, error) {
   409  	return nil, errors.New("not implemented")
   410  }
   411  
   412  // Exec is a placeholder for an unimplemented CRI method.
   413  func (v *VirtletRuntimeService) Exec(context.Context, *kubeapi.ExecRequest) (*kubeapi.ExecResponse, error) {
   414  	return nil, errors.New("not implemented")
   415  }
   416  
   417  // Attach calls streamer server to implement Attach functionality from CRI.
   418  func (v *VirtletRuntimeService) Attach(ctx context.Context, req *kubeapi.AttachRequest) (*kubeapi.AttachResponse, error) {
   419  	if !req.Stdout && !req.Stderr {
   420  		// Support k8s 1.8 or earlier.
   421  		// We don't care about Stderr because it's not used
   422  		// by the Virtlet stream server.
   423  		req.Stdout = true
   424  	}
   425  	return v.streamServer.GetAttach(req)
   426  }
   427  
   428  // PortForward calls streamer server to implement PortForward functionality from CRI.
   429  func (v *VirtletRuntimeService) PortForward(ctx context.Context, req *kubeapi.PortForwardRequest) (*kubeapi.PortForwardResponse, error) {
   430  	return v.streamServer.GetPortForward(req)
   431  }
   432  
   433  // UpdateRuntimeConfig is a placeholder for an unimplemented CRI method.
   434  func (v *VirtletRuntimeService) UpdateRuntimeConfig(context.Context, *kubeapi.UpdateRuntimeConfigRequest) (*kubeapi.UpdateRuntimeConfigResponse, error) {
   435  	// we don't need to do anything here for now
   436  	return &kubeapi.UpdateRuntimeConfigResponse{}, nil
   437  }
   438  
   439  // UpdateContainerResources stores in domain on libvirt info about Cpuset
   440  // for container then looks for running emulator and tries to adjust its
   441  // current settings through cgroups
   442  func (v *VirtletRuntimeService) UpdateContainerResources(ctx context.Context, req *kubeapi.UpdateContainerResourcesRequest) (*kubeapi.UpdateContainerResourcesResponse, error) {
   443  	setByCgroup, err := v.virtTool.UpdateCpusetsForEmulatorProcess(req.GetContainerId(), req.GetLinux().CpusetCpus)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	if !setByCgroup {
   448  		if err = v.virtTool.UpdateCpusetsInContainerDefinition(req.GetContainerId(), req.GetLinux().CpusetCpus); err != nil {
   449  			return nil, err
   450  		}
   451  	}
   452  	return &kubeapi.UpdateContainerResourcesResponse{}, nil
   453  }
   454  
   455  // Status method implements Status from CRI for both types of service, Image and Runtime.
   456  func (v *VirtletRuntimeService) Status(context.Context, *kubeapi.StatusRequest) (*kubeapi.StatusResponse, error) {
   457  	ready := true
   458  	runtimeReadyStr := kubeapi.RuntimeReady
   459  	networkReadyStr := kubeapi.NetworkReady
   460  	return &kubeapi.StatusResponse{
   461  		Status: &kubeapi.RuntimeStatus{
   462  			Conditions: []*kubeapi.RuntimeCondition{
   463  				{
   464  					Type:   runtimeReadyStr,
   465  					Status: ready,
   466  				},
   467  				{
   468  					Type:   networkReadyStr,
   469  					Status: ready,
   470  				},
   471  			},
   472  		},
   473  	}, nil
   474  }
   475  
   476  // ContainerStats returns cpu/memory/disk usage for particular container id
   477  func (v *VirtletRuntimeService) ContainerStats(ctx context.Context, in *kubeapi.ContainerStatsRequest) (*kubeapi.ContainerStatsResponse, error) {
   478  	info, err := v.virtTool.ContainerInfo(in.ContainerId)
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  	vs, err := v.virtTool.VMStats(info.Id, info.Name)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  	fsstats, err := v.virtTool.ImageManager().FilesystemStats()
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  	return &kubeapi.ContainerStatsResponse{
   491  		Stats: VMStatsToCRIContainerStats(*vs, fsstats.Mountpoint),
   492  	}, nil
   493  }
   494  
   495  // ListContainerStats returns stats (same as ContainerStats) for containers
   496  // selected by filter
   497  func (v *VirtletRuntimeService) ListContainerStats(ctx context.Context, in *kubeapi.ListContainerStatsRequest) (*kubeapi.ListContainerStatsResponse, error) {
   498  	filter := CRIContainerStatsFilterToVMStatsFilter(in.GetFilter())
   499  	vmstatsList, err := v.virtTool.ListVMStats(filter)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	fsstats, err := v.virtTool.ImageManager().FilesystemStats()
   504  	if err != nil {
   505  		return nil, err
   506  	}
   507  	var stats []*kubeapi.ContainerStats
   508  	for _, vs := range vmstatsList {
   509  		stats = append(stats, VMStatsToCRIContainerStats(vs, fsstats.Mountpoint))
   510  	}
   511  
   512  	return &kubeapi.ListContainerStatsResponse{
   513  		Stats: stats,
   514  	}, nil
   515  }
   516  
   517  // VMStatsToCRIContainerStats converts internal representation of vm/container stats
   518  // to corresponding kubeapi type object
   519  func VMStatsToCRIContainerStats(vs types.VMStats, mountpoint string) *kubeapi.ContainerStats {
   520  	return &kubeapi.ContainerStats{
   521  		Attributes: &kubeapi.ContainerAttributes{
   522  			Id: vs.ContainerID,
   523  			Metadata: &kubeapi.ContainerMetadata{
   524  				Name: vs.ContainerID,
   525  			},
   526  		},
   527  		Cpu: &kubeapi.CpuUsage{
   528  			Timestamp:            vs.Timestamp,
   529  			UsageCoreNanoSeconds: &kubeapi.UInt64Value{Value: vs.CpuUsage},
   530  		},
   531  		Memory: &kubeapi.MemoryUsage{
   532  			Timestamp:       vs.Timestamp,
   533  			WorkingSetBytes: &kubeapi.UInt64Value{Value: vs.MemoryUsage},
   534  		},
   535  		WritableLayer: &kubeapi.FilesystemUsage{
   536  			Timestamp: vs.Timestamp,
   537  			FsId: &kubeapi.FilesystemIdentifier{
   538  				Mountpoint: mountpoint,
   539  			},
   540  			UsedBytes:  &kubeapi.UInt64Value{Value: vs.FsBytes},
   541  			InodesUsed: &kubeapi.UInt64Value{Value: 1},
   542  		},
   543  	}
   544  }
   545  
   546  // ReopenContainerLog is a placeholder for an unimplemented CRI method.
   547  func (v *VirtletRuntimeService) ReopenContainerLog(ctx context.Context, in *kubeapi.ReopenContainerLogRequest) (*kubeapi.ReopenContainerLogResponse, error) {
   548  	return &kubeapi.ReopenContainerLogResponse{}, nil
   549  }
   550  
   551  func validatePodSandboxConfig(config *kubeapi.PodSandboxConfig) error {
   552  	if config.GetMetadata() == nil {
   553  		return errors.New("sandbox config is missing Metadata attribute")
   554  	}
   555  
   556  	return nil
   557  }