| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- // 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 storageman
- import (
- "context"
- "fmt"
- "os"
- "path"
- "path/filepath"
- "time"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- "yunion.io/x/cloudmux/pkg/multicloud/esxi"
- "yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/qemuimgfmt"
- "yunion.io/x/pkg/util/seclib"
- "yunion.io/x/pkg/util/timeutils"
- api "yunion.io/x/onecloud/pkg/apis/compute"
- "yunion.io/x/onecloud/pkg/cloudcommon/agent/iagent"
- esxi_options "yunion.io/x/onecloud/pkg/esxi/options"
- "yunion.io/x/onecloud/pkg/hostman/guestman/desc"
- deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
- "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
- "yunion.io/x/onecloud/pkg/hostman/hostutils"
- "yunion.io/x/onecloud/pkg/hostman/options"
- modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
- "yunion.io/x/onecloud/pkg/util/logclient"
- "yunion.io/x/onecloud/pkg/util/procutils"
- "yunion.io/x/onecloud/pkg/util/qemuimg"
- )
- type SAgentStorage struct {
- SLocalStorage
- agent iagent.IAgent
- }
- func NewAgentStorage(manager *SStorageManager, agent iagent.IAgent, path string) *SAgentStorage {
- s := &SAgentStorage{SLocalStorage: *NewLocalStorage(manager, path, 0)}
- s.agent = agent
- s.checkDirC(path)
- return s
- }
- func (as *SAgentStorage) GetDiskById(diskId string) (IDisk, error) {
- return NewAgentDisk(as, diskId), nil
- }
- func (as *SAgentStorage) CreateDiskByDiskInfo(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
- input, ok := params.(*SDiskCreateByDiskinfo)
- if !ok {
- return nil, errors.Wrap(hostutils.ParamsError, "CreateDiskByDiskInfo params format error")
- }
- diskMeta, err := as.SLocalStorage.CreateDiskByDiskinfo(ctx, params)
- if err != nil {
- return nil, errors.Wrap(err, "as.SLocalStorage.CreateDiskByDiskinfo")
- }
- disk, err := as.GetDiskById(input.DiskId)
- if err != nil {
- return nil, errors.Wrapf(err, "GetDiskById(%s)", input.DiskId)
- }
- _, ds, err := as.getHostAndDatastore(ctx, SHostDatastore{HostIp: input.DiskInfo.HostIp, Datastore: input.DiskInfo.Datastore})
- if err != nil {
- return nil, errors.Wrap(err, "as.getHostAndDatastore")
- }
- remotePath := "disks/" + input.DiskId
- file, err := os.Open(disk.GetPath())
- if err != nil {
- return nil, errors.Wrap(err, "fail to open disk path")
- }
- defer file.Close()
- err = ds.Upload(ctx, remotePath, file)
- if err != nil {
- return nil, errors.Wrap(err, "dataStore.Upload")
- }
- as.RemoveDisk(disk)
- return diskMeta, nil
- }
- func (as *SAgentStorage) agentRebuildRoot(ctx context.Context, data jsonutils.JSONObject) error {
- type sRebuildParam struct {
- GuestExtId string
- SHostDatastore
- Desc struct {
- Disks []struct {
- DiskId string
- ImagePath string
- ImageInfo struct {
- ImageExternalId string
- }
- }
- }
- }
- rp := sRebuildParam{}
- err := data.Unmarshal(&rp)
- if err != nil {
- return errors.Wrap(err, hostutils.ParamsError.Error())
- }
- host, _, err := as.getHostAndDatastore(ctx, rp.SHostDatastore)
- if err != nil {
- return errors.Wrap(err, "as.getHostAndDatastore")
- }
- ivm, err := host.GetIVMById(rp.GuestExtId)
- if err != nil {
- return errors.Wrapf(err, "host.GetIVMById of id '%s'", rp.GuestExtId)
- }
- if len(rp.Desc.Disks) == 0 {
- return errors.Wrap(hostutils.ParamsError, "agentRebuildRoot data.desc.disks is empty")
- }
- imagePath := rp.Desc.Disks[0].ImagePath
- uefi := false
- var newPath string
- if len(imagePath) == 0 {
- vm, err := host.SearchTemplateVM(rp.Desc.Disks[0].ImageInfo.ImageExternalId)
- if err != nil {
- return errors.Wrapf(err, "SearchTemplateVM(%s)", rp.Desc.Disks[0].ImageInfo.ImageExternalId)
- }
- uefi = vm.GetBios() == cloudprovider.UEFI
- newPath, err = vm.GetRootImagePath()
- if err != nil {
- return errors.Wrapf(err, "GetRootImagePath")
- }
- } else {
- newPath, err = host.FileUrlPathToDsPath(imagePath)
- if err != nil {
- return err
- }
- }
- diskId := rp.Desc.Disks[0].DiskId
- vm := ivm.(*esxi.SVirtualMachine)
- return vm.DoRebuildRoot(ctx, newPath, diskId, uefi)
- }
- func (as *SAgentStorage) agentCreateGuest(ctx context.Context, data jsonutils.JSONObject) (bool, error) {
- hd := SHostDatastore{}
- err := data.Unmarshal(&hd)
- if err != nil {
- return false, errors.Wrap(err, hostutils.ParamsError.Error())
- }
- host, ds, err := as.getHostAndDatastore(ctx, hd)
- if err != nil {
- return false, err
- }
- desc, _ := data.Get("desc")
- descDict, ok := desc.(*jsonutils.JSONDict)
- if !ok {
- return false, errors.Wrap(hostutils.ParamsError, "agentCreateGuest data format error")
- }
- createParam := esxi.SCreateVMParam{}
- err = descDict.Unmarshal(&createParam)
- if err != nil {
- return false, errors.Wrapf(err, "%s: fail to unmarshal to esxi.SCreateVMParam", hostutils.ParamsError)
- }
- if !esxi_options.Options.EnableFolderNameUUID {
- createParam.Uuid = ""
- }
- needDeploy, vm, err := host.CreateVM2(ctx, ds, createParam)
- if err != nil {
- return false, errors.Wrap(err, "SHost.CreateVM2")
- }
- tags := map[string]string{}
- data.Unmarshal(&tags, "tags")
- if len(tags) > 0 {
- err = vm.SetTags(tags, true)
- if err != nil {
- log.Warningf("set tags for vm %s error: %v", createParam.Name, err)
- }
- }
- if esxi_options.Options.EnableFolderNameUUID && len(createParam.Uuid) > 0 {
- err = as.tryRenameVm(ctx, vm, createParam.Name)
- if err != nil {
- return false, errors.Wrapf(err, "RenameVm name '%s'", createParam.Name)
- }
- }
- return needDeploy, nil
- }
- func (as *SAgentStorage) tryRenameVm(ctx context.Context, vm *esxi.SVirtualMachine, name string) error {
- var (
- tried = 0
- err error
- )
- alterName := fmt.Sprintf("%s-%s", name, timeutils.ShortDate(time.Now()))
- cands := []string{name, alterName}
- for tried < 10 {
- var n string
- if tried < len(cands) {
- n = cands[tried]
- } else {
- n = fmt.Sprintf("%s-%d", alterName, tried-len(cands)+1)
- }
- tried += 1
- err = vm.DoRename(ctx, n)
- if err == nil {
- return nil
- }
- }
- return err
- }
- type esxiVm struct {
- UUID string
- Name string
- }
- func (self esxiVm) Keyword() string {
- return "server"
- }
- func (self esxiVm) GetId() string {
- return self.UUID
- }
- func (self esxiVm) GetName() string {
- return self.Name
- }
- type SHostDatastore struct {
- HostIp string
- Datastore vcenter.SVCenterAccessInfo
- }
- type SDeployInfo struct {
- Action string
- SHostDatastore
- Desc desc.SGuestDesc
- PublicKey string
- DeletePublicKey string
- AdminPublicKey string
- ProjectPublicKey string
- GuestExtId string
- GuestId string
- ResetPassword bool
- Password string
- EnableCloudInit bool
- LoginAccount string
- DeployTelegraf bool
- TelegrafConf string
- Deploys []struct {
- Path string
- Content string
- Action string
- }
- }
- func (as *SAgentStorage) AgentDeployGuest(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
- init := false
- dataDict := data.(*jsonutils.JSONDict)
- log.Debugf("dataDict: %s", dataDict)
- deployInfo := SDeployInfo{}
- err := dataDict.Unmarshal(&deployInfo)
- if err != nil {
- return nil, errors.Wrap(err, "Unmarshal DeployInfo")
- }
- var needDeploy = true
- switch deployInfo.Action {
- case "create":
- needDeploy, err = as.agentCreateGuest(ctx, dataDict)
- if err != nil {
- return nil, errors.Wrap(err, "agentCreateGuest")
- }
- init = true
- case "rebuild":
- err := as.agentRebuildRoot(ctx, dataDict)
- if err != nil {
- return nil, errors.Wrap(err, "agentRebuildRoot")
- }
- init = true
- }
- dc, info, err := esxi.NewESXiClientFromJson(ctx, jsonutils.Marshal(deployInfo.Datastore))
- if err != nil {
- return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
- }
- host, err := dc.FindHostByIp(deployInfo.HostIp)
- if err != nil {
- return nil, errors.Wrap(err, "SDatacenter.FindHostByIp")
- }
- vmId := deployInfo.GuestExtId
- if !esxi_options.Options.EnableFolderNameUUID && deployInfo.Action == "create" {
- vmId = deployInfo.Desc.Name
- }
- realHost := host
- ivm, err := host.GetIVMById(vmId)
- if err == cloudprovider.ErrNotFound {
- // reschedule by DRS, migrate to other host
- siblingHosts, err := host.GetSiblingHosts()
- if err != nil {
- return nil, errors.Wrap(err, "GetSiblingHosts")
- }
- for _, sh := range siblingHosts {
- ivm, err = host.GetIVMById(vmId)
- if err == nil {
- realHost = sh
- break
- }
- }
- }
- if err != nil {
- return nil, errors.Wrap(err, "SHost.GetIVMById")
- }
- if ivm == nil {
- return nil, errors.Error(fmt.Sprintf("no such vm '%s'", ivm.GetId()))
- }
- vm := ivm.(*esxi.SVirtualMachine)
- disks, err := vm.GetIDisks()
- if err != nil {
- return nil, errors.Wrap(err, "VM.GetIDisks")
- }
- if len(disks) == 0 {
- return nil, errors.Error(fmt.Sprintf("no such disks for vm %s", vm.GetId()))
- }
- vmref := vm.GetMoid()
- rootPath := disks[0].(*esxi.SVirtualDisk).GetFilename()
- log.Debugf("host: %s, port: %d, user: %s, passwd: %s", info.Host, info.Port, info.Account, info.Password)
- var deploy *deployapi.DeployGuestFsResponse
- if needDeploy {
- vddkInfo := deployapi.VDDKConInfo{
- Host: info.Host,
- Port: int32(info.Port),
- User: info.Account,
- Passwd: info.Password,
- Vmref: vmref,
- }
- guestDesc := deployapi.GuestDesc{}
- err = dataDict.Unmarshal(&guestDesc, "desc")
- if err != nil {
- return nil, errors.Wrapf(err, "%s: unmarshal to guestDesc", hostutils.ParamsError.Error())
- }
- guestDesc.Hypervisor = api.HYPERVISOR_ESXI
- key := deployapi.SSHKeys{
- PublicKey: deployInfo.PublicKey,
- DeletePublicKey: deployInfo.DeletePublicKey,
- AdminPublicKey: deployInfo.AdminPublicKey,
- ProjectPublicKey: deployInfo.ProjectPublicKey,
- }
- deployArray := make([]*deployapi.DeployContent, 0)
- for _, deploy := range deployInfo.Deploys {
- deployArray = append(deployArray, &deployapi.DeployContent{
- Path: deploy.Path,
- Content: deploy.Content,
- Action: deploy.Action,
- })
- }
- isRandomPassword := false
- passwd := deployInfo.Password
- resetPassword := deployInfo.ResetPassword
- if resetPassword && len(passwd) == 0 {
- passwd = seclib.RandomPassword(12)
- isRandomPassword = true
- }
- if deployInfo.DeployTelegraf && deployInfo.TelegrafConf == "" {
- return nil, errors.Errorf("missing telegraf_conf")
- }
- deployInformation := deployapi.NewDeployInfo(&key, deployArray, passwd, isRandomPassword, init, false,
- options.HostOptions.LinuxDefaultRootUser, options.HostOptions.WindowsDefaultAdminUser,
- deployInfo.EnableCloudInit, deployInfo.LoginAccount, deployInfo.DeployTelegraf, deployInfo.TelegrafConf,
- deployInfo.Desc.UserData,
- )
- log.Debugf("deployInfo: %s", jsonutils.Marshal(deployInfo))
- deploy, err = deployclient.GetDeployClient().DeployGuestFs(ctx, &deployapi.DeployParams{
- DiskInfo: &deployapi.DiskInfo{
- Path: rootPath,
- },
- GuestDesc: &guestDesc,
- DeployInfo: deployInformation,
- VddkInfo: &vddkInfo,
- })
- customize := false
- if err != nil {
- model := &esxiVm{}
- dataDict.Unmarshal(model, "desc")
- logclient.AddSimpleActionLog(model, logclient.ACT_VM_DEPLOY, errors.Wrapf(err, "DeployGuestFs"), hostutils.GetComputeSession(context.Background()).GetToken(), false)
- log.Errorf("unable to DeployGuestFs: %v", err)
- customize = true
- } else if deploy == nil {
- log.Errorf("unable to DeployGuestFs: deploy is nil")
- customize = true
- } else if len(deploy.Os) == 0 {
- log.Errorf("unable to DeployGuestFs: os is empty")
- customize = true
- }
- if customize {
- as.waitVmToolsVersion(ctx, vm)
- err = vm.DoCustomize(ctx, jsonutils.Marshal(deployInfo.Desc))
- if err != nil {
- log.Errorf("unable to DoCustomize for vm %s: %v", vm.GetId(), err)
- }
- }
- }
- array := jsonutils.NewArray()
- for _, d := range disks {
- disk := d.(*esxi.SVirtualDisk)
- diskId := disk.GetId()
- diskDict := jsonutils.NewDict()
- diskDict.Add(jsonutils.NewString(diskId), "disk_id")
- diskDict.Add(jsonutils.NewString(disk.GetGlobalId()), "uuid")
- diskDict.Add(jsonutils.NewInt(int64(disk.GetDiskSizeMB())), "size")
- diskDict.Add(jsonutils.NewString(disk.GetFilename()), "path")
- diskDict.Add(jsonutils.NewString(disk.GetCacheMode()), "cache_mode")
- diskDict.Add(jsonutils.NewString(disk.GetDiskType()), "disk_type")
- diskDict.Add(jsonutils.NewString(disk.GetDriver()), "driver")
- array.Add(diskDict)
- }
- updated := jsonutils.NewDict()
- updated.Add(array, "disks")
- updated.Add(jsonutils.NewString(vm.GetGlobalId()), "uuid")
- updated.Add(jsonutils.NewString(realHost.GetAccessIp()), "host_ip")
- var ret jsonutils.JSONObject
- if deploy != nil {
- ret = jsonutils.Marshal(deploy)
- } else {
- ret = jsonutils.NewDict()
- }
- ret.(*jsonutils.JSONDict).Update(updated)
- return ret, nil
- }
- func (as *SAgentStorage) waitVmToolsVersion(ctx context.Context, vm *esxi.SVirtualMachine) {
- timeout := 90 * time.Second
- timeUpper := time.Now().Add(timeout)
- for len(vm.GetToolsVersion()) == 0 && time.Now().Before(timeUpper) {
- if vm.GetStatus() != api.VM_RUNNING {
- vm.StartVM(ctx)
- time.Sleep(5 * time.Second)
- }
- }
- timeUpper = time.Now().Add(timeout)
- for vm.GetStatus() == api.VM_RUNNING && time.Now().Before(timeUpper) {
- opts := &cloudprovider.ServerStopOptions{
- IsForce: true,
- }
- vm.StopVM(ctx, opts)
- time.Sleep(5 * time.Second)
- }
- return
- }
- func (as *SAgentStorage) getHostAndDatastore(ctx context.Context, data SHostDatastore) (*esxi.SHost, *esxi.SDatastore, error) {
- client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore)
- if err != nil {
- return nil, nil, errors.Wrap(err, "fail to generate client")
- }
- host, err := client.FindHostByIp(data.HostIp)
- if err != nil {
- return nil, nil, errors.Wrap(err, "fail to find host")
- }
- ds, err := host.FindDataStoreById(data.Datastore.PrivateId)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "fail to find datastore")
- }
- return host, ds, nil
- }
- func (as *SAgentStorage) isExist(dir string) bool {
- _, err := os.Stat(dir)
- if err != nil {
- if os.IsExist(err) {
- return true
- }
- return false
- }
- return true
- }
- func (as *SAgentStorage) checkDirC(dir string) error {
- if as.isExist(dir) {
- return nil
- }
- return os.MkdirAll(dir, 0770)
- }
- func (as *SAgentStorage) checkFileR(file string) error {
- if !as.isExist(file) {
- return nil
- }
- return os.Remove(file)
- }
- func (as *SAgentStorage) PrepareSaveToGlance(ctx context.Context, taskId string, diskInfo jsonutils.JSONObject) (
- ret jsonutils.JSONObject, err error) {
- type specStruct struct {
- Vm vcenter.SVCenterAccessInfo
- Disk vcenter.SVCenterAccessInfo
- HostIp string
- ImageId string
- }
- spec := specStruct{}
- err = diskInfo.Unmarshal(&spec)
- if err != nil {
- return nil, errors.Wrap(hostutils.ParamsError, err.Error())
- }
- destDir := as.GetImgsaveBackupPath()
- as.checkDirC(destDir)
- backupPath := filepath.Join(destDir, fmt.Sprintf("%s.%s", spec.Vm.PrivateId, taskId))
- client, err := esxi.NewESXiClientFromAccessInfo(ctx, &spec.Vm)
- if err != nil {
- return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
- }
- host, err := client.FindHostByIp(spec.HostIp)
- if err != nil {
- return nil, errors.Wrapf(err, "ESXiClient.FindHostByIp of ip '%s'", spec.HostIp)
- }
- ivm, err := host.GetIVMById(spec.Vm.PrivateId)
- if err != nil {
- return nil, errors.Wrapf(err, "esxi.SHost.GetIVMById for '%s'", spec.Vm.PrivateId)
- }
- vm := ivm.(*esxi.SVirtualMachine)
- idisk, err := vm.GetIDiskById(spec.Disk.PrivateId)
- if err != nil {
- return nil, errors.Wrapf(err, "esxi.SVirtualMachine for '%s'", spec.Disk.PrivateId)
- }
- disk := idisk.(*esxi.SVirtualDisk)
- log.Infof("Export VM image to %s", backupPath)
- err = vm.ExportTemplate(ctx, disk.GetIndex(), backupPath)
- if err != nil {
- return nil, errors.Wrap(err, "VM.ExportTemplate")
- }
- dict := jsonutils.NewDict()
- dict.Add(jsonutils.NewString(backupPath), "backup")
- return dict, nil
- }
- func (as *SAgentStorage) SaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
- data, ok := params.(jsonutils.JSONObject)
- if !ok {
- return nil, hostutils.ParamsError
- }
- var (
- imageId, _ = data.GetString("image_id")
- imagePath, _ = data.GetString("image_path")
- compress = jsonutils.QueryBoolean(data, "compress", true)
- format, _ = data.GetString("format")
- )
- log.Debugf("image path: %s", imagePath)
- if err := as.saveToGlance(ctx, imageId, imagePath, compress, format); err != nil {
- log.Errorf("Save to glance failed: %s", err)
- as.onSaveToGlanceFailed(ctx, imageId, err.Error())
- return nil, err
- }
- // delete the backup image
- as.checkFileR(imagePath)
- imagecacheManager := as.Manager.LocalStorageImagecacheManager
- if len(imagecacheManager.GetId()) > 0 {
- err := procutils.NewCommand("rm", "-f", imagePath).Run()
- return nil, err
- } else {
- dstPath := path.Join(imagecacheManager.GetPath(), imageId)
- if err := procutils.NewCommand("mv", imagePath, dstPath).Run(); err != nil {
- log.Errorf("Fail to move saved image to cache: %s", err)
- }
- imagecacheManager.LoadImageCache(imageId)
- _, err := hostutils.RemoteStoragecacheCacheImage(ctx,
- imagecacheManager.GetId(), imageId, "active", dstPath)
- if err != nil {
- log.Errorf("Fail to remote cache image: %s", err)
- }
- }
- return nil, nil
- }
- func (as *SAgentStorage) saveToGlance(ctx context.Context, imageId, imagePath string,
- compress bool, format string) error {
- diskInfo := &deployapi.DiskInfo{
- Path: imagePath,
- }
- ret, err := deployclient.GetDeployClient().SaveToGlance(context.Background(),
- &deployapi.SaveToGlanceParams{DiskInfo: diskInfo, Compress: compress})
- if err != nil {
- return errors.Wrap(err, "DeployClient.SaveToGlance")
- }
- if compress {
- origin, err := qemuimg.NewQemuImage(imagePath)
- if err != nil {
- log.Errorln(err)
- return errors.Wrap(err, "qemuimg.NewQemuImage")
- }
- if len(format) == 0 {
- format = options.HostOptions.DefaultImageSaveFormat
- }
- if format == string(qemuimgfmt.QCOW2) {
- // may be encrypted
- if err := origin.Convert2Qcow2(true, "", "", ""); err != nil {
- log.Errorln(err)
- return err
- }
- } else {
- if err := origin.Convert2Vmdk(true); err != nil {
- log.Errorln(err)
- return err
- }
- }
- }
- f, err := os.Open(imagePath)
- if err != nil {
- return err
- }
- defer f.Close()
- finfo, err := f.Stat()
- if err != nil {
- return err
- }
- size := finfo.Size()
- var params = jsonutils.NewDict()
- if len(ret.OsInfo) > 0 {
- params.Set("os_type", jsonutils.NewString(ret.OsInfo))
- }
- releaseInfoToParams(ret.ReleaseInfo, params)
- params.Set("image_id", jsonutils.NewString(imageId))
- _, err = modules.Images.Upload(hostutils.GetImageSession(ctx),
- params, f, size)
- if err != nil {
- return errors.Wrap(err, "Images.Upload")
- }
- return nil
- }
|