Files
cloudpods/pkg/compute/models/containers.go
T

1366 lines
50 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 models
import (
"context"
"encoding/base64"
"fmt"
"path/filepath"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis"
api "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
identityapi "yunion.io/x/onecloud/pkg/apis/identity"
imageapi "yunion.io/x/onecloud/pkg/apis/image"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/compute/utils"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
identitymod "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
kubemod "yunion.io/x/onecloud/pkg/mcclient/modules/k8s"
)
var containerManager *SContainerManager
func GetContainerManager() *SContainerManager {
if containerManager == nil {
containerManager = &SContainerManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SContainer{},
"containers_tbl",
"container",
"containers"),
}
containerManager.SetVirtualObject(containerManager)
}
return containerManager
}
func init() {
GetContainerManager()
}
type SContainerManager struct {
db.SVirtualResourceBaseManager
}
type SContainer struct {
db.SVirtualResourceBase
// GuestId is also the pod id
GuestId string `width:"36" charset:"ascii" create:"required" list:"user" index:"true"`
// Spec stores all container running options
Spec *api.ContainerSpec `length:"long" create:"required" list:"user" update:"user"`
// 启动时间
StartedAt time.Time `nullable:"true" created_at:"false" index:"true" get:"user" list:"user" json:"started_at"`
// 上次退出时间
LastFinishedAt time.Time `nullable:"true" created_at:"false" index:"true" get:"user" list:"user" json:"last_finished_at"`
// 重启次数
RestartCount int `nullable:"true" list:"user"`
}
func (m *SContainerManager) CreateOnPod(
ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
pod *SGuest, data *api.PodContainerCreateInput) (*SContainer, error) {
input := &api.ContainerCreateInput{
GuestId: pod.GetId(),
Spec: data.ContainerSpec,
SkipTask: true,
}
input.Name = data.Name
obj, err := db.DoCreate(m, ctx, userCred, nil, jsonutils.Marshal(input), ownerId)
if err != nil {
return nil, errors.Wrap(err, "create container")
}
return obj.(*SContainer), nil
}
func (m *SContainerManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
guestId, _ := data.GetString("guest_id")
return jsonutils.Marshal(map[string]string{"guest_id": guestId})
}
func (m *SContainerManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
guestId, _ := values.GetString("guest_id")
if len(guestId) > 0 {
q = q.Equals("guest_id", guestId)
}
return q
}
func (m *SContainerManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ContainerListInput) (*sqlchemy.SQuery, error) {
q, err := m.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VirtualResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter")
}
if query.GuestId != "" {
gst, err := GuestManager.FetchByIdOrName(ctx, userCred, query.GuestId)
if err != nil {
return nil, errors.Wrapf(err, "fetch guest by %s", query.GuestId)
}
q = q.Equals("guest_id", gst.GetId())
}
if query.HostId != "" {
host, _ := HostManager.FetchByIdOrName(ctx, nil, query.HostId)
if host == nil {
return nil, httperrors.NewResourceNotFoundError("host %s not found", query.HostId)
}
gst := GuestManager.Query().SubQuery()
q = q.Join(gst, sqlchemy.Equals(q.Field("guest_id"), gst.Field("id")))
q = q.Filter(sqlchemy.Equals(gst.Field("host_id"), host.GetId()))
}
return q, nil
}
func (m *SContainerManager) GetContainersByPod(guestId string) ([]SContainer, error) {
q := m.Query().Equals("guest_id", guestId)
ctrs := make([]SContainer, 0)
if err := db.FetchModelObjects(m, q, &ctrs); err != nil {
return nil, errors.Wrap(err, "db.FetchModelObjects")
}
return ctrs, nil
}
func (m *SContainerManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, _ jsonutils.JSONObject, input *api.ContainerCreateInput) (*api.ContainerCreateInput, error) {
if input.GuestId == "" {
return nil, httperrors.NewNotEmptyError("guest_id is required")
}
obj, err := GuestManager.FetchByIdOrName(ctx, userCred, input.GuestId)
if err != nil {
return nil, errors.Wrapf(err, "fetch guest by %s", input.GuestId)
}
pod := obj.(*SGuest)
input.GuestId = pod.GetId()
if err := m.ValidateSpec(ctx, userCred, &input.Spec, pod, nil); err != nil {
return nil, errors.Wrap(err, "validate spec")
}
return input, nil
}
func (m *SContainerManager) ValidateSpec(ctx context.Context, userCred mcclient.TokenCredential, spec *api.ContainerSpec, pod *SGuest, ctr *SContainer) error {
if spec.ImagePullPolicy == "" {
spec.ImagePullPolicy = apis.ImagePullPolicyIfNotPresent
}
if !sets.NewString(apis.ImagePullPolicyAlways, apis.ImagePullPolicyIfNotPresent).Has(string(spec.ImagePullPolicy)) {
return httperrors.NewInputParameterError("invalid image_pull_policy %s", spec.ImagePullPolicy)
}
if spec.ImageCredentialId != "" {
if _, err := m.GetImageCredential(ctx, userCred, spec.ImageCredentialId); err != nil {
return errors.Wrapf(err, "get image credential by id: %s", spec.ImageCredentialId)
}
}
if pod != nil {
if err := m.ValidateSpecRootFs(ctx, userCred, pod, spec, ctr); err != nil {
return errors.Wrap(err, "ValidateSpecRootFs")
}
if err := m.ValidateSpecVolumeMounts(ctx, userCred, pod, spec, ctr); err != nil {
return errors.Wrap(err, "ValidateSpecVolumeMounts")
}
for idx, dev := range spec.Devices {
newDev, err := m.ValidateSpecDevice(ctx, userCred, pod, dev)
if err != nil {
return errors.Wrapf(err, "validate device %s", jsonutils.Marshal(dev))
}
spec.Devices[idx] = newDev
}
}
if err := m.ValidateSpecLifecycle(ctx, userCred, spec); err != nil {
return errors.Wrap(err, "validate lifecycle")
}
if spec.ShmSizeMB != 0 && spec.ShmSizeMB < 64 {
return httperrors.NewInputParameterError("/dev/shm size is small than 64MB")
}
if err := m.ValidateSpecProbe(ctx, userCred, spec); err != nil {
return errors.Wrap(err, "validate probe configuration")
}
if ctr != nil {
// only detect loop when update container
ctrs, err := m.GetContainersByPod(pod.GetId())
if err != nil {
return errors.Wrap(err, "get containers by pod")
}
for idx, container := range ctrs {
if container.GetId() == ctr.GetId() {
ctrs[idx].Spec = spec
}
}
err = utils.TopologicalSortContainers(
ctrs,
func(ctr SContainer) string { return ctr.Name },
func(ctr SContainer) []string { return ctr.Spec.DependsOn },
)
if err != nil {
return errors.Wrap(err, "validate topological sort")
}
}
return nil
}
func (m *SContainerManager) ValidateSpecLifecycle(ctx context.Context, cred mcclient.TokenCredential, spec *api.ContainerSpec) error {
if spec.Lifecyle == nil {
return nil
}
if err := m.ValidateSpecLifecyclePostStart(ctx, cred, spec.Lifecyle.PostStart); err != nil {
return errors.Wrap(err, "validate post start")
}
return nil
}
func (m *SContainerManager) ValidateSpecLifecyclePostStart(ctx context.Context, userCred mcclient.TokenCredential, input *apis.ContainerLifecyleHandler) error {
drv, err := GetContainerLifecyleDriverWithError(input.Type)
if err != nil {
return httperrors.NewInputParameterError("get lifecycle driver: %v", err)
}
return drv.ValidateCreateData(ctx, userCred, input)
}
func (m *SContainerManager) ValidateSpecDevice(ctx context.Context, userCred mcclient.TokenCredential, pod *SGuest, dev *api.ContainerDevice) (*api.ContainerDevice, error) {
drv, err := GetContainerDeviceDriverWithError(dev.Type)
if err != nil {
return nil, httperrors.NewInputParameterError("get device driver: %v", err)
}
return drv.ValidateCreateData(ctx, userCred, pod, dev)
}
func (m *SContainerManager) ValidateSpecRootFs(ctx context.Context, userCred mcclient.TokenCredential, pod *SGuest, spec *api.ContainerSpec, ctr *SContainer) error {
if spec.RootFs == nil {
return nil
}
rootFs := spec.RootFs
drv, err := GetContainerRootFsDriverWithError(rootFs.Type)
if err != nil {
return errors.Wrapf(err, "get container volume mount driver %q", rootFs.Type)
}
if err := drv.ValidateRootFsCreateData(ctx, userCred, pod, rootFs); err != nil {
return errors.Wrapf(err, "validate %s create data", drv.GetType())
}
return nil
}
func (m *SContainerManager) ValidateSpecVolumeMounts(ctx context.Context, userCred mcclient.TokenCredential, pod *SGuest, spec *api.ContainerSpec, ctr *SContainer) error {
relation, err := m.GetVolumeMountRelations(pod, spec)
if err != nil {
return errors.Wrap(err, "GetVolumeMountRelations")
}
curCtrs, _ := m.GetContainersByPod(pod.GetId())
volUniqNames := sets.NewString()
for idx := range curCtrs {
if ctr != nil && ctr.GetId() == curCtrs[idx].GetId() {
continue
}
for _, vol := range curCtrs[idx].Spec.VolumeMounts {
if vol.UniqueName != "" {
volUniqNames.Insert(vol.UniqueName)
}
}
}
for idx, vm := range spec.VolumeMounts {
if vm.UniqueName != "" {
if volUniqNames.Has(vm.UniqueName) {
return httperrors.NewDuplicateNameError("volume_mount unique_name %s", fmt.Sprintf("%s: %s, index: %d", vm.UniqueName, jsonutils.Marshal(vm), idx))
} else {
volUniqNames.Insert(vm.UniqueName)
}
}
newVm, err := m.ValidateSpecVolumeMount(ctx, userCred, pod, vm)
if err != nil {
return errors.Wrapf(err, "validate volume mount %s", jsonutils.Marshal(vm))
}
spec.VolumeMounts[idx] = newVm
}
if _, err := m.ConvertVolumeMountRelationToSpec(ctx, userCred, relation); err != nil {
return errors.Wrap(err, "ConvertVolumeMountRelationToSpec")
}
return nil
}
func (m *SContainerManager) ValidateSpecVolumeMount(ctx context.Context, userCred mcclient.TokenCredential, pod *SGuest, vm *apis.ContainerVolumeMount) (*apis.ContainerVolumeMount, error) {
if vm.Type == "" {
return nil, httperrors.NewNotEmptyError("type is required")
}
if vm.MountPath == "" {
return nil, httperrors.NewNotEmptyError("mount_path is required")
}
drv, err := GetContainerVolumeMountDriverWithError(vm.Type)
if err != nil {
return nil, errors.Wrapf(err, "get container volume mount driver %s", vm.Type)
}
vm, err = drv.ValidateCreateData(ctx, userCred, pod, vm)
if err != nil {
return nil, errors.Wrapf(err, "validate %s create data", drv.GetType())
}
return vm, nil
}
/*func (m *SContainerManager) GetContainerIndex(guestId string) (int, error) {
cnt, err := m.Query("guest_id").Equals("guest_id", guestId).CountWithError()
if err != nil {
return -1, errors.Wrapf(err, "get container numbers of pod %s", guestId)
}
return cnt, nil
}
func (c *SContainer) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
input := new(api.ContainerCreateInput)
if err := data.Unmarshal(input); err != nil {
return errors.Wrap(err, "unmarshal to ContainerCreateInput")
}
if input.Spec.ImagePullPolicy == "" {
c.Spec.ImagePullPolicy = apis.ImagePullPolicyIfNotPresent
}
return nil
}*/
func (m *SContainerManager) ValidateSpecProbe(ctx context.Context, userCred mcclient.TokenCredential, spec *api.ContainerSpec) error {
//if err := m.validateSpecProbe(ctx, userCred, spec.LivenessProbe); err != nil {
// return errors.Wrap(err, "validate liveness probe")
//}
if err := m.validateSpecProbe(ctx, userCred, spec.StartupProbe); err != nil {
return errors.Wrap(err, "validate startup probe")
}
return nil
}
func (m *SContainerManager) validateSpecProbe(ctx context.Context, userCred mcclient.TokenCredential, probe *apis.ContainerProbe) error {
if probe == nil {
return nil
}
if err := m.validateSpecProbeHandler(probe.ContainerProbeHandler); err != nil {
return errors.Wrap(err, "validate container probe handler")
}
for key, val := range map[string]int32{
//"initial_delay_seconds": probe.InitialDelaySeconds,
"timeout_seconds": probe.TimeoutSeconds,
"period_seconds": probe.PeriodSeconds,
"success_threshold": probe.SuccessThreshold,
"failure_threshold": probe.FailureThreshold,
} {
if val < 0 {
return httperrors.NewInputParameterError("%s is negative", key)
}
}
//if probe.InitialDelaySeconds == 0 {
// probe.InitialDelaySeconds = 5
//}
if probe.TimeoutSeconds == 0 {
probe.TimeoutSeconds = 3
}
if probe.PeriodSeconds == 0 {
probe.PeriodSeconds = 10
}
if probe.SuccessThreshold == 0 {
probe.SuccessThreshold = 1
}
if probe.FailureThreshold == 0 {
probe.FailureThreshold = 3
}
return nil
}
func (m *SContainerManager) validateSpecProbeHandler(probe apis.ContainerProbeHandler) error {
isAllNil := true
if probe.Exec != nil {
isAllNil = false
if len(probe.Exec.Command) == 0 {
return httperrors.NewInputParameterError("exec command is required")
}
}
if probe.TCPSocket != nil {
isAllNil = false
port := probe.TCPSocket.Port
if port < 1 || port > 65535 {
return httperrors.NewInputParameterError("invalid tcp socket port: %d, must between [1,65535]", port)
}
}
if probe.HTTPGet != nil {
isAllNil = false
port := probe.HTTPGet.Port
if port < 1 || port > 65535 {
return httperrors.NewInputParameterError("invalid http port: %d, must between [1,65535]", port)
}
}
if isAllNil {
return httperrors.NewInputParameterError("one of [exec, http_get, tcp_socket] is required")
}
return nil
}
func (m *SContainerManager) startBatchTask(ctx context.Context, userCred mcclient.TokenCredential, taskName string, ctrs []SContainer, taskData *jsonutils.JSONDict, parentTaskId string) error {
ctrPtrs := make([]db.IStandaloneModel, len(ctrs))
for i := range ctrs {
ctrPtrs[i] = &ctrs[i]
}
task, err := taskman.TaskManager.NewParallelTask(ctx, taskName, ctrPtrs, userCred, taskData, parentTaskId, "")
if err != nil {
return errors.Wrapf(err, "NewParallelTask %s", taskName)
}
return task.ScheduleRun(nil)
}
func (m *SContainerManager) StartBatchStartTask(ctx context.Context, userCred mcclient.TokenCredential, ctrs []SContainer, parentTaskId string) error {
return m.startBatchTask(ctx, userCred, "ContainerBatchStartTask", ctrs, nil, parentTaskId)
}
func (m *SContainerManager) StartBatchStopTask(ctx context.Context, userCred mcclient.TokenCredential, ctrs []SContainer, timeout int, force bool, parentTaskId string) error {
params := make([]api.ContainerStopInput, len(ctrs))
for i := range ctrs {
params[i] = api.ContainerStopInput{
Timeout: timeout,
Force: force,
}
}
taskParams := jsonutils.NewDict()
taskParams.Add(jsonutils.Marshal(params), "params")
return m.startBatchTask(ctx, userCred, "ContainerBatchStopTask", ctrs, taskParams, parentTaskId)
}
func (m *SContainerManager) StartBatchRestartTask(ctx context.Context, userCred mcclient.TokenCredential, ctrs []SContainer, timeout int, force bool, parentTaskId string) error {
params := make([]api.ContainerRestartInput, len(ctrs))
for i := range ctrs {
params[i] = api.ContainerRestartInput{
Timeout: timeout,
Force: force,
}
}
taskParams := jsonutils.NewDict()
taskParams.Add(jsonutils.Marshal(params), "params")
return m.startBatchTask(ctx, userCred, "ContainerBatchRestartTask", ctrs, taskParams, parentTaskId)
}
func (c *SContainer) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
c.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
if !jsonutils.QueryBoolean(data, "skip_task", false) {
if err := c.StartCreateTask(ctx, userCred, "", data.(*jsonutils.JSONDict)); err != nil {
log.Errorf("StartCreateTask error: %v", err)
}
}
}
func (c *SContainer) StartCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, params *jsonutils.JSONDict) error {
task, err := taskman.TaskManager.NewTask(ctx, "ContainerCreateTask", c, userCred, params, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ContainerUpdateInput) (*api.ContainerUpdateInput, error) {
if !api.ContainerExitedStatus.Has(c.GetStatus()) {
return nil, httperrors.NewInvalidStatusError("current status %s is not in %v", c.GetStatus(), api.ContainerExitedStatus.List())
}
baseInput, err := c.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.VirtualResourceBaseUpdateInput)
if err != nil {
return input, errors.Wrap(err, "SVirtualResourceBase.ValidateUpdateData")
}
input.VirtualResourceBaseUpdateInput = baseInput
if err := GetContainerManager().ValidateSpec(ctx, userCred, &input.Spec, c.GetPod(), c); err != nil {
return nil, errors.Wrap(err, "validate spec")
}
return input, nil
}
func (c *SContainer) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
c.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
if err := c.GetPod().StartSyncTaskWithoutSyncstatus(ctx, userCred, false, ""); err != nil {
log.Errorf("container %s StartSyncTaskWithoutSyncstatus error: %v", c.GetName(), err)
}
}
func (c *SContainer) GetPod() *SGuest {
return GuestManager.FetchGuestById(c.GuestId)
}
func (c *SContainer) GetVolumeMounts() []*apis.ContainerVolumeMount {
return c.Spec.VolumeMounts
}
type ContainerRootFsRelation struct {
RootFs *apis.ContainerRootfs
pod *SGuest
}
func (rr *ContainerRootFsRelation) ToHostRootFs() (*hostapi.ContainerRootfs, error) {
drv, err := GetContainerRootFsDriverWithError(rr.RootFs.Type)
if err != nil {
return nil, errors.Wrapf(err, "get container root fs driver %q", rr.RootFs.Type)
}
rootFs, err := drv.ToHostRootFs(rr.RootFs)
if err != nil {
return nil, errors.Wrapf(err, "to host root fs")
}
return rootFs, nil
}
type ContainerVolumeMountRelation struct {
VolumeMount *apis.ContainerVolumeMount
pod *SGuest
}
func (vm *ContainerVolumeMountRelation) toHostDiskMount(disk *apis.ContainerVolumeMountDisk) (*hostapi.ContainerVolumeMountDisk, error) {
diskObj := DiskManager.FetchDiskById(disk.Id)
if diskObj == nil {
return nil, errors.Errorf("fetch disk by id %s", disk.Id)
}
ret := &hostapi.ContainerVolumeMountDisk{
Index: disk.Index,
Id: disk.Id,
TemplateId: diskObj.TemplateId,
SubDirectory: disk.SubDirectory,
StorageSizeFile: disk.StorageSizeFile,
Overlay: disk.Overlay,
CaseInsensitivePaths: disk.CaseInsensitivePaths,
PostOverlay: disk.PostOverlay,
ResUid: disk.ResUid,
ResGid: disk.ResGid,
}
return ret, nil
}
func (vm *ContainerVolumeMountRelation) toCephFSMount(fs *apis.ContainerVolumeMountCephFS) (*hostapi.ContainerVolumeMountCephFS, error) {
fsObj, err := FileSystemManager.FetchById(fs.Id)
if err != nil {
return nil, errors.Errorf("fetch cephfs by id %s", fs.Id)
}
filesystem := fsObj.(*SFileSystem)
ret := &hostapi.ContainerVolumeMountCephFS{
Id: filesystem.Id,
Path: filesystem.ExternalId,
}
account := filesystem.GetCloudaccount()
if gotypes.IsNil(account) {
return nil, fmt.Errorf("invalid cephfs %s", filesystem.Name)
}
ret.Secret, err = account.GetOptionPassword()
if err != nil {
return nil, err
}
ret.Name, _ = account.Options.GetString("name")
if len(ret.Name) == 0 {
ret.Name = "admin"
}
ret.MonHost, _ = account.Options.GetString("mon_host")
return ret, nil
}
func (vm *ContainerVolumeMountRelation) ToHostMount(ctx context.Context, userCred mcclient.TokenCredential) (*hostapi.ContainerVolumeMount, error) {
ret := &hostapi.ContainerVolumeMount{
UniqueName: vm.VolumeMount.UniqueName,
Type: vm.VolumeMount.Type,
Disk: nil,
CephFS: nil,
Text: vm.VolumeMount.Text,
HostPath: vm.VolumeMount.HostPath,
ReadOnly: vm.VolumeMount.ReadOnly,
MountPath: vm.VolumeMount.MountPath,
SelinuxRelabel: vm.VolumeMount.SelinuxRelabel,
Propagation: vm.VolumeMount.Propagation,
FsUser: vm.VolumeMount.FsUser,
FsGroup: vm.VolumeMount.FsGroup,
}
if vm.VolumeMount.Disk != nil {
disk, err := vm.toHostDiskMount(vm.VolumeMount.Disk)
if err != nil {
return nil, errors.Wrap(err, "toHostDiskMount")
}
ret.Disk = disk
}
if vm.VolumeMount.CephFS != nil {
fs, err := vm.toCephFSMount(vm.VolumeMount.CephFS)
if err != nil {
return nil, errors.Wrapf(err, "getCephFSSecret")
}
ret.CephFS = fs
}
return ret, nil
}
func (m *SContainerManager) GetRootFsRelation(pod *SGuest, spec *api.ContainerSpec) (*ContainerRootFsRelation, error) {
return &ContainerRootFsRelation{
RootFs: spec.RootFs,
pod: pod,
}, nil
}
func (m *SContainerManager) GetVolumeMountRelations(pod *SGuest, spec *api.ContainerSpec) ([]*ContainerVolumeMountRelation, error) {
relation := make([]*ContainerVolumeMountRelation, len(spec.VolumeMounts))
for idx, vm := range spec.VolumeMounts {
tmpVm := vm
relation[idx] = &ContainerVolumeMountRelation{
VolumeMount: tmpVm,
pod: pod,
}
}
return relation, nil
}
func (c *SContainer) GetRootFsRelation() (*ContainerRootFsRelation, error) {
return GetContainerManager().GetRootFsRelation(c.GetPod(), c.Spec)
}
func (c *SContainer) GetVolumeMountRelations() ([]*ContainerVolumeMountRelation, error) {
return GetContainerManager().GetVolumeMountRelations(c.GetPod(), c.Spec)
}
func (c *SContainer) PerformStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !sets.NewString(api.CONTAINER_STATUS_EXITED, api.CONTAINER_STATUS_START_FAILED).Has(c.Status) {
return nil, httperrors.NewInvalidStatusError("Can't start container in status %s", c.Status)
}
return nil, c.StartStartTask(ctx, userCred, "")
}
func (c *SContainer) StartStartTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_STARTING, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerStartTask", c, userCred, nil, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *api.ContainerStopInput) (jsonutils.JSONObject, error) {
if !data.Force {
if !sets.NewString(
api.CONTAINER_STATUS_RUNNING,
api.CONTAINER_STATUS_PROBING,
api.CONTAINER_STATUS_PROBE_FAILED,
api.CONTAINER_STATUS_STOP_FAILED).Has(c.Status) {
return nil, httperrors.NewInvalidStatusError("Can't stop container in status %s", c.Status)
}
}
return nil, c.StartStopTask(ctx, userCred, data, "")
}
func (c *SContainer) StartStopTask(ctx context.Context, userCred mcclient.TokenCredential, data *api.ContainerStopInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_STOPPING, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerStopTask", c, userCred, jsonutils.Marshal(data).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) PerformRestart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *api.ContainerRestartInput) (jsonutils.JSONObject, error) {
if !data.Force {
if !sets.NewString(
api.CONTAINER_STATUS_RUNNING,
api.CONTAINER_STATUS_PROBING,
api.CONTAINER_STATUS_PROBE_FAILED,
api.CONTAINER_STATUS_STOP_FAILED).Has(c.Status) {
return nil, httperrors.NewInvalidStatusError("Can't restart container in status %s", c.Status)
}
}
return nil, c.StartRestartTask(ctx, userCred, data, "")
}
func (c *SContainer) StartRestartTask(ctx context.Context, userCred mcclient.TokenCredential, data *api.ContainerRestartInput, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "ContainerRestartTask", c, userCred, jsonutils.Marshal(data).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, c.StartSyncStatusTask(ctx, userCred, "")
}
func (c *SContainer) StartSyncStatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_SYNC_STATUS, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerSyncStatusTask", c, userCred, nil, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query, data jsonutils.JSONObject) error {
return c.StartDeleteTask(ctx, userCred, "", jsonutils.QueryBoolean(data, "purge", false))
}
func (c *SContainer) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, purge bool) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_DELETING, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerDeleteTask", c, userCred, jsonutils.Marshal(map[string]interface{}{
"purge": purge,
}).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (m *SContainerManager) GetImageCredential(ctx context.Context, userCred mcclient.TokenCredential, id string) (*apis.ContainerPullImageAuthConfig, error) {
s := auth.GetSession(ctx, userCred, options.Options.Region)
ret, err := identitymod.Credentials.GetById(s, id, nil)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound || strings.Contains(err.Error(), "NotFound") {
ret, err = identitymod.Credentials.GetByName(s, id, nil)
if err != nil {
return nil, errors.Wrapf(err, "get credential by id or name of %s", id)
}
}
return nil, errors.Wrapf(err, "get credentials by id with %s", userCred.String())
}
credType, _ := ret.GetString("type")
if credType != identityapi.CONTAINER_IMAGE_TYPE {
return nil, httperrors.NewNotSupportedError("unsupported credential type %s", credType)
}
blobStr, err := ret.GetString("blob")
if err != nil {
return nil, errors.Wrap(err, "get blob")
}
obj, err := jsonutils.ParseString(blobStr)
if err != nil {
return nil, errors.Wrapf(err, "json parse string: %s", blobStr)
}
blob := new(identityapi.CredentialContainerImageBlob)
if err := obj.Unmarshal(blob); err != nil {
return nil, errors.Wrap(err, "unmarshal blob")
}
out := &apis.ContainerPullImageAuthConfig{
Username: blob.Username,
Password: blob.Password,
Auth: blob.Auth,
ServerAddress: blob.ServerAddress,
IdentityToken: blob.IdentityToken,
RegistryToken: blob.RegistryToken,
}
return out, nil
}
func (c *SContainer) GetImageCredential(ctx context.Context, userCred mcclient.TokenCredential) (*apis.ContainerPullImageAuthConfig, error) {
if c.Spec.ImageCredentialId == "" {
return nil, errors.Wrap(errors.ErrEmpty, "image_credential_id is empty")
}
return GetContainerManager().GetImageCredential(ctx, userCred, c.Spec.ImageCredentialId)
}
func (c *SContainer) GetHostPullImageInput(ctx context.Context, userCred mcclient.TokenCredential) (*hostapi.ContainerPullImageInput, error) {
input := &hostapi.ContainerPullImageInput{
Image: c.Spec.Image,
PullPolicy: c.Spec.ImagePullPolicy,
}
if c.Spec.ImageCredentialId != "" {
cred, err := c.GetImageCredential(ctx, userCred)
if err != nil {
return nil, errors.Wrapf(err, "GetImageCredential %s", c.Spec.ImageCredentialId)
}
input.Auth = cred
}
return input, nil
}
func (c *SContainer) StartPullImageTask(ctx context.Context, userCred mcclient.TokenCredential, input *hostapi.ContainerPullImageInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_PULLING_IMAGE, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerPullImageTask", c, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
return c.SVirtualResourceBase.Delete(ctx, userCred)
}
func (m *SContainerManager) ConvertVolumeMountRelationToSpec(ctx context.Context, userCred mcclient.TokenCredential, relation []*ContainerVolumeMountRelation) ([]*hostapi.ContainerVolumeMount, error) {
mounts := make([]*hostapi.ContainerVolumeMount, 0)
for _, r := range relation {
mount, err := r.ToHostMount(ctx, userCred)
if err != nil {
return nil, errors.Wrapf(err, "ToMountOrDevice: %#v", r)
}
if mount != nil {
mounts = append(mounts, mount)
}
}
return mounts, nil
}
func (m *SContainerManager) ConvertRootFsRelationToSpec(ctx context.Context, userCred mcclient.TokenCredential, relation *ContainerRootFsRelation) (*hostapi.ContainerRootfs, error) {
if relation.RootFs == nil {
return nil, nil
}
rootFs, err := relation.ToHostRootFs()
if err != nil {
return nil, errors.Wrap(err, "ToHostRootFs")
}
return rootFs, nil
}
func (c *SContainer) ToHostContainerSpec(ctx context.Context, userCred mcclient.TokenCredential) (*hostapi.ContainerSpec, error) {
rootFsRelation, err := c.GetRootFsRelation()
if err != nil {
return nil, errors.Wrap(err, "GetRootFsRelation")
}
rootFs, err := GetContainerManager().ConvertRootFsRelationToSpec(ctx, userCred, rootFsRelation)
if err != nil {
return nil, errors.Wrap(err, "ConvertRootFsRelationToSpec")
}
vmRelation, err := c.GetVolumeMountRelations()
if err != nil {
return nil, errors.Wrap(err, "GetVolumeMountRelations")
}
mounts, err := GetContainerManager().ConvertVolumeMountRelationToSpec(ctx, userCred, vmRelation)
if err != nil {
return nil, errors.Wrap(err, "ConvertVolumeRelationToSpec")
}
ctrDevs := make([]*hostapi.ContainerDevice, 0)
for _, dev := range c.Spec.Devices {
ctrDev, err := GetContainerDeviceDriver(dev.Type).ToHostDevice(dev)
if err != nil {
return nil, errors.Wrapf(err, "ToHostDevice %s", jsonutils.Marshal(dev))
}
ctrDevs = append(ctrDevs, ctrDev)
}
spec := c.Spec.ContainerSpec
hSpec := &hostapi.ContainerSpec{
ContainerSpec: spec,
Rootfs: rootFs,
VolumeMounts: mounts,
Devices: ctrDevs,
}
pullInput, err := c.GetHostPullImageInput(ctx, userCred)
if err != nil {
return nil, errors.Wrap(err, "GetHostPullImageInput")
}
hSpec.ImageCredentialToken = base64.StdEncoding.EncodeToString([]byte(jsonutils.Marshal(pullInput.Auth).String()))
return hSpec, nil
}
func (c *SContainer) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential) (*hostapi.ContainerDesc, error) {
spec, err := c.ToHostContainerSpec(ctx, userCred)
if err != nil {
return nil, errors.Wrap(err, "ToHostContainerSpec")
}
return &hostapi.ContainerDesc{
Id: c.GetId(),
Name: c.GetName(),
Spec: spec,
StartedAt: c.StartedAt,
LastFinishedAt: c.LastFinishedAt,
RestartCount: c.RestartCount,
}, nil
}
func (c *SContainer) PrepareSaveImage(ctx context.Context, userCred mcclient.TokenCredential, input *api.ContainerSaveVolumeMountToImageInput) (string, error) {
imageInput := &CreateGlanceImageInput{
Name: input.Name,
GenerateName: input.GenerateName,
DiskFormat: imageapi.IMAGE_DISK_FORMAT_TGZ,
Properties: map[string]string{
"notes": input.Notes,
},
// inherit the ownership of disk
ProjectId: c.ProjectId,
}
if len(input.Dirs) > 0 {
dirMap := make(map[string]string, 0)
for _, dir := range input.Dirs {
dirMap[dir] = dir
}
imageInput.Properties[imageapi.IMAGE_INTERNAL_PATH_MAP] = jsonutils.Marshal(dirMap).String()
}
if input.UsedByPostOverlay {
imageInput.Properties[imageapi.IMAGE_USED_BY_POST_OVERLAY] = jsonutils.Marshal(input.UsedByPostOverlay).String()
}
// check class metadata
cm, err := c.GetAllClassMetadata()
if err != nil {
return "", errors.Wrap(err, "unable to GetAllClassMetadata")
}
imageInput.ClassMetadata = cm
return DiskManager.CreateGlanceImage(ctx, userCred, imageInput)
}
func (c *SContainer) PerformSaveVolumeMountImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ContainerSaveVolumeMountToImageInput) (*hostapi.ContainerSaveVolumeMountToImageInput, error) {
if c.GetStatus() != api.CONTAINER_STATUS_EXITED {
return nil, httperrors.NewInvalidStatusError("Can't save volume disk of container in status %s", c.Status)
}
if c.GetPod().GetStatus() != api.VM_READY {
return nil, httperrors.NewInvalidStatusError("Can't save volume disk of pod in status %s", c.GetPod().GetStatus())
}
vols := c.GetVolumeMounts()
if input.Index < 0 || input.Index >= len(vols) {
return nil, httperrors.NewInputParameterError("Only %d volume_mounts", len(vols))
}
imageId, err := c.PrepareSaveImage(ctx, userCred, input)
if err != nil {
return nil, errors.Wrap(err, "prepare to save image")
}
vrs, err := c.GetVolumeMountRelations()
if err != nil {
return nil, errors.Wrap(err, "GetVolumeMountRelations")
}
hvm, err := vrs[input.Index].ToHostMount(ctx, userCred)
if err != nil {
return nil, errors.Wrap(err, "ToHostMount")
}
cleanupDirPath := func(dirPath string) string {
nPath := ""
pathSegs := strings.Split(dirPath, "/")
for _, seg := range pathSegs {
if len(seg) > 0 {
nPath = filepath.Join(nPath, seg)
}
}
return nPath
}
cleanupDirPaths := func(dirPaths []string) []string {
for i := range dirPaths {
dirPaths[i] = cleanupDirPath(dirPaths[i])
}
return dirPaths
}
hostInput := &hostapi.ContainerSaveVolumeMountToImageInput{
ImageId: imageId,
VolumeMountIndex: input.Index,
VolumeMount: hvm,
VolumeMountDirs: cleanupDirPaths(input.Dirs),
VolumeMountPrefix: cleanupDirPath(input.DirPrefix),
}
return hostInput, c.StartSaveVolumeMountImage(ctx, userCred, hostInput, "")
}
func (c *SContainer) StartSaveVolumeMountImage(ctx context.Context, userCred mcclient.TokenCredential, input *hostapi.ContainerSaveVolumeMountToImageInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_SAVING_IMAGE, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerSaveVolumeMountImageTask", c, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) GetPodDriver() IPodDriver {
driver, err := c.GetPod().GetDriver()
if err != nil {
return nil
}
return driver.(IPodDriver)
}
func (c *SContainer) GetDetailsExecInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*api.ContainerExecInfoOutput, error) {
gst := c.GetPod()
host, err := gst.GetHost()
if err != nil {
return nil, errors.Wrap(err, "GetHost")
}
out := &api.ContainerExecInfoOutput{
HostUri: host.ManagerUri,
PodId: c.GuestId,
ContainerId: c.Id,
}
return out, nil
}
func (c *SContainer) PerformExecSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ContainerExecSyncInput) (jsonutils.JSONObject, error) {
if sets.NewString(
api.CONTAINER_STATUS_EXITED,
api.CONTAINER_STATUS_CREATED,
).Has(c.GetStatus()) {
return nil, httperrors.NewInvalidStatusError("Can't exec container in status %s", c.Status)
}
return c.GetPodDriver().RequestExecSyncContainer(ctx, userCred, c, input)
}
func (c *SContainer) PerformSetResourcesLimit(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ContainerResourcesSetInput) (jsonutils.JSONObject, error) {
limit := &input.ContainerResources
if err := c.ValidateResourcesLimit(limit, input.DisableLimitCheck); err != nil {
return nil, errors.Wrap(err, "ValidateResourcesLimit")
}
if _, err := db.Update(c, func() error {
c.Spec.ResourcesLimit = limit
return nil
}); err != nil {
return nil, errors.Wrap(err, "Update spec.resources_limit")
}
if !api.ContainerRunningStatus.Has(c.GetStatus()) {
return nil, nil
}
return c.GetPodDriver().RequestSetContainerResourcesLimit(ctx, userCred, c, limit)
}
func (c *SContainer) ValidateResourcesLimit(limit *apis.ContainerResources, disableLimitCheck bool) error {
if limit == nil {
return httperrors.NewInputParameterError("limit cannot be nil")
}
pod := c.GetPod()
if limit.CpuCfsQuota != nil {
if *limit.CpuCfsQuota <= 0 {
return httperrors.NewInputParameterError("invalid cpu_cfs_quota %f", *limit.CpuCfsQuota)
}
if !disableLimitCheck {
if *limit.CpuCfsQuota > float64(pod.VcpuCount) {
return httperrors.NewInputParameterError("cpu_cfs_quota %f can't large than %d", *limit.CpuCfsQuota, pod.VcpuCount)
}
}
}
return nil
}
type ContainerReleasedDevice struct {
*api.ContainerDevice
DeviceType string
DeviceModel string
}
func NewContainerReleasedDevice(device *api.ContainerDevice, devType, devModel string) *ContainerReleasedDevice {
return &ContainerReleasedDevice{
ContainerDevice: device,
DeviceType: devType,
DeviceModel: devModel,
}
}
func (c *SContainer) SaveReleasedDevices(ctx context.Context, userCred mcclient.TokenCredential, devs map[string]ContainerReleasedDevice) error {
return c.SetMetadata(ctx, api.CONTAINER_METADATA_RELEASED_DEVICES, devs, userCred)
}
func (c *SContainer) GetReleasedDevices(ctx context.Context, userCred mcclient.TokenCredential) (map[string]ContainerReleasedDevice, error) {
out := make(map[string]ContainerReleasedDevice, 0)
if ret := c.GetMetadata(ctx, api.CONTAINER_METADATA_RELEASED_DEVICES, userCred); ret == "" {
return out, nil
}
obj := c.GetMetadataJson(ctx, api.CONTAINER_METADATA_RELEASED_DEVICES, userCred)
if obj == nil {
return nil, errors.Error("get metadata released devices")
}
if err := obj.Unmarshal(&out); err != nil {
return nil, errors.Wrap(err, "Unmarshal metadata released devices")
}
return out, nil
}
func (c *SContainer) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ContainerPerformStatusInput) (jsonutils.JSONObject, error) {
if api.ContainerExitedStatus.Has(c.GetStatus()) {
if sets.NewString(api.CONTAINER_STATUS_PROBE_FAILED, api.CONTAINER_STATUS_NET_FAILED).Has(input.Status) {
return nil, httperrors.NewInputParameterError("can't set container status to %s when %s", input.Status, c.Status)
}
}
if _, err := db.Update(c, func() error {
if input.RestartCount > 0 {
c.RestartCount = input.RestartCount
}
if api.ContainerRunningStatus.Has(input.Status) {
// 当容器状态是运行时 restart_count 重新计数
c.RestartCount = 0
}
if input.StartedAt != nil {
c.StartedAt = *input.StartedAt
}
if input.LastFinishedAt != nil {
c.LastFinishedAt = *input.LastFinishedAt
}
return nil
}); err != nil {
return nil, errors.Wrap(err, "Update container status")
}
return c.SVirtualResourceBase.PerformStatus(ctx, userCred, query, input.PerformStatusInput)
}
func (c *SContainer) getContainerHostCommitInput(ctx context.Context, userCred mcclient.TokenCredential, input *api.ContainerCommitInput) (*hostapi.ContainerCommitInput, error) {
var hostInput = &hostapi.ContainerCommitInput{
Auth: new(apis.ContainerPullImageAuthConfig),
}
var repoUrl string
imageName := input.ImageName
tag := input.Tag
if imageName == "" {
imageName = c.GetName()
}
if tag == "" {
tag = time.Now().Format("20060102150405")
}
if input.RegistryId != "" {
s := auth.GetSession(ctx, userCred, consts.GetRegion())
obj, err := kubemod.ContainerRegistries.Get(s, input.RegistryId, nil)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
reg := new(api.KubeServerContainerRegistryDetails)
if err := obj.Unmarshal(reg); err != nil {
return nil, errors.Wrap(err, "Unmarshal kube server registry details")
}
if reg.Config == nil {
confObj, err := kubemod.ContainerRegistries.GetSpecific(s, input.RegistryId, "config", nil)
if err != nil {
return nil, errors.Wrap(err, "Get kube server registry config")
}
reg.Config = new(api.KubeServerContainerRegistryConfig)
if err := confObj.Unmarshal(reg.Config); err != nil {
return nil, errors.Wrap(err, "Unmarshal kube server registry config")
}
}
repoUrl = reg.Url
if reg.Config != nil {
switch reg.Type {
case "common":
cfg := reg.Config.Common
hostInput.Auth.Username = cfg.Username
hostInput.Auth.Password = cfg.Password
case "harbor":
cfg := reg.Config.Harbor
hostInput.Auth.Username = cfg.Username
hostInput.Auth.Password = cfg.Password
default:
return nil, httperrors.NewInputParameterError("invalid registry type %s", reg.Type)
}
}
} else if input.ExternalRegistry != nil {
repoUrl = input.ExternalRegistry.Url
if repoUrl == "" {
return nil, httperrors.NewNotEmptyError("empty external registry url")
}
hostInput.Auth = input.ExternalRegistry.Auth
} else {
return nil, httperrors.NewInputParameterError("one of registry_id or external_registry must provided")
}
repoPrefix := strings.TrimPrefix(strings.TrimPrefix(repoUrl, "http://"), "https://")
hostInput.Repository = fmt.Sprintf("%s:%s", filepath.Join(repoPrefix, imageName), tag)
return hostInput, nil
}
func (c *SContainer) PerformCommit(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ContainerCommitInput) (*api.ContainerCommitOutput, error) {
hostInput, err := c.getContainerHostCommitInput(ctx, userCred, input)
if err != nil {
return nil, err
}
out := &api.ContainerCommitOutput{
Repository: hostInput.Repository,
}
return out, c.StartCommit(ctx, userCred, hostInput, "")
}
func (c *SContainer) StartCommit(ctx context.Context, userCred mcclient.TokenCredential, input *hostapi.ContainerCommitInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_COMMITTING, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerCommitTask", c, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) isPostOverlayExist(vm *apis.ContainerVolumeMount, ov *apis.ContainerVolumeMountDiskPostOverlay) (*apis.ContainerVolumeMountDiskPostOverlay, bool) {
for i := range vm.Disk.PostOverlay {
cov := vm.Disk.PostOverlay[i]
if cov.IsEqual(*ov) {
return cov, true
}
}
return nil, false
}
func (c *SContainer) validateVolumeMountPostOverlayAction(action string, index int) (*apis.ContainerVolumeMount, error) {
if !api.ContainerExitedStatus.Has(c.Status) && !api.ContainerRunningStatus.Has(c.Status) {
return nil, httperrors.NewInvalidStatusError("can't %s post overlay on status %s", action, c.Status)
}
if index >= len(c.Spec.VolumeMounts) {
return nil, httperrors.NewInputParameterError("index %d out of volume_mount size %d", index, len(c.Spec.VolumeMounts))
}
vm := new(apis.ContainerVolumeMount)
curVm := c.Spec.VolumeMounts[index]
if err := jsonutils.Marshal(curVm).Unmarshal(vm); err != nil {
return nil, errors.Wrap(err, "use json unmarshal to new volume mount")
}
if vm.Type != apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK {
return nil, httperrors.NewInputParameterError("invalid volume mount type %s", vm.Type)
}
return vm, nil
}
func (c *SContainer) GetVolumeMountCopy(index int) (*apis.ContainerVolumeMount, error) {
if index >= len(c.Spec.VolumeMounts) {
return nil, httperrors.NewInputParameterError("index %d out of volume_mount size %d", index, len(c.Spec.VolumeMounts))
}
vm := new(apis.ContainerVolumeMount)
curVm := c.Spec.VolumeMounts[index]
if err := jsonutils.Marshal(curVm).Unmarshal(vm); err != nil {
return nil, errors.Wrap(err, "use json unmarshal to new volume mount")
}
return vm, nil
}
func (c *SContainer) getPostOverlayVolumeMount(
index int,
updateF func(mount *apis.ContainerVolumeMount) (*apis.ContainerVolumeMount, error),
) (*apis.ContainerVolumeMount, error) {
vm, err := c.GetVolumeMountCopy(index)
if err != nil {
return nil, err
}
return updateF(vm)
}
func (c *SContainer) GetAddPostOverlayVolumeMount(index int, ovs []*apis.ContainerVolumeMountDiskPostOverlay) (*apis.ContainerVolumeMount, error) {
return c.getPostOverlayVolumeMount(index, func(vm *apis.ContainerVolumeMount) (*apis.ContainerVolumeMount, error) {
if vm.Disk.PostOverlay == nil {
vm.Disk.PostOverlay = []*apis.ContainerVolumeMountDiskPostOverlay{}
}
vm.Disk.PostOverlay = append(vm.Disk.PostOverlay, ovs...)
return vm, nil
})
}
func (c *SContainer) GetRemovePostOverlayVolumeMount(index int, ovs []*apis.ContainerVolumeMountDiskPostOverlay) (*apis.ContainerVolumeMount, error) {
return c.getPostOverlayVolumeMount(index, func(vm *apis.ContainerVolumeMount) (*apis.ContainerVolumeMount, error) {
// remove post overlay
for _, ov := range ovs {
vm.Disk = c.removePostOverlay(vm.Disk, ov)
}
return vm, nil
})
}
func (c *SContainer) PerformAddVolumeMountPostOverlay(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ContainerVolumeMountAddPostOverlayInput) (jsonutils.JSONObject, error) {
vm, err := c.validateVolumeMountPostOverlayAction("add", input.Index)
if err != nil {
return nil, err
}
totalOvs := []*apis.ContainerVolumeMountDiskPostOverlay{}
totalOvs = append(totalOvs, vm.Disk.PostOverlay...)
drv := GetContainerVolumeMountDriver(vm.Type)
dDrv := drv.(IContainerVolumeMountDiskDriver)
for i := range input.PostOverlay {
ov := input.PostOverlay[i]
cov, isExist := c.isPostOverlayExist(vm, ov)
if isExist {
return nil, httperrors.NewInputParameterError("post overlay already exists: %s", jsonutils.Marshal(cov))
}
if err := dDrv.ValidatePostSingleOverlay(ctx, userCred, ov); err != nil {
return nil, errors.Wrapf(err, "validate post overlay %s", jsonutils.Marshal(ov))
}
totalOvs = append(totalOvs, ov)
}
if err := dDrv.ValidatePostOverlayTargetDirs(totalOvs); err != nil {
return nil, errors.Wrapf(err, "validate container target dirs")
}
return nil, c.StartAddVolumeMountPostOverlayTask(ctx, userCred, input, "")
}
func (c *SContainer) StartCacheImagesTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ContainerCacheImagesInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_CACHE_IMAGE, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerCacheImagesTask", c, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "New ContainerCacheImagesTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) StartAddVolumeMountPostOverlayTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ContainerVolumeMountAddPostOverlayInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_ADD_POST_OVERLY, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerAddVolumeMountPostOverlayTask", c, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "New ContainerAddVolumeMountPostOverlayTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) StartRemoveVolumeMountPostOverlayTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ContainerVolumeMountRemovePostOverlayInput, parentTaskId string) error {
c.SetStatus(ctx, userCred, api.CONTAINER_STATUS_REMOVE_POST_OVERLY, "")
task, err := taskman.TaskManager.NewTask(ctx, "ContainerRemoveVolumeMountPostOverlayTask", c, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "New ContainerRemoveVolumeMountPostOverlayTask")
}
return task.ScheduleRun(nil)
}
func (c *SContainer) PerformRemoveVolumeMountPostOverlay(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ContainerVolumeMountRemovePostOverlayInput) (jsonutils.JSONObject, error) {
vm, err := c.validateVolumeMountPostOverlayAction("remove", input.Index)
if err != nil {
return nil, err
}
if len(vm.Disk.PostOverlay) == 0 {
return nil, httperrors.NewInputParameterError("no post overlay")
}
for i, ov := range input.PostOverlay {
cov, isExist := c.isPostOverlayExist(vm, ov)
if !isExist {
return nil, httperrors.NewInputParameterError("post overlay not exists: %s", jsonutils.Marshal(ov))
}
input.PostOverlay[i] = cov
}
return nil, c.StartRemoveVolumeMountPostOverlayTask(ctx, userCred, input, "")
}
func (c *SContainer) removePostOverlay(vmd *apis.ContainerVolumeMountDisk, ov *apis.ContainerVolumeMountDiskPostOverlay) *apis.ContainerVolumeMountDisk {
curOvs := vmd.PostOverlay
resultOvs := []*apis.ContainerVolumeMountDiskPostOverlay{}
for i := range curOvs {
cov := curOvs[i]
if cov.IsEqual(*ov) {
continue
}
resultOvs = append(resultOvs, cov)
}
vmd.PostOverlay = resultOvs
return vmd
}
func (c *SContainer) SetStatusFromHost(ctx context.Context, userCred mcclient.TokenCredential, resp api.ContainerSyncStatusResponse, reason string) error {
errs := []error{}
if err := c.SetStatus(ctx, userCred, resp.Status, reason); err != nil {
errs = append(errs, errors.Wrap(err, "SetStatus"))
}
if _, err := db.Update(c, func() error {
if resp.RestartCount > 0 {
c.RestartCount = resp.RestartCount
}
c.StartedAt = resp.StartedAt
return nil
}); err != nil {
errs = append(errs, errors.Wrap(err, "Update container started_at: %s"))
}
return errors.NewAggregate(errs)
}