Files
cloudpods/pkg/hostman/guestman/pod.go
T
Zexi Li ab2dd4db55 feat(host): pod volume metrics (#21291)
* feat(host): pod volume metrics

* feat(host): add pod_netio for pod
2024-09-25 14:12:03 +08:00

2095 lines
67 KiB
Go

// Copyright 2019 Yunion
//
// 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 guestman
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/onecloud/pkg/apis"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/hostman/container/device"
"yunion.io/x/onecloud/pkg/hostman/container/lifecycle"
"yunion.io/x/onecloud/pkg/hostman/container/prober"
proberesults "yunion.io/x/onecloud/pkg/hostman/container/prober/results"
"yunion.io/x/onecloud/pkg/hostman/container/status"
"yunion.io/x/onecloud/pkg/hostman/container/volume_mount"
"yunion.io/x/onecloud/pkg/hostman/guestman/desc"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/hostman/hostinfo"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/isolated_device"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/hostman/storageman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
computemod "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
imagemod "yunion.io/x/onecloud/pkg/mcclient/modules/image"
"yunion.io/x/onecloud/pkg/util/cgrouputils/cpuset"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/pod"
"yunion.io/x/onecloud/pkg/util/pod/logs"
"yunion.io/x/onecloud/pkg/util/pod/nerdctl"
"yunion.io/x/onecloud/pkg/util/procutils"
)
func (m *SGuestManager) startContainerProbeManager() {
livenessManager := proberesults.NewManager()
startupManager := proberesults.NewManager()
man := prober.NewManager(status.NewManager(), livenessManager, startupManager, newContainerRunner(m))
m.containerProbeManager = man
man.Start()
}
func (m *SGuestManager) GetContainerProbeManager() prober.Manager {
return m.containerProbeManager
}
func newContainerRunner(man *SGuestManager) *containerRunner {
return &containerRunner{man}
}
type containerRunner struct {
manager *SGuestManager
}
func (cr *containerRunner) RunInContainer(pod *desc.SGuestDesc, containerId string, cmd []string, timeout time.Duration) ([]byte, error) {
srv, ok := cr.manager.GetServer(pod.Uuid)
if !ok {
return nil, errors.Wrapf(httperrors.ErrNotFound, "server %s not found", pod.Uuid)
}
s := srv.(*sPodGuestInstance)
ctrCriId, err := s.getContainerCRIId(containerId)
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
cli := s.getCRI().GetRuntimeClient()
resp, err := cli.ExecSync(context.Background(), &runtimeapi.ExecSyncRequest{
ContainerId: ctrCriId,
Cmd: cmd,
Timeout: int64(timeout),
})
if err != nil {
return nil, errors.Wrapf(err, "exec sync %#v to %s", cmd, ctrCriId)
}
return append(resp.Stdout, resp.Stderr...), nil
}
type PodInstance interface {
GuestRuntimeInstance
GetContainerById(ctrId string) *hostapi.ContainerDesc
CreateContainer(ctx context.Context, userCred mcclient.TokenCredential, id string, input *hostapi.ContainerCreateInput) (jsonutils.JSONObject, error)
StartContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerCreateInput) (jsonutils.JSONObject, error)
DeleteContainer(ctx context.Context, cred mcclient.TokenCredential, id string) (jsonutils.JSONObject, error)
SyncContainerStatus(ctx context.Context, cred mcclient.TokenCredential, ctrId string) (jsonutils.JSONObject, error)
StopContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, body jsonutils.JSONObject) (jsonutils.JSONObject, error)
PullImage(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerPullImageInput) (jsonutils.JSONObject, error)
SaveVolumeMountToImage(ctx context.Context, userCred mcclient.TokenCredential, input *hostapi.ContainerSaveVolumeMountToImageInput, ctrId string) (jsonutils.JSONObject, error)
ExecContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *computeapi.ContainerExecInput) (*url.URL, error)
ContainerExecSync(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *computeapi.ContainerExecSyncInput) (jsonutils.JSONObject, error)
SetContainerResourceLimit(ctrId string, limit *apis.ContainerResources) (jsonutils.JSONObject, error)
CommitContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerCommitInput) (jsonutils.JSONObject, error)
ReadLogs(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *computeapi.PodLogOptions, stdout, stderr io.Writer) error
// for monitoring
GetVolumeMountUsages() (map[ContainerVolumeKey]*volume_mount.ContainerVolumeMountUsage, error)
}
type sContainer struct {
Id string `json:"id"`
Index int `json:"index"`
CRIId string `json:"cri_id"`
}
func newContainer(id string) *sContainer {
return &sContainer{
Id: id,
}
}
type startStatHelper struct {
podId string
homeDir string
}
func newStartStatHelper(podId string, homeDir string) *startStatHelper {
return &startStatHelper{
podId: podId,
homeDir: homeDir,
}
}
func (h startStatHelper) getPodFile() string {
return filepath.Join(h.homeDir, "pod-start.stat")
}
func (h startStatHelper) IsPodFileExists() bool {
return fileutils2.Exists(h.getPodFile())
}
func (h startStatHelper) createStatFile(fp string) error {
if fileutils2.Exists(fp) {
return nil
}
if err := pod.EnsureFile(fp, "", "755"); err != nil {
return errors.Wrapf(err, "ensure file %s", fp)
}
return nil
}
func (h startStatHelper) removeStatFile(fp string) error {
if !fileutils2.Exists(fp) {
return nil
}
if err := os.Remove(fp); err != nil {
return errors.Wrapf(err, "remove file %s", fp)
}
return nil
}
func (h startStatHelper) CreatePodFile() error {
return h.createStatFile(h.getPodFile())
}
func (h startStatHelper) RemovePodFile() error {
return h.removeStatFile(h.getPodFile())
}
func (h startStatHelper) getContainerFile(ctrId string) string {
return filepath.Join(h.homeDir, fmt.Sprintf("container-start-%s.stat", ctrId))
}
func (h startStatHelper) IsContainerFileExists(ctrId string) bool {
return fileutils2.Exists(h.getContainerFile(ctrId))
}
func (h startStatHelper) CreateContainerFile(ctrId string) error {
return h.createStatFile(h.getContainerFile(ctrId))
}
func (h startStatHelper) RemoveContainerFile(ctrId string) error {
return h.removeStatFile(h.getContainerFile(ctrId))
}
type sPodGuestInstance struct {
*sBaseGuestInstance
containers map[string]*sContainer
startStat *startStatHelper
}
func newPodGuestInstance(id string, man *SGuestManager) PodInstance {
p := &sPodGuestInstance{
sBaseGuestInstance: newBaseGuestInstance(id, man, computeapi.HYPERVISOR_POD),
containers: make(map[string]*sContainer),
}
p.startStat = newStartStatHelper(id, p.HomeDir())
return p
}
func (s *sPodGuestInstance) CleanGuest(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
criId := s.getCRIId()
if criId != "" {
if err := s.getCRI().RemovePod(ctx, criId); err != nil {
return nil, errors.Wrapf(err, "RemovePod with cri_id %q", criId)
}
}
return nil, DeleteHomeDir(s)
}
func (s *sPodGuestInstance) ExitCleanup(clear bool) {
}
func (s *sPodGuestInstance) CleanDirtyGuest(ctx context.Context) error {
_, err := s.CleanGuest(ctx, false)
return err
}
func (s *sPodGuestInstance) ImportServer(pendingDelete bool) {
// TODO: 参考SKVMGuestInstance,可以做更多的事,比如同步状态
s.manager.SaveServer(s.Id, s)
s.manager.RemoveCandidateServer(s)
if s.IsDaemon() || s.IsDirtyShutdown() {
ctx := context.Background()
cred := hostutils.GetComputeSession(ctx).GetToken()
if err := s.StartLocalPod(ctx, cred); err != nil {
log.Errorf("start local pod err %s", err.Error())
}
} else {
s.SyncStatus("sync status after host started")
s.getProbeManager().AddPod(s.Desc)
}
}
func (s *sPodGuestInstance) isPodDirtyShutdown() bool {
if !s.IsRunning() && s.startStat.IsPodFileExists() {
return true
}
return false
}
func (s *sPodGuestInstance) isContainerDirtyShutdown(ctrId string) bool {
if !s.IsContainerRunning(context.Background(), ctrId) && s.startStat.IsContainerFileExists(ctrId) {
return true
}
return false
}
func (s *sPodGuestInstance) IsDirtyShutdown() bool {
if !s.manager.EnableDirtyRecoveryFeature() {
return false
}
if s.isPodDirtyShutdown() {
return true
}
for _, ctr := range s.GetContainers() {
if s.isContainerDirtyShutdown(ctr.Id) {
return true
}
}
return false
}
func (s *sPodGuestInstance) SyncStatus(reason string) {
// sync pod status
var status = computeapi.VM_READY
if s.IsRunning() {
status = computeapi.VM_RUNNING
}
ctx := context.Background()
if status == computeapi.VM_READY {
// remove pod
if err := s.stopPod(ctx, 1); err != nil {
log.Warningf("stop cri pod when sync status: %s: %v", s.Id, err)
}
}
statusInput := &apis.PerformStatusInput{
Status: status,
Reason: reason,
PowerStates: GetPowerStates(s),
HostId: hostinfo.Instance().HostId,
}
if _, err := hostutils.UpdateServerStatus(ctx, s.Id, statusInput); err != nil {
log.Errorf("failed update guest status %s", err)
}
// sync container's status
for _, c := range s.containers {
status, err := s.getContainerStatus(ctx, c.Id)
if err != nil {
log.Errorf("get container %s status of pod %s", c.Id, s.Id)
continue
}
statusInput = &apis.PerformStatusInput{
Status: status,
Reason: reason,
HostId: hostinfo.Instance().HostId,
}
if _, err := hostutils.UpdateContainerStatus(ctx, c.Id, statusInput); err != nil {
log.Errorf("failed update container %s status: %s", c.Id, err)
}
}
}
func (s *sPodGuestInstance) DeployFs(ctx context.Context, userCred mcclient.TokenCredential, deployInfo *deployapi.DeployInfo) (jsonutils.JSONObject, error) {
// update port_mappings
/*podInput, err := s.getPodCreateParams()
if err != nil {
return nil, errors.Wrap(err, "getPodCreateParams")
}
if len(podInput.PortMappings) != 0 {
pms, err := s.getPortMappings(podInput.PortMappings)
if err != nil {
return nil, errors.Wrap(err, "get port mappings")
}
if err := s.setPortMappings(ctx, userCred, s.convertToPodMetadataPortMappings(pms)); err != nil {
return nil, errors.Wrap(err, "set port mappings")
}
}*/
return nil, nil
}
func (s *sPodGuestInstance) IsStopped() bool {
//TODO implement me
panic("implement me")
}
func (s *sPodGuestInstance) IsSuspend() bool {
return false
}
func (s *sPodGuestInstance) getCRI() pod.CRI {
return s.manager.GetCRI()
}
func (s *sPodGuestInstance) getProbeManager() prober.Manager {
return s.manager.GetContainerProbeManager()
}
func (s *sPodGuestInstance) getHostCPUMap() *pod.HostContainerCPUMap {
return s.manager.GetContainerCPUMap()
}
func (s *sPodGuestInstance) getPod(ctx context.Context) (*runtimeapi.PodSandbox, error) {
pods, err := s.getCRI().ListPods(ctx, pod.ListPodOptions{})
if err != nil {
return nil, errors.Wrap(err, "ListPods")
}
for _, p := range pods {
if p.Metadata.Uid == s.Id {
return p, nil
}
}
return nil, errors.Wrap(httperrors.ErrNotFound, "Not found pod from containerd")
}
func (s *sPodGuestInstance) IsRunning() bool {
pod, err := s.getPod(context.Background())
if err != nil {
return false
}
if pod.GetState() == runtimeapi.PodSandboxState_SANDBOX_READY {
return true
}
return false
}
func (s *sPodGuestInstance) IsContainerRunning(ctx context.Context, ctrId string) bool {
status, err := s.getContainerStatus(ctx, ctrId)
if err != nil {
return false
}
if sets.NewString(computeapi.CONTAINER_STATUS_RUNNING, computeapi.CONTAINER_STATUS_PROBING).Has(status) {
return true
}
return false
}
func (s *sPodGuestInstance) HandleGuestStatus(ctx context.Context, status string, body *jsonutils.JSONDict) (jsonutils.JSONObject, error) {
body.Set("status", jsonutils.NewString(status))
hostutils.TaskComplete(ctx, body)
return nil, nil
}
func (s *sPodGuestInstance) HandleGuestStart(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
hostutils.DelayTaskWithWorker(ctx, func(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
resp, err := s.startPod(ctx, userCred)
if err != nil {
return nil, errors.Wrap(err, "startPod")
}
return jsonutils.Marshal(resp), nil
}, nil, s.manager.GuestStartWorker)
return nil, nil
}
func (s *sPodGuestInstance) HandleStop(ctx context.Context, timeout int64) error {
hostutils.DelayTask(ctx, func(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
err := s.stopPod(ctx, timeout)
if err != nil {
return nil, errors.Wrap(err, "stopPod")
}
return nil, nil
}, nil)
return nil
}
func (s *sPodGuestInstance) getCreateParams() (jsonutils.JSONObject, error) {
createParamsStr, ok := s.GetDesc().Metadata[computeapi.VM_METADATA_CREATE_PARAMS]
if !ok {
return nil, errors.Errorf("not found %s in metadata", computeapi.VM_METADATA_CREATE_PARAMS)
}
return jsonutils.ParseString(createParamsStr)
}
func (s *sPodGuestInstance) getPodCreateParams() (*computeapi.PodCreateInput, error) {
createParams, err := s.getCreateParams()
if err != nil {
return nil, errors.Wrapf(err, "getCreateParams")
}
input := new(computeapi.PodCreateInput)
if err := createParams.Unmarshal(input, "pod"); err != nil {
return nil, errors.Wrapf(err, "unmarshal to pod creation input")
}
return input, nil
}
func (s *sPodGuestInstance) getPodLogDir() string {
return filepath.Join(s.HomeDir(), "logs")
}
func (s *sPodGuestInstance) getShmDir() string {
return filepath.Join(s.HomeDir(), "shm")
}
func (s *sPodGuestInstance) getContainerShmDir(containerName string) string {
return filepath.Join(s.getShmDir(), fmt.Sprintf("%s-shm", containerName))
}
func (s *sPodGuestInstance) GetDisks() []*desc.SGuestDisk {
return s.GetDesc().Disks
}
func (s *sPodGuestInstance) mountPodVolumes() error {
for ctrId, vols := range s.getContainerVolumeMounts() {
for _, vol := range vols {
drv := volume_mount.GetDriver(vol.Type)
if err := drv.Mount(s, ctrId, vol); err != nil {
return errors.Wrapf(err, "mount volume %s, ctrId %s", jsonutils.Marshal(vol), ctrId)
}
if vol.FsUser != nil || vol.FsGroup != nil {
// change mountpoint owner
if err := volume_mount.ChangeDirOwner(s, drv, ctrId, vol); err != nil {
return errors.Wrapf(err, "change dir owner")
}
}
}
}
return nil
}
func (s *sPodGuestInstance) umountPodVolumes() error {
for ctrId, vols := range s.getContainerVolumeMounts() {
for _, vol := range vols {
if err := volume_mount.GetDriver(vol.Type).Unmount(s, ctrId, vol); err != nil {
return errors.Wrapf(err, "Unmount volume %s, ctrId %s", jsonutils.Marshal(vol), ctrId)
}
}
}
return nil
}
func (s *sPodGuestInstance) GetContainers() []*hostapi.ContainerDesc {
return s.GetDesc().Containers
}
func (s *sPodGuestInstance) GetContainerById(ctrId string) *hostapi.ContainerDesc {
ctrs := s.GetContainers()
for i := range ctrs {
ctr := ctrs[i]
if ctr.Id == ctrId {
return ctr
}
}
return nil
}
func (s *sPodGuestInstance) getContainerVolumeMounts() map[string][]*hostapi.ContainerVolumeMount {
result := make(map[string][]*hostapi.ContainerVolumeMount, 0)
for _, ctr := range s.GetDesc().Containers {
mnts, ok := result[ctr.Id]
if !ok {
mnts = make([]*hostapi.ContainerVolumeMount, 0)
}
for _, vol := range ctr.Spec.VolumeMounts {
tmp := vol
mnts = append(mnts, tmp)
}
result[ctr.Id] = mnts
}
return result
}
func (s *sPodGuestInstance) getContainerVolumeMountsByDiskId(ctrId, diskId string) []*hostapi.ContainerVolumeMount {
ctrVols := s.getContainerVolumeMounts()
vols, ok := ctrVols[ctrId]
if !ok {
return nil
}
volList := make([]*hostapi.ContainerVolumeMount, 0)
for _, vol := range vols {
if vol.Disk != nil {
if vol.Disk.Id == diskId {
tmpVol := vol
volList = append(volList, tmpVol)
}
}
}
return volList
}
func (s *sPodGuestInstance) GetVolumesDir() string {
return filepath.Join(s.HomeDir(), "volumes")
}
func (s *sPodGuestInstance) GetVolumesOverlayDir() string {
return filepath.Join(s.GetVolumesDir(), "overlay")
}
func (s *sPodGuestInstance) GetDiskMountPoint(disk storageman.IDisk) string {
return s.GetDiskMountPointById(disk.GetId())
}
func (s *sPodGuestInstance) GetDiskMountPointById(diskId string) string {
return filepath.Join(s.GetVolumesDir(), diskId)
}
func (s *sPodGuestInstance) getPodPrivilegedMode(input *computeapi.PodCreateInput) bool {
for _, ctr := range input.Containers {
if ctr.Privileged {
return true
}
}
return false
}
func (s *sPodGuestInstance) getOtherPods() []*sPodGuestInstance {
man := s.manager
otherPods := make([]*sPodGuestInstance, 0)
man.Servers.Range(func(id, value any) bool {
if id == s.Id {
return true
}
ins := value.(GuestRuntimeInstance)
pod, ok := ins.(*sPodGuestInstance)
if !ok {
return true
}
otherPods = append(otherPods, pod)
return true
})
return otherPods
}
func (s *sPodGuestInstance) getCgroupParent() string {
return "/cloudpods"
}
type localPodStartTask struct {
ctx context.Context
userCred mcclient.TokenCredential
pod *sPodGuestInstance
}
func newLocalPodStartTask(ctx context.Context, userCred mcclient.TokenCredential, pod *sPodGuestInstance) *localPodStartTask {
return &localPodStartTask{
ctx: ctx,
userCred: userCred,
pod: pod,
}
}
func (t *localPodStartTask) Run() {
if t.pod.isPodDirtyShutdown() {
log.Infof("start pod locally (%s/%s)", t.pod.Id, t.pod.GetName())
if _, err := t.pod.startPod(t.ctx, t.userCred); err != nil {
log.Errorf("start pod(%s/%s) err: %s", t.pod.GetId(), t.pod.GetName(), err.Error())
}
}
for _, ctr := range t.pod.GetContainers() {
if t.pod.isContainerDirtyShutdown(ctr.Id) {
log.Infof("start container locally (%s/%s/%s/%s)", t.pod.Id, t.pod.GetName(), ctr.Id, ctr.Name)
if _, err := t.pod.StartLocalContainer(t.ctx, t.userCred, ctr.Id); err != nil {
log.Errorf("start container %s err: %s", ctr.Id, err.Error())
}
}
}
t.pod.SyncStatus("sync status after pod start locally")
}
func (t *localPodStartTask) Dump() string {
return fmt.Sprintf("pod start task %s/%s", t.pod.GetId(), t.pod.GetName())
}
func (s *sPodGuestInstance) StartLocalPod(ctx context.Context, userCred mcclient.TokenCredential) error {
s.manager.GuestStartWorker.Run(newLocalPodStartTask(ctx, userCred, s), nil, nil)
return nil
}
func (s *sPodGuestInstance) startPod(ctx context.Context, userCred mcclient.TokenCredential) (*computeapi.PodStartResponse, error) {
retries := 3
sec := 5 * time.Second
var err error
var resp *computeapi.PodStartResponse
for i := 1; i <= retries; i++ {
resp, err = s._startPod(ctx, userCred)
if err == nil {
return resp, nil
}
log.Errorf("start pod %s/%s error with %d times: %v", s.GetId(), s.GetName(), i, err)
time.Sleep(sec * time.Duration(i))
}
return resp, err
}
func (s *sPodGuestInstance) _startPod(ctx context.Context, userCred mcclient.TokenCredential) (*computeapi.PodStartResponse, error) {
podInput, err := s.getPodCreateParams()
if err != nil {
return nil, errors.Wrap(err, "getPodCreateParams")
}
if err := s.mountPodVolumes(); err != nil {
return nil, errors.Wrap(err, "mountPodVolumes")
}
if err := s.ensurePodRemoved(ctx, 0); err != nil {
log.Warningf("ensure pod removed before starting %s: %v", s.GetId(), err)
}
podCfg := &runtimeapi.PodSandboxConfig{
Metadata: &runtimeapi.PodSandboxMetadata{
Name: s.GetDesc().Name,
Uid: s.GetId(),
Namespace: s.GetDesc().TenantId,
Attempt: 1,
},
Hostname: s.GetDesc().Hostname,
LogDirectory: s.getPodLogDir(),
DnsConfig: nil,
PortMappings: nil,
Labels: nil,
Annotations: nil,
Linux: &runtimeapi.LinuxPodSandboxConfig{
CgroupParent: s.getCgroupParent(),
SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{
NamespaceOptions: nil,
SelinuxOptions: nil,
RunAsUser: nil,
RunAsGroup: nil,
ReadonlyRootfs: false,
SupplementalGroups: nil,
Privileged: s.getPodPrivilegedMode(podInput),
Seccomp: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
},
Apparmor: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
},
SeccompProfilePath: "",
},
Sysctls: nil,
},
Windows: nil,
}
// inject pod security context
podSec := podInput.SecurityContext
if podSec != nil {
/*podCfg.Linux.Sysctls = map[string]string{
"net.ipv4.ip_unprivileged_port_start": "80",
}*/
if podSec.RunAsUser != nil {
podCfg.Linux.SecurityContext.RunAsUser = &runtimeapi.Int64Value{
Value: *podSec.RunAsUser,
}
}
if podSec.RunAsGroup != nil {
podCfg.Linux.SecurityContext.RunAsGroup = &runtimeapi.Int64Value{
Value: *podSec.RunAsGroup,
}
}
}
if options.HostOptions.EnableContainerCniPortmap {
metaPms, err := s.GetPortMappings()
if err != nil {
return nil, errors.Wrap(err, "GetPortMappings")
}
if len(metaPms) != 0 {
pms := make([]*runtimeapi.PortMapping, len(metaPms))
for idx := range metaPms {
pm := metaPms[idx]
proto := runtimeapi.Protocol_TCP
switch pm.Protocol {
case computeapi.PodPortMappingProtocolTCP:
proto = runtimeapi.Protocol_TCP
case computeapi.PodPortMappingProtocolUDP:
proto = runtimeapi.Protocol_UDP
}
pms[idx] = &runtimeapi.PortMapping{
Protocol: proto,
ContainerPort: int32(pm.Port),
HostPort: int32(*pm.HostPort),
HostIp: pm.HostIp,
}
}
podCfg.PortMappings = pms
}
}
criId, err := s.getCRI().RunPod(ctx, podCfg, "")
if err != nil {
return nil, errors.Wrap(err, "cri.RunPod")
}
if err := s.setCRIInfo(ctx, userCred, criId, podCfg); err != nil {
return nil, errors.Wrap(err, "setCRIId")
}
// set pod cgroup resources
if err := s.setPodCgroupResources(criId, s.GetDesc().Mem, s.GetDesc().Cpu); err != nil {
return nil, errors.Wrapf(err, "set pod %s cgroup memMB %d, cpu %d", criId, s.GetDesc().Mem, s.GetDesc().Cpu)
}
s.getProbeManager().AddPod(s.Desc)
if err := s.startStat.CreatePodFile(); err != nil {
return nil, errors.Wrap(err, "startStat.CreatePodFile")
}
return &computeapi.PodStartResponse{
CRIId: criId,
IsRunning: false,
}, nil
}
func (s *sPodGuestInstance) setPodCgroupResources(criId string, memMB int64, cpuCnt int64) error {
if err := s.getCGUtil().SetMemoryLimitBytes(criId, memMB*1024*1024); err != nil {
return errors.Wrap(err, "set cgroup memory limit")
}
if err := s.getCGUtil().SetCPUCfs(criId, cpuCnt*s.getDefaultCPUPeriod(), s.getDefaultCPUPeriod()); err != nil {
return errors.Wrap(err, "set cgroup cfs")
}
return nil
}
func (s *sPodGuestInstance) ensurePodRemoved(ctx context.Context, timeout int64) error {
if timeout == 0 {
timeout = 5
}
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
defer cancel()
/*if err := s.getCRI().StopPod(ctx, &runtimeapi.StopPodSandboxRequest{
PodSandboxId: s.getCRIId(),
}); err != nil {
return errors.Wrapf(err, "stop cri pod: %s", s.getCRIId())
}*/
criId := s.getCRIId()
if criId != "" {
if err := s.getCRI().RemovePod(ctx, s.getCRIId()); err != nil {
return errors.Wrapf(err, "remove cri pod: %s", s.getCRIId())
}
}
p, _ := s.getPod(ctx)
if p != nil {
if err := s.getCRI().RemovePod(ctx, p.GetId()); err != nil {
return errors.Wrapf(err, "remove cri pod: %s", p.GetId())
}
}
s.getProbeManager().RemovePod(s.Desc)
if err := s.startStat.RemovePodFile(); err != nil {
return errors.Wrap(err, "startStat.RemovePodFile")
}
return nil
}
func (s *sPodGuestInstance) stopPod(ctx context.Context, timeout int64) error {
ReleaseGuestCpuset(s.manager, s)
if err := s.umountPodVolumes(); err != nil {
return errors.Wrapf(err, "umount pod volumes")
}
if timeout == 0 {
timeout = 5
}
return s.ensurePodRemoved(ctx, timeout)
}
func (s *sPodGuestInstance) LoadDesc() error {
if err := LoadDesc(s); err != nil {
return errors.Wrap(err, "LoadDesc")
}
if err := s.loadContainers(); err != nil {
return errors.Wrap(err, "loadContainers")
}
return nil
}
func (s *sPodGuestInstance) loadContainers() error {
s.containers = make(map[string]*sContainer)
ctrFile := s.getContainersFilePath()
if !fileutils2.Exists(ctrFile) {
log.Warningf("pod %s containers file %s doesn't exist", s.Id, ctrFile)
return nil
}
ctrStr, err := ioutil.ReadFile(ctrFile)
if err != nil {
return errors.Wrapf(err, "read %s", ctrFile)
}
obj, err := jsonutils.Parse(ctrStr)
if err != nil {
return errors.Wrapf(err, "jsonutils.Parse %s", ctrStr)
}
ctrs := make(map[string]*sContainer)
if err := obj.Unmarshal(ctrs); err != nil {
return errors.Wrapf(err, "unmarshal %s to container map", obj.String())
}
s.containers = ctrs
return nil
}
func (s *sPodGuestInstance) PostLoad(m *SGuestManager) error {
return LoadGuestCpuset(m, s)
}
func (s *sPodGuestInstance) SyncConfig(ctx context.Context, guestDesc *desc.SGuestDesc, fwOnly bool) (jsonutils.JSONObject, error) {
if err := SaveDesc(s, guestDesc); err != nil {
return nil, errors.Wrap(err, "SaveDesc")
}
if err := SaveLiveDesc(s, s.Desc); err != nil {
return nil, errors.Wrap(err, "SaveLiveDesc")
}
return nil, nil
}
func (s *sPodGuestInstance) getContainerMeta(id string) *sContainer {
return s.containers[id]
}
func (s *sPodGuestInstance) getContainerCRIId(ctrId string) (string, error) {
ctr := s.getContainerMeta(ctrId)
if ctr == nil {
return "", errors.Wrapf(errors.ErrNotFound, "Not found container %s", ctrId)
}
return ctr.CRIId, nil
}
func (s *sPodGuestInstance) StartLocalContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string) (jsonutils.JSONObject, error) {
ctr := s.GetContainerById(ctrId)
if ctr == nil {
return nil, errors.Wrapf(errors.ErrNotFound, "Not found container %s", ctrId)
}
input := &hostapi.ContainerCreateInput{
Name: ctr.Name,
GuestId: s.GetId(),
Spec: ctr.Spec,
}
ret, err := s.StartContainer(ctx, userCred, ctrId, input)
if err != nil {
return nil, errors.Wrap(err, "start container")
}
return ret, nil
}
func (s *sPodGuestInstance) StartContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerCreateInput) (jsonutils.JSONObject, error) {
_, hasCtr := s.containers[ctrId]
needRecreate := false
if hasCtr {
status, err := s.getContainerStatus(ctx, ctrId)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound || strings.Contains(err.Error(), "not found") {
needRecreate = true
} else {
return nil, errors.Wrap(err, "get container status")
}
} else {
if status == computeapi.CONTAINER_STATUS_EXITED {
needRecreate = true
} else if status != computeapi.CONTAINER_STATUS_CREATED {
return nil, errors.Wrapf(err, "can't start container when status is %s", status)
}
}
}
if !hasCtr || needRecreate {
log.Infof("recreate container %s before starting. hasCtr: %v, needRecreate: %v", ctrId, hasCtr, needRecreate)
// delete and recreate the container before starting
if hasCtr {
if _, err := s.DeleteContainer(ctx, userCred, ctrId); err != nil {
return nil, errors.Wrap(err, "delete container before starting")
}
}
if _, err := s.CreateContainer(ctx, userCred, ctrId, input); err != nil {
return nil, errors.Wrap(err, "recreate container before starting")
}
}
criId, err := s.getContainerCRIId(ctrId)
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
if err := s.getCRI().StartContainer(ctx, criId); err != nil {
return nil, errors.Wrap(err, "CRI.StartContainer")
}
if err := s.setContainerCgroupDevicesAllow(criId, input.Spec.CgroupDevicesAllow); err != nil {
return nil, errors.Wrap(err, "set cgroup devices allow")
}
if input.Spec.CgroupPidsMax > 0 {
if err := s.getCGUtil().SetPidsMax(criId, input.Spec.CgroupPidsMax); err != nil {
return nil, errors.Wrap(err, "set cgroup pids.max")
}
}
if err := s.doContainerStartPostLifecycle(ctx, criId, input); err != nil {
return nil, errors.Wrap(err, "do container lifecycle")
}
if err := s.startStat.CreateContainerFile(ctrId); err != nil {
return nil, errors.Wrapf(err, "create container startup stat file %s", ctrId)
}
if input.Spec.ResourcesLimit != nil {
if err := s.setContainerResourcesLimit(criId, input.Spec.ResourcesLimit); err != nil {
return nil, errors.Wrap(err, "set container resources limit")
}
}
return nil, nil
}
func (s *sPodGuestInstance) allocateCpuNumaPin() error {
if len(s.Desc.CpuNumaPin) != 0 || len(s.Desc.VcpuPin) != 0 {
return nil
}
var cpus = make([]int, 0)
var perferNumaNode int8 = -1
for i := range s.Desc.IsolatedDevices {
if s.Desc.IsolatedDevices[i].NumaNode >= 0 {
perferNumaNode = s.Desc.IsolatedDevices[i].NumaNode
break
}
}
nodeNumaCpus, err := s.manager.cpuSet.AllocCpuset(int(s.Desc.Cpu), s.Desc.Mem*1024, perferNumaNode)
if err != nil {
return err
}
for _, numaCpus := range nodeNumaCpus {
cpus = append(cpus, numaCpus.Cpuset...)
}
if !s.manager.numaAllocate {
s.Desc.VcpuPin = []desc.SCpuPin{
{
Vcpus: fmt.Sprintf("0-%d", s.Desc.Cpu-1),
Pcpus: cpuset.NewCPUSet(cpus...).String(),
},
}
}
return SaveLiveDesc(s, s.Desc)
}
func (s *sPodGuestInstance) doContainerStartPostLifecycle(ctx context.Context, criId string, input *hostapi.ContainerCreateInput) error {
ls := input.Spec.Lifecyle
if ls == nil {
return nil
}
if ls.PostStart == nil {
return nil
}
drv := lifecycle.GetDriver(ls.PostStart.Type)
if err := drv.Run(ctx, ls.PostStart, s.getCRI(), criId); err != nil {
return errors.Wrapf(err, "run %s", ls.PostStart.Type)
}
return nil
}
func (s *sPodGuestInstance) StopContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
criId, err := s.getContainerCRIId(ctrId)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound {
// cri id not found, should already stopped
return nil, nil
}
return nil, errors.Wrap(err, "get container cri id")
}
var timeout int64 = 15
if body.Contains("timeout") {
timeout, _ = body.Int("timeout")
}
if body.Contains("shm_size_mb") {
shmSizeMB, _ := body.Int("shm_size_mb")
if shmSizeMB > 64 {
name, err := body.GetString("container_name")
if err != nil {
return nil, errors.Wrapf(err, "not found name from body: %s", body)
}
if err := s.unmountDevShm(name); err != nil {
return nil, errors.Wrapf(err, "unmount shm %s", name)
}
}
}
if err := s.getCRI().StopContainer(ctx, criId, timeout); err != nil {
return nil, errors.Wrap(err, "CRI.StopContainer")
}
if err := s.startStat.RemoveContainerFile(ctrId); err != nil {
return nil, errors.Wrap(err, "startStat.RemoveContainerFile")
}
return nil, nil
}
func (s *sPodGuestInstance) getCRIId() string {
return s.GetSourceDesc().Metadata[computeapi.POD_METADATA_CRI_ID]
}
func (s *sPodGuestInstance) convertToPodMetadataPortMappings(pms []*runtimeapi.PortMapping) []*computeapi.PodMetadataPortMapping {
ret := make([]*computeapi.PodMetadataPortMapping, len(pms))
for idx := range pms {
pm := pms[idx]
var proto computeapi.PodPortMappingProtocol = computeapi.PodPortMappingProtocolTCP
if pm.Protocol == runtimeapi.Protocol_UDP {
proto = computeapi.PodPortMappingProtocolUDP
}
ret[idx] = &computeapi.PodMetadataPortMapping{
Protocol: proto,
ContainerPort: pm.ContainerPort,
HostPort: pm.HostPort,
HostIp: pm.HostIp,
}
}
return ret
}
func (s *sPodGuestInstance) setPortMappings(ctx context.Context, userCred mcclient.TokenCredential, pms []*computeapi.PodMetadataPortMapping) error {
pmStr := jsonutils.Marshal(pms).String()
s.Desc.Metadata[computeapi.POD_METADATA_PORT_MAPPINGS] = pmStr
session := auth.GetSession(ctx, userCred, options.HostOptions.Region)
if _, err := computemod.Servers.SetMetadata(session, s.GetId(), jsonutils.Marshal(map[string]string{
computeapi.POD_METADATA_PORT_MAPPINGS: pmStr,
})); err != nil {
return errors.Wrapf(err, "set cri_id of pod %s", s.GetId())
}
return SaveDesc(s, s.Desc)
}
func (s *sPodGuestInstance) setCRIInfo(ctx context.Context, userCred mcclient.TokenCredential, criId string, cfg *runtimeapi.PodSandboxConfig) error {
s.Desc.Metadata[computeapi.POD_METADATA_CRI_ID] = criId
cfgStr := jsonutils.Marshal(cfg).String()
s.Desc.Metadata[computeapi.POD_METADATA_CRI_CONFIG] = cfgStr
session := auth.GetSession(ctx, userCred, options.HostOptions.Region)
if _, err := computemod.Servers.SetMetadata(session, s.GetId(), jsonutils.Marshal(map[string]string{
computeapi.POD_METADATA_CRI_ID: criId,
computeapi.POD_METADATA_CRI_CONFIG: cfgStr,
})); err != nil {
return errors.Wrapf(err, "set cri_id of pod %s", s.GetId())
}
return SaveDesc(s, s.Desc)
}
func (s *sPodGuestInstance) setContainerCRIInfo(ctx context.Context, userCred mcclient.TokenCredential, ctrId, criId string) error {
session := auth.GetSession(ctx, userCred, options.HostOptions.Region)
if _, err := computemod.Containers.SetMetadata(session, ctrId, jsonutils.Marshal(map[string]string{
computeapi.CONTAINER_METADATA_CRI_ID: criId,
})); err != nil {
return errors.Wrapf(err, "set cri_id of container %s", ctrId)
}
return nil
}
func (s *sPodGuestInstance) getPodSandboxConfig() (*runtimeapi.PodSandboxConfig, error) {
cfgStr := s.GetSourceDesc().Metadata[computeapi.POD_METADATA_CRI_CONFIG]
obj, err := jsonutils.ParseString(cfgStr)
if err != nil {
return nil, errors.Wrapf(err, "ParseString to json object: %s", cfgStr)
}
podCfg := new(runtimeapi.PodSandboxConfig)
if err := obj.Unmarshal(podCfg); err != nil {
return nil, errors.Wrap(err, "Unmarshal to PodSandboxConfig")
}
return podCfg, nil
}
func (s *sPodGuestInstance) GetPortMappings() (computeapi.GuestPortMappings, error) {
srcDesc := s.GetSourceDesc()
nics := srcDesc.Nics
pms := make([]*computeapi.GuestPortMapping, 0)
for _, nic := range nics {
for _, pm := range nic.PortMappings {
tmpPm := pm
pms = append(pms, tmpPm)
}
}
return pms, nil
}
func (s *sPodGuestInstance) saveContainer(id string, criId string) error {
_, ok := s.containers[id]
if ok {
return errors.Errorf("container %s already exists", criId)
}
ctr := newContainer(id)
ctr.CRIId = criId
s.containers[id] = ctr
if err := s.saveContainersFile(s.containers); err != nil {
return errors.Wrap(err, "saveContainersFile")
}
return nil
}
func (s *sPodGuestInstance) saveContainersFile(containers map[string]*sContainer) error {
content := jsonutils.Marshal(containers).String()
if err := fileutils2.FilePutContents(s.getContainersFilePath(), content, false); err != nil {
return errors.Wrapf(err, "put content %s to containers file", content)
}
return nil
}
func (s *sPodGuestInstance) getContainersFilePath() string {
return path.Join(s.HomeDir(), "containers")
}
func (s *sPodGuestInstance) CreateContainer(ctx context.Context, userCred mcclient.TokenCredential, id string, input *hostapi.ContainerCreateInput) (jsonutils.JSONObject, error) {
ctrCriId, err := s.createContainer(ctx, userCred, id, input)
if err != nil {
return nil, errors.Wrap(err, "CRI.CreateContainer")
}
if err := s.setContainerCRIInfo(ctx, userCred, id, ctrCriId); err != nil {
return nil, errors.Wrap(err, "setContainerCRIInfo")
}
return nil, nil
}
func (s *sPodGuestInstance) getContainerLogPath(ctrId string) string {
return filepath.Join(fmt.Sprintf("%s.log", ctrId))
}
func (s *sPodGuestInstance) getLxcfsMounts() []*runtimeapi.Mount {
// lxcfsPath := "/var/lib/lxc/lxcfs"
lxcfsPath := options.HostOptions.LxcfsPath
const (
procCpuinfo = "/proc/cpuinfo"
procDiskstats = "/proc/diskstats"
procMeminfo = "/proc/meminfo"
procStat = "/proc/stat"
procSwaps = "/proc/swaps"
procUptime = "/proc/uptime"
)
newLxcfsMount := func(fp string) *runtimeapi.Mount {
return &runtimeapi.Mount{
ContainerPath: fp,
HostPath: fmt.Sprintf("%s%s", lxcfsPath, fp),
}
}
return []*runtimeapi.Mount{
newLxcfsMount(procUptime),
newLxcfsMount(procMeminfo),
newLxcfsMount(procStat),
newLxcfsMount(procCpuinfo),
newLxcfsMount(procSwaps),
newLxcfsMount(procDiskstats),
}
}
func (s *sPodGuestInstance) getContainerMounts(ctrId string, input *hostapi.ContainerCreateInput) ([]*runtimeapi.Mount, error) {
inputMounts := input.Spec.VolumeMounts
if len(inputMounts) == 0 {
return make([]*runtimeapi.Mount, 0), nil
}
mounts := make([]*runtimeapi.Mount, len(inputMounts))
for idx, im := range inputMounts {
mnt := &runtimeapi.Mount{
ContainerPath: im.MountPath,
Readonly: im.ReadOnly,
SelinuxRelabel: im.SelinuxRelabel,
Propagation: volume_mount.GetRuntimeVolumeMountPropagation(im.Propagation),
}
hostPath, err := volume_mount.GetDriver(im.Type).GetRuntimeMountHostPath(s, ctrId, im)
if err != nil {
return nil, errors.Wrapf(err, "get runtime host mount path of %s", jsonutils.Marshal(im))
}
mnt.HostPath = hostPath
mounts[idx] = mnt
}
return mounts, nil
}
func (s *sPodGuestInstance) getCGUtil() pod.CgroupUtil {
return pod.NewPodCgroupV1Util(s.getCgroupParent())
}
func (s *sPodGuestInstance) setContainerCgroupDevicesAllow(ctrId string, allowStrs []string) error {
return s.getCGUtil().SetDevicesAllow(ctrId, allowStrs)
}
func (s *sPodGuestInstance) SetContainerResourceLimit(ctrId string, limit *apis.ContainerResources) (jsonutils.JSONObject, error) {
criId, err := s.getContainerCRIId(ctrId)
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
return nil, s.setContainerResourcesLimit(criId, limit)
}
func (s *sPodGuestInstance) setContainerResourcesLimit(ctrId string, limit *apis.ContainerResources) error {
cgUtil := s.getCGUtil()
/*if limit.MemoryLimitMB != nil {
if err := cgUtil.SetMemoryLimitBytes(ctrId, *limit.MemoryLimitMB*1024*1024); err != nil {
return errors.Wrapf(err, "set memory limit to %d MB", *limit.MemoryLimitMB)
}
}*/
if limit.CpuCfsQuota != nil {
cpuCfsQuotaUs := *limit.CpuCfsQuota * float64(s.getDefaultCPUPeriod())
if err := cgUtil.SetCPUCfs(ctrId, int64(cpuCfsQuotaUs), s.getDefaultCPUPeriod()); err != nil {
return errors.Wrapf(err, "set cpu cfs quota to %d", int64(cpuCfsQuotaUs))
}
}
if limit.PidsMax != nil {
if err := cgUtil.SetPidsMax(ctrId, *limit.PidsMax); err != nil {
return errors.Wrapf(err, "set pids.max to %d", *limit.PidsMax)
}
}
if len(limit.DevicesAllow) != 0 {
if err := cgUtil.SetDevicesAllow(ctrId, limit.DevicesAllow); err != nil {
return errors.Wrapf(err, "set devices.allow %v", limit.DevicesAllow)
}
}
return nil
}
func (s *sPodGuestInstance) getDefaultCPUPeriod() int64 {
return 100000
}
func (s *sPodGuestInstance) createContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerCreateInput) (string, error) {
podCfg, err := s.getPodSandboxConfig()
if err != nil {
return "", errors.Wrap(err, "getPodSandboxConfig")
}
spec := input.Spec
mounts, err := s.getContainerMounts(ctrId, input)
if err != nil {
return "", errors.Wrap(err, "get container mounts")
}
if spec.SimulateCpu {
systemCpuMounts, err := s.simulateContainerSystemCpu(ctx, ctrId)
if err != nil {
return "", errors.Wrapf(err, "simulate container system cpu")
}
newMounts := systemCpuMounts
newMounts = append(newMounts, mounts...)
mounts = newMounts
}
// process shm size
if spec.ShmSizeMB > 64 {
// mount empty dir
shmPath, err := s.mountDevShm(input, spec.ShmSizeMB)
if err != nil {
return "", errors.Wrapf(err, "mount dev shm")
}
mounts = append(mounts, &runtimeapi.Mount{
ContainerPath: "/dev/shm",
HostPath: shmPath,
})
}
if err := s.allocateCpuNumaPin(); err != nil {
return "", errors.Wrap(err, "allocateCpuNumaPin")
}
var cpuSetCpus string
{
cpuSets := sets.NewString()
if len(s.Desc.CpuNumaPin) > 0 {
for _, cpuNum := range s.GetDesc().CpuNumaPin {
for _, cpuPin := range cpuNum.VcpuPin {
cpuSets.Insert(fmt.Sprintf("%d", cpuPin.Pcpu))
}
}
cpuSetCpus = strings.Join(cpuSets.List(), ",")
} else if len(s.Desc.VcpuPin) > 0 {
for _, vcpuPin := range s.Desc.VcpuPin {
cpuSets.Insert(vcpuPin.Pcpus)
}
cpuSetCpus = strings.Join(cpuSets.List(), ",")
}
}
ctrCfg := &runtimeapi.ContainerConfig{
Metadata: &runtimeapi.ContainerMetadata{
Name: input.Name,
},
Image: &runtimeapi.ImageSpec{
Image: spec.Image,
},
Linux: &runtimeapi.LinuxContainerConfig{
Resources: &runtimeapi.LinuxContainerResources{
// REF: https://docs.docker.com/config/containers/resource_constraints/#configure-the-default-cfs-scheduler
CpuPeriod: s.getDefaultCPUPeriod(),
CpuQuota: s.GetDesc().Cpu * s.getDefaultCPUPeriod(),
//CpuShares: defaultCPUPeriod,
MemoryLimitInBytes: s.GetDesc().Mem * 1024 * 1024,
OomScoreAdj: 0,
CpusetCpus: cpuSetCpus,
CpusetMems: "",
HugepageLimits: nil,
Unified: nil,
MemorySwapLimitInBytes: 0,
},
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
Capabilities: &runtimeapi.Capability{},
Privileged: spec.Privileged,
NamespaceOptions: nil,
SelinuxOptions: nil,
RunAsUser: nil,
RunAsGroup: nil,
RunAsUsername: "",
ReadonlyRootfs: false,
SupplementalGroups: nil,
NoNewPrivs: !spec.DisableNoNewPrivs,
MaskedPaths: nil,
ReadonlyPaths: nil,
Seccomp: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
},
Apparmor: &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
},
ApparmorProfile: "",
SeccompProfilePath: "",
},
},
LogPath: s.getContainerLogPath(ctrId),
Envs: make([]*runtimeapi.KeyValue, 0),
Devices: []*runtimeapi.Device{},
Mounts: mounts,
}
// inherit security context
if spec.SecurityContext != nil {
secInput := spec.SecurityContext
if secInput.RunAsUser != nil {
ctrCfg.Linux.SecurityContext.RunAsUser = &runtimeapi.Int64Value{
Value: *secInput.RunAsUser,
}
}
if secInput.RunAsGroup != nil {
ctrCfg.Linux.SecurityContext.RunAsGroup = &runtimeapi.Int64Value{
Value: *secInput.RunAsGroup,
}
}
}
if spec.EnableLxcfs {
ctrCfg.Mounts = append(ctrCfg.Mounts, s.getLxcfsMounts()...)
}
if spec.Capabilities != nil {
ctrCfg.Linux.SecurityContext.Capabilities.AddCapabilities = spec.Capabilities.Add
ctrCfg.Linux.SecurityContext.Capabilities.DropCapabilities = spec.Capabilities.Drop
}
for _, env := range spec.Envs {
ctrCfg.Envs = append(ctrCfg.Envs, &runtimeapi.KeyValue{
Key: env.Key,
Value: env.Value,
})
}
pms, err := s.GetPortMappings()
if err != nil {
return "", errors.Wrapf(err, "get pod port mappings")
}
if len(pms) != 0 {
for _, pm := range pms {
envKey := fmt.Sprintf("CLOUDPODS_%s_PORT_%d", strings.ToUpper(string(pm.Protocol)), pm.Port)
envVal := fmt.Sprintf("%d", *pm.HostPort)
ctrCfg.Envs = append(ctrCfg.Envs, &runtimeapi.KeyValue{
Key: envKey,
Value: envVal,
})
}
}
if s.GetDesc().HostAccessIp != "" {
ctrCfg.Envs = append(ctrCfg.Envs, &runtimeapi.KeyValue{
Key: "CLOUDPODS_HOST_ACCESS_IP",
Value: s.GetDesc().HostAccessIp,
})
}
if s.GetDesc().HostEIP != "" {
ctrCfg.Envs = append(ctrCfg.Envs, &runtimeapi.KeyValue{
Key: "CLOUDPODS_HOST_EIP",
Value: s.GetDesc().HostEIP,
})
}
if len(spec.Devices) != 0 {
devsByType := map[apis.ContainerDeviceType][]*hostapi.ContainerDevice{}
for i := range spec.Devices {
if devs, ok := devsByType[spec.Devices[i].Type]; ok {
devsByType[spec.Devices[i].Type] = append(devs, spec.Devices[i])
} else {
devsByType[spec.Devices[i].Type] = []*hostapi.ContainerDevice{spec.Devices[i]}
}
}
for cdType, devs := range devsByType {
ctrDevs, err := device.GetDriver(cdType).GetRuntimeDevices(input, devs)
if err != nil {
return "", errors.Wrapf(err, "GetRuntimeDevices of %s", jsonutils.Marshal(devs))
}
ctrCfg.Devices = append(ctrCfg.Devices, ctrDevs...)
}
if err := s.getIsolatedDeviceExtraConfig(spec, ctrCfg); err != nil {
return "", err
}
}
if len(spec.Command) != 0 {
ctrCfg.Command = spec.Command
}
if len(spec.Args) != 0 {
ctrCfg.Args = spec.Args
}
criId, err := s.getCRI().CreateContainer(ctx, s.getCRIId(), podCfg, ctrCfg, false)
if err != nil {
return "", errors.Wrap(err, "cri.CreateContainer")
}
if err := s.saveContainer(ctrId, criId); err != nil {
return "", errors.Wrap(err, "saveContainer")
}
return criId, nil
}
func (s *sPodGuestInstance) getIsolatedDeviceExtraConfig(spec *hostapi.ContainerSpec, ctrCfg *runtimeapi.ContainerConfig) error {
devTypes := []isolated_device.ContainerDeviceType{
isolated_device.ContainerDeviceTypeNvidiaGpu,
isolated_device.ContainerDeviceTypeNvidiaMps,
isolated_device.ContainerDeviceTypeAscendNpu,
}
for _, devType := range devTypes {
devMan, err := isolated_device.GetContainerDeviceManager(devType)
if err != nil {
return errors.Wrapf(err, "GetContainerDeviceManager by type %q", devType)
}
envs, mounts := devMan.GetContainerExtraConfigures(spec.Devices)
if len(envs) > 0 {
ctrCfg.Envs = append(ctrCfg.Envs, envs...)
}
if len(mounts) > 0 {
ctrCfg.Mounts = append(ctrCfg.Mounts, mounts...)
}
}
return nil
}
func (s *sPodGuestInstance) getContainerSystemCpusDir(ctrId string) string {
return filepath.Join(s.HomeDir(), "cpus", ctrId)
}
func (s *sPodGuestInstance) ensureContainerSystemCpuDir(cpuDir string, cpuCnt int64) error {
// create cpu dir like /var/lib/docker/cpus/$ctr_name
if err := pod.EnsureContainerSystemCpuDir(cpuDir, cpuCnt); err != nil {
return errors.Wrap(err, "ensure container system cpu dir")
}
return nil
}
func (s *sPodGuestInstance) findHostCpuPath(ctrId string, cpuIndex int) (int, error) {
return s.getHostCPUMap().Get(ctrId, cpuIndex)
}
func (s *sPodGuestInstance) simulateContainerSystemCpu(ctx context.Context, ctrId string) ([]*runtimeapi.Mount, error) {
cpuDir := s.getContainerSystemCpusDir(ctrId)
cpuCnt := s.GetDesc().Cpu
if err := s.ensureContainerSystemCpuDir(cpuDir, cpuCnt); err != nil {
return nil, err
}
cpufreqConfig := s.manager.host.GetContainerCpufreqSimulateConfig()
if cpufreqConfig != nil {
cpufreqDir := path.Join(cpuDir, "cpufreq")
out, err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", cpufreqDir).Output()
if err != nil {
return nil, errors.Wrapf(err, "mkdir %s: %s", cpufreqDir, out)
}
}
sysCpuPath := "/sys/devices/system/cpu"
ret := []*runtimeapi.Mount{
{
ContainerPath: sysCpuPath,
HostPath: cpuDir,
},
}
for i := 0; i < int(cpuCnt); i++ {
hostCpuIdx, err := s.findHostCpuPath(ctrId, i)
if err != nil {
return nil, errors.Wrapf(err, "find host cpu by container %s with index %d", ctrId, i)
}
hostCpuPath := filepath.Join(sysCpuPath, fmt.Sprintf("cpu%d", hostCpuIdx))
if cpufreqConfig != nil {
if err := s.ensureContainerSystemCpufreqHostDir(cpuDir, hostCpuPath, i, cpufreqConfig); err != nil {
return nil, errors.Wrap(err, "ensureContainerSystemCpufreqHostDir")
}
} else {
ret = append(ret, &runtimeapi.Mount{
ContainerPath: filepath.Join(sysCpuPath, fmt.Sprintf("cpu%d", i)),
HostPath: hostCpuPath,
})
}
}
pathMap := func(baseName string) *runtimeapi.Mount {
p := filepath.Join(sysCpuPath, baseName)
return &runtimeapi.Mount{
ContainerPath: p,
HostPath: p,
Readonly: true,
}
}
cpuConfigs := []string{"modalias", "power", "cpuidle", "hotplug", "isolated", "uevent"}
if cpufreqConfig == nil {
cpuConfigs = append(cpuConfigs, "cpufreq")
}
for _, baseName := range cpuConfigs {
ret = append(ret, pathMap(baseName))
}
return ret, nil
}
func (s *sPodGuestInstance) ensureContainerSystemCpufreqHostDir(cpuDir, hostCpuPath string, cpuIdx int, cpufreqConfig *jsonutils.JSONDict) error {
cpufreqPolicyDir := path.Join(cpuDir, "cpufreq", fmt.Sprintf("policy%d", cpuIdx))
out, err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", cpufreqPolicyDir).Output()
if err != nil {
return errors.Wrapf(err, "mkdir %s: %s", cpufreqPolicyDir, out)
}
cpuiDir := path.Join(cpuDir, fmt.Sprintf("cpu%d", cpuIdx))
out, err = procutils.NewRemoteCommandAsFarAsPossible("cp", "-rf", hostCpuPath, cpuiDir).Output()
if err != nil {
log.Warningf("cp %s to %s: %s %s", hostCpuPath, cpuiDir, out, err)
}
cpufreqDir := path.Join(cpuiDir, "cpufreq")
out, err = procutils.NewRemoteCommandAsFarAsPossible("rm", "-f", cpufreqDir).Output()
if err != nil {
return errors.Wrapf(err, "rm -f %s: %s", cpufreqDir, out)
}
for _, fname := range []string{
"affected_cpus",
"cpuinfo_max_freq",
"cpuinfo_min_freq",
"cpuinfo_transition_latency",
"related_cpus",
"scaling_available_governors",
"scaling_cur_freq",
"scaling_driver",
"scaling_governor",
"scaling_max_freq",
"scaling_min_freq",
"scaling_setspeed",
} {
switch fname {
case "affected_cpus", "related_cpus":
val := strconv.Itoa(cpuIdx)
cpath := path.Join(cpufreqPolicyDir, fname)
if err := fileutils2.FilePutContents(cpath, val+"\n", false); err != nil {
return errors.Wrapf(err, "failed write %s", cpath)
}
default:
val, err := cpufreqConfig.GetString(fname)
if err != nil {
log.Warningf("simulate cpufreq no %s", fname)
continue
}
cpath := path.Join(cpufreqPolicyDir, fname)
if err := fileutils2.FilePutContents(cpath, val+"\n", false); err != nil {
return errors.Wrapf(err, "failed write %s", cpath)
}
}
}
out, err = procutils.NewRemoteCommandAsFarAsPossible("ln", "-s", fmt.Sprintf("../cpufreq/policy%d", cpuIdx), cpufreqDir).Output()
if err != nil {
return errors.Wrapf(err, "ln -s ../cpufreq/policy%d %s: %s", cpuIdx, cpufreqDir, out)
}
return nil
}
func (s *sPodGuestInstance) DeleteContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string) (jsonutils.JSONObject, error) {
criId, err := s.getContainerCRIId(ctrId)
if err != nil && errors.Cause(err) != errors.ErrNotFound {
return nil, errors.Wrap(err, "getContainerCRIId")
}
if criId != "" {
if err := s.getCRI().RemoveContainer(ctx, criId); err != nil && !strings.Contains(err.Error(), "not found") {
return nil, errors.Wrap(err, "cri.RemoveContainer")
}
}
// refresh local containers file
delete(s.containers, ctrId)
if err := s.saveContainersFile(s.containers); err != nil {
return nil, errors.Wrap(err, "saveContainersFile")
}
if err := s.getHostCPUMap().Delete(ctrId); err != nil {
log.Warningf("delete container %s cpu map: %v", ctrId, err)
}
return nil, nil
}
func (s *sPodGuestInstance) getContainerStatus(ctx context.Context, ctrId string) (string, error) {
criId, err := s.getContainerCRIId(ctrId)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound {
// not found, already stopped
return computeapi.CONTAINER_STATUS_EXITED, nil
}
return "", errors.Wrapf(err, "get container cri_id by %s", ctrId)
}
resp, err := s.getCRI().ContainerStatus(ctx, criId)
if err != nil {
if strings.Contains(err.Error(), "NotFound") {
return computeapi.CONTAINER_STATUS_EXITED, nil
}
return "", errors.Wrap(err, "cri.ContainerStatus")
}
status := computeapi.CONTAINER_STATUS_UNKNOWN
switch resp.Status.State {
case runtimeapi.ContainerState_CONTAINER_CREATED:
status = computeapi.CONTAINER_STATUS_CREATED
case runtimeapi.ContainerState_CONTAINER_RUNNING:
status = computeapi.CONTAINER_STATUS_RUNNING
case runtimeapi.ContainerState_CONTAINER_EXITED:
status = computeapi.CONTAINER_STATUS_EXITED
case runtimeapi.ContainerState_CONTAINER_UNKNOWN:
status = computeapi.CONTAINER_STATUS_UNKNOWN
}
if status == computeapi.CONTAINER_STATUS_RUNNING {
ctr := s.GetContainerById(ctrId)
if ctr == nil {
return "", errors.Wrapf(httperrors.ErrNotFound, "not found container by id %s", ctrId)
}
if ctr.Spec.NeedProbe() {
status = computeapi.CONTAINER_STATUS_PROBING
}
}
return status, nil
}
func (s *sPodGuestInstance) SyncContainerStatus(ctx context.Context, userCred mcclient.TokenCredential, ctrId string) (jsonutils.JSONObject, error) {
status, err := s.getContainerStatus(ctx, ctrId)
if err != nil {
return nil, errors.Wrap(err, "get container status")
}
if status == computeapi.CONTAINER_STATUS_PROBING {
log.Infof("mark container %s to dirty after syncing status", ctrId)
s.getProbeManager().SetDirtyContainer(ctrId)
}
return jsonutils.Marshal(computeapi.ContainerSyncStatusResponse{Status: status}), nil
}
func (s *sPodGuestInstance) PullImage(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerPullImageInput) (jsonutils.JSONObject, error) {
policy := input.PullPolicy
if policy == apis.ImagePullPolicyIfNotPresent || policy == "" {
// check if image is presented
img, err := s.getCRI().ImageStatus(ctx, &runtimeapi.ImageStatusRequest{
Image: &runtimeapi.ImageSpec{
Image: input.Image,
},
})
if err != nil {
return nil, errors.Wrapf(err, "cri.ImageStatus %s", input.Image)
}
if img.Image != nil {
log.Infof("image %s already exists, skipping pulling it when policy is %s", input.Image, policy)
return jsonutils.Marshal(&runtimeapi.PullImageResponse{
ImageRef: img.Image.Id,
}), nil
}
}
return s.pullImageByCtrCmd(ctx, userCred, ctrId, input)
// return s.pullImageByCRI(ctx, userCred, ctrId, input)
}
func (s *sPodGuestInstance) pullImageByCtrCmd(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerPullImageInput) (jsonutils.JSONObject, error) {
if err := PullContainerdImage(input); err != nil {
return nil, errors.Wrap(err, "pull containerd image")
}
return jsonutils.Marshal(&runtimeapi.PullImageResponse{
ImageRef: input.Image,
}), nil
}
func (s *sPodGuestInstance) pullImageByCRI(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerPullImageInput) (jsonutils.JSONObject, error) {
/*podCfg, err := s.getPodSandboxConfig()
if err != nil {
return nil, errors.Wrap(err, "get pod sandbox config")
}*/
req := &runtimeapi.PullImageRequest{
Image: &runtimeapi.ImageSpec{
Image: input.Image,
},
// SandboxConfig: podCfg,
}
if input.Auth != nil {
authCfg := &runtimeapi.AuthConfig{
Username: input.Auth.Username,
Password: input.Auth.Password,
Auth: input.Auth.Auth,
ServerAddress: input.Auth.ServerAddress,
IdentityToken: input.Auth.IdentityToken,
RegistryToken: input.Auth.RegistryToken,
}
req.Auth = authCfg
}
resp, err := s.getCRI().PullImage(ctx, req)
if err != nil {
return nil, errors.Wrapf(err, "cri.PullImage %s", input.Image)
}
return jsonutils.Marshal(resp), nil
}
func (s *sPodGuestInstance) SaveVolumeMountToImage(ctx context.Context, userCred mcclient.TokenCredential, input *hostapi.ContainerSaveVolumeMountToImageInput, ctrId string) (jsonutils.JSONObject, error) {
vol := input.VolumeMount
drv := volume_mount.GetDriver(vol.Type)
if err := drv.Mount(s, ctrId, vol); err != nil {
return nil, errors.Wrapf(err, "mount volume %s, ctrId %s", jsonutils.Marshal(vol), ctrId)
}
defer func() {
if err := drv.Unmount(s, ctrId, vol); err != nil {
log.Warningf("unmount volume %s: %v", jsonutils.Marshal(vol), err)
}
}()
hostPath, err := drv.GetRuntimeMountHostPath(s, ctrId, vol)
if err != nil {
return nil, errors.Wrapf(err, "get runtime host mount path of %s", jsonutils.Marshal(vol))
}
// 1. tar hostPath to tgz
imgPath, err := s.tarGzDir(input, ctrId, hostPath)
if err != nil {
return nil, errors.Wrapf(err, "tar and zip directory %s", hostPath)
}
defer func() {
out, err := procutils.NewRemoteCommandAsFarAsPossible("rm", "-f", imgPath).Output()
if err != nil {
log.Warningf("rm -f %s: %s", imgPath, out)
}
}()
// 2. upload target tgz to glance
if err := s.saveTarGzToGlance(ctx, input, imgPath); err != nil {
return nil, errors.Wrapf(err, "saveTarGzToGlance: %s", imgPath)
}
return nil, nil
}
func (s *sPodGuestInstance) tarGzDir(input *hostapi.ContainerSaveVolumeMountToImageInput, ctrId string, hostPath string) (string, error) {
fp := fmt.Sprintf("volimg-%s-ctr-%s-%d.tar.gz", input.ImageId, ctrId, input.VolumeMountIndex)
outputFp := filepath.Join(s.GetVolumesDir(), fp)
cmd := fmt.Sprintf("tar -czf %s -C %s .", outputFp, hostPath)
if out, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output(); err != nil {
return "", errors.Wrapf(err, "%s: %s", cmd, out)
}
return outputFp, nil
}
func (s *sPodGuestInstance) saveTarGzToGlance(ctx context.Context, input *hostapi.ContainerSaveVolumeMountToImageInput, imgPath string) error {
f, err := os.Open(imgPath)
if err != nil {
return err
}
defer f.Close()
finfo, err := f.Stat()
if err != nil {
return err
}
size := finfo.Size()
var params = jsonutils.NewDict()
params.Set("image_id", jsonutils.NewString(input.ImageId))
if _, err := imagemod.Images.Upload(hostutils.GetImageSession(ctx), params, f, size); err != nil {
return errors.Wrap(err, "upload image")
}
return err
}
func (s *sPodGuestInstance) ExecContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *computeapi.ContainerExecInput) (*url.URL, error) {
rCli := s.getCRI().GetRuntimeClient()
criId, err := s.getContainerCRIId(ctrId)
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
req := &runtimeapi.ExecRequest{
ContainerId: criId,
Cmd: input.Command,
Tty: input.Tty,
Stdin: true,
Stdout: true,
//Stderr: true,
}
resp, err := rCli.Exec(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "exec")
}
return url.Parse(resp.Url)
}
func (s *sPodGuestInstance) mountDevShm(input *hostapi.ContainerCreateInput, mb int) (string, error) {
shmPath := s.getContainerShmDir(input.Name)
if !fileutils2.Exists(shmPath) {
out, err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", shmPath).Output()
if err != nil {
return "", errors.Wrapf(err, "mkdir -p %s: %s", shmPath, out)
}
}
if err := procutils.NewRemoteCommandAsFarAsPossible("mountpoint", shmPath).Run(); err == nil {
log.Warningf("mountpoint %s is already mounted", shmPath)
return "", nil
}
out, err := procutils.NewRemoteCommandAsFarAsPossible("mount", "-t", "tmpfs", "-o", fmt.Sprintf("size=%dM", mb), "tmpfs", shmPath).Output()
if err != nil {
return "", errors.Wrapf(err, "mount tmpfs %s: %s", shmPath, out)
}
return shmPath, nil
}
func (s *sPodGuestInstance) unmountDevShm(containerName string) error {
shmPath := s.getContainerShmDir(containerName)
if err := procutils.NewRemoteCommandAsFarAsPossible("mountpoint", shmPath).Run(); err != nil {
return nil
}
out, err := procutils.NewRemoteCommandAsFarAsPossible("umount", shmPath).Output()
if err != nil {
return errors.Wrapf(err, "mount tmpfs %s: %s", shmPath, out)
}
return nil
}
func (s *sPodGuestInstance) DoSnapshot(ctx context.Context, params *SDiskSnapshot) (jsonutils.JSONObject, error) {
if s.IsRunning() {
return nil, errors.Errorf("Pod dosen't support live snapshot")
}
if params.BackupDiskConfig == nil {
return nil, httperrors.NewMissingParameterError("missing backup_disk_config")
}
if params.BackupDiskConfig.BackupAsTar == nil {
return nil, httperrors.NewMissingParameterError("missing backup_disk_config.backup_as_tar")
}
input := params.BackupDiskConfig.BackupAsTar
if input.ContainerId == "" {
return nil, httperrors.NewMissingParameterError("missing backup_disk_config.backup_as_tar.container_id")
}
vols := s.getContainerVolumeMountsByDiskId(input.ContainerId, params.Disk.GetId())
if len(vols) == 0 {
return nil, httperrors.NewNotFoundError("not found container volume_mount by container_id %s and disk_id %s", input.ContainerId, params.Disk.GetId())
}
tmpBackRootDir, err := storageman.EnsureBackupDir()
if err != nil {
return nil, errors.Wrap(err, "EnsureBackupDir")
}
defer storageman.CleanupDirOrFile(tmpBackRootDir)
for _, vol := range vols {
drv := volume_mount.GetDriver(vol.Type)
if err := drv.Mount(s, input.ContainerId, vol); err != nil {
return nil, errors.Wrapf(err, "mount %s to %s", input.ContainerId, jsonutils.Marshal(vol))
}
mntPath, err := drv.GetRuntimeMountHostPath(s, input.ContainerId, vol)
if err != nil {
return nil, errors.Wrapf(err, "GetRuntimeMountHostPath containerId: %s, vol: %s", input.ContainerId, jsonutils.Marshal(vol))
}
isMntPathFile := false
targetBindMntPath := tmpBackRootDir
if vol.Disk.SubDirectory != "" {
// mkdir tmpBackRootDir/subdirectory
targetBindMntPath = filepath.Join(tmpBackRootDir, vol.Disk.SubDirectory)
}
if vol.Disk.StorageSizeFile != "" {
targetBindMntPath = filepath.Join(tmpBackRootDir, vol.Disk.StorageSizeFile)
isMntPathFile = true
}
if isMntPathFile {
if out, err := procutils.NewRemoteCommandAsFarAsPossible("touch", targetBindMntPath).Output(); err != nil {
return nil, errors.Wrapf(err, "touch %s: %s", targetBindMntPath, out)
}
} else {
if out, err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", targetBindMntPath).Output(); err != nil {
return nil, errors.Wrapf(err, "mkdir -p %s: %s", targetBindMntPath, out)
}
}
// do bind mount
out, err := procutils.NewRemoteCommandAsFarAsPossible("mount", "--bind", mntPath, targetBindMntPath).Output()
if err != nil {
return nil, errors.Wrapf(err, "bind mount %s to %s: %s", mntPath, targetBindMntPath, out)
}
}
deferUmount := func() error {
for _, vol := range vols {
// unbind mount
// umount
targetBindMntPath := filepath.Join(tmpBackRootDir, vol.Disk.SubDirectory)
if vol.Disk.StorageSizeFile != "" {
targetBindMntPath = filepath.Join(tmpBackRootDir, vol.Disk.StorageSizeFile)
}
out, err := procutils.NewRemoteCommandAsFarAsPossible("umount", targetBindMntPath).Output()
if err != nil {
return errors.Wrapf(err, "umount bind point %s: %s", targetBindMntPath, out)
}
}
for _, vol := range vols {
drv := volume_mount.GetDriver(vol.Type)
if err := drv.Unmount(s, input.ContainerId, vol); err != nil {
return errors.Wrapf(err, "unmount %s to %s", input.ContainerId, jsonutils.Marshal(vol))
}
}
return nil
}
defer func() {
if err := deferUmount(); err != nil {
log.Errorf("deferUmount after snapshot error: %s", err)
} else {
log.Infof("defer umount success")
}
}()
snapshotPath, err := s.createSnapshot(params, tmpBackRootDir)
if err != nil {
return nil, errors.Wrap(err, "create snapshot")
}
res := jsonutils.NewDict()
res.Set("location", jsonutils.NewString(snapshotPath))
return res, nil
}
func (s *sPodGuestInstance) createSnapshot(params *SDiskSnapshot, hostPath string) (string, error) {
d := params.Disk
snapshotDir := d.GetSnapshotDir()
log.Infof("snapshotDir of LocalDisk %s: %s", d.GetId(), snapshotDir)
if !fileutils2.Exists(snapshotDir) {
output, err := procutils.NewCommand("mkdir", "-p", snapshotDir).Output()
if err != nil {
log.Errorf("mkdir %s failed: %s", snapshotDir, output)
return "", errors.Wrapf(err, "mkdir %s failed: %s", snapshotDir, output)
}
}
snapshotPath := s.getSnapshotPath(d, params.SnapshotId)
// tar hostPath to snapshotPath
input := params.BackupDiskConfig.BackupAsTar
if err := s.tarHostDir(hostPath, snapshotPath, input.IncludeFiles, input.ExcludeFiles, input.IgnoreNotExistFile); err != nil {
return "", errors.Wrapf(err, "tar host dir %s to %s", hostPath, snapshotPath)
}
return snapshotPath, nil
}
func (s *sPodGuestInstance) tarHostDir(srcDir, targetPath string,
includeFiles, excludeFiles []string,
ignoreNotExistFile bool) error {
baseCmd := "tar"
filterNotExistFiles := func(files []string) []string {
result := []string{}
for i := range files {
if fileutils2.Exists(filepath.Join(srcDir, files[i])) {
result = append(result, files[i])
}
}
return result
}
if ignoreNotExistFile {
includeFiles = filterNotExistFiles(includeFiles)
excludeFiles = filterNotExistFiles(excludeFiles)
}
for _, exclude := range excludeFiles {
baseCmd = fmt.Sprintf("%s --exclude='%s'", baseCmd, exclude)
}
includeStr := "."
if len(includeFiles) > 0 {
includeStr = strings.Join(includeFiles, " ")
}
cmd := fmt.Sprintf("%s -cf %s -C %s %s", baseCmd, targetPath, srcDir, includeStr)
log.Infof("tar cmd: %s", cmd)
if out, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output(); err != nil {
return errors.Wrapf(err, "%s: %s", cmd, out)
}
return nil
}
func (s *sPodGuestInstance) getSnapshotPath(d storageman.IDisk, snapshotId string) string {
snapshotDir := d.GetSnapshotDir()
snapshotPath := path.Join(snapshotDir, fmt.Sprintf("%s.tar", snapshotId))
return snapshotPath
}
func (s *sPodGuestInstance) DeleteSnapshot(ctx context.Context, params *SDeleteDiskSnapshot) (jsonutils.JSONObject, error) {
snapshotPath := s.getSnapshotPath(params.Disk, params.DeleteSnapshot)
out, err := procutils.NewCommand("rm", "-f", snapshotPath).Output()
if err != nil {
return nil, errors.Wrapf(err, "rm -f %s: %s", snapshotPath, out)
}
res := jsonutils.NewDict()
res.Set("deleted", jsonutils.JSONTrue)
return res, nil
}
func (s *sPodGuestInstance) ContainerExecSync(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *computeapi.ContainerExecSyncInput) (jsonutils.JSONObject, error) {
ctrCriId, err := s.getContainerCRIId(ctrId)
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
cli := s.getCRI().GetRuntimeClient()
resp, err := cli.ExecSync(ctx, &runtimeapi.ExecSyncRequest{
ContainerId: ctrCriId,
Cmd: input.Command,
Timeout: input.Timeout,
})
if err != nil {
return nil, errors.Wrapf(err, "exec sync %#v to %s", input.Command, ctrCriId)
}
return jsonutils.Marshal(&computeapi.ContainerExecSyncResponse{
Stdout: string(resp.Stdout),
Stderr: string(resp.Stderr),
ExitCode: resp.ExitCode,
}), nil
}
func (s *sPodGuestInstance) ReadLogs(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *computeapi.PodLogOptions, stdout, stderr io.Writer) error {
// Do a zero-byte write to stdout before handing off to the container runtime.
// This ensures at least one Write call is made to the writer when copying starts,
// even if we then block waiting for log output from the container.
if _, err := stdout.Write([]byte{}); err != nil {
return err
}
ctrCriId, err := s.getContainerCRIId(ctrId)
if err != nil {
return errors.Wrapf(err, "get container cri id %s", ctrId)
}
resp, err := s.getCRI().ContainerStatus(ctx, ctrCriId)
if err != nil {
return errors.Wrapf(err, "get container status %s", ctrCriId)
}
logPath := resp.GetStatus().GetLogPath()
opts := logs.NewLogOptions(input, time.Now())
return logs.ReadLogs(ctx, logPath, ctrCriId, opts, s.getCRI().GetRuntimeClient(), stdout, stderr)
}
func (s *sPodGuestInstance) CommitContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrId string, input *hostapi.ContainerCommitInput) (jsonutils.JSONObject, error) {
criId, err := s.getContainerCRIId(ctrId)
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
// 1. commit
tool := NewContainerdNerdctl()
imgRepo, err := tool.Commit(criId, &nerdctl.CommitOptions{Repository: input.Repository})
if err != nil {
return nil, errors.Wrapf(err, "commit container %s image", ctrId)
}
log.Infof("container %s was commited to %s", ctrId, imgRepo)
// 2. push to repository
if err := PushContainerdImage(&hostapi.ContainerPushImageInput{
Image: imgRepo,
Auth: input.Auth,
}); err != nil {
return nil, errors.Wrapf(err, "push container %s image", ctrId)
}
log.Infof("container %s was pushed to %s", ctrId, imgRepo)
return jsonutils.Marshal(map[string]interface{}{
"image_repository": imgRepo,
}), nil
}