storage_agent.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package storageman
  15. import (
  16. "context"
  17. "fmt"
  18. "os"
  19. "path"
  20. "path/filepath"
  21. "time"
  22. "yunion.io/x/cloudmux/pkg/cloudprovider"
  23. "yunion.io/x/cloudmux/pkg/multicloud/esxi"
  24. "yunion.io/x/cloudmux/pkg/multicloud/esxi/vcenter"
  25. "yunion.io/x/jsonutils"
  26. "yunion.io/x/log"
  27. "yunion.io/x/pkg/errors"
  28. "yunion.io/x/pkg/util/qemuimgfmt"
  29. "yunion.io/x/pkg/util/seclib"
  30. "yunion.io/x/pkg/util/timeutils"
  31. api "yunion.io/x/onecloud/pkg/apis/compute"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/agent/iagent"
  33. esxi_options "yunion.io/x/onecloud/pkg/esxi/options"
  34. "yunion.io/x/onecloud/pkg/hostman/guestman/desc"
  35. deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
  36. "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
  37. "yunion.io/x/onecloud/pkg/hostman/hostutils"
  38. "yunion.io/x/onecloud/pkg/hostman/options"
  39. modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
  40. "yunion.io/x/onecloud/pkg/util/logclient"
  41. "yunion.io/x/onecloud/pkg/util/procutils"
  42. "yunion.io/x/onecloud/pkg/util/qemuimg"
  43. )
  44. type SAgentStorage struct {
  45. SLocalStorage
  46. agent iagent.IAgent
  47. }
  48. func NewAgentStorage(manager *SStorageManager, agent iagent.IAgent, path string) *SAgentStorage {
  49. s := &SAgentStorage{SLocalStorage: *NewLocalStorage(manager, path, 0)}
  50. s.agent = agent
  51. s.checkDirC(path)
  52. return s
  53. }
  54. func (as *SAgentStorage) GetDiskById(diskId string) (IDisk, error) {
  55. return NewAgentDisk(as, diskId), nil
  56. }
  57. func (as *SAgentStorage) CreateDiskByDiskInfo(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  58. input, ok := params.(*SDiskCreateByDiskinfo)
  59. if !ok {
  60. return nil, errors.Wrap(hostutils.ParamsError, "CreateDiskByDiskInfo params format error")
  61. }
  62. diskMeta, err := as.SLocalStorage.CreateDiskByDiskinfo(ctx, params)
  63. if err != nil {
  64. return nil, errors.Wrap(err, "as.SLocalStorage.CreateDiskByDiskinfo")
  65. }
  66. disk, err := as.GetDiskById(input.DiskId)
  67. if err != nil {
  68. return nil, errors.Wrapf(err, "GetDiskById(%s)", input.DiskId)
  69. }
  70. _, ds, err := as.getHostAndDatastore(ctx, SHostDatastore{HostIp: input.DiskInfo.HostIp, Datastore: input.DiskInfo.Datastore})
  71. if err != nil {
  72. return nil, errors.Wrap(err, "as.getHostAndDatastore")
  73. }
  74. remotePath := "disks/" + input.DiskId
  75. file, err := os.Open(disk.GetPath())
  76. if err != nil {
  77. return nil, errors.Wrap(err, "fail to open disk path")
  78. }
  79. defer file.Close()
  80. err = ds.Upload(ctx, remotePath, file)
  81. if err != nil {
  82. return nil, errors.Wrap(err, "dataStore.Upload")
  83. }
  84. as.RemoveDisk(disk)
  85. return diskMeta, nil
  86. }
  87. func (as *SAgentStorage) agentRebuildRoot(ctx context.Context, data jsonutils.JSONObject) error {
  88. type sRebuildParam struct {
  89. GuestExtId string
  90. SHostDatastore
  91. Desc struct {
  92. Disks []struct {
  93. DiskId string
  94. ImagePath string
  95. ImageInfo struct {
  96. ImageExternalId string
  97. }
  98. }
  99. }
  100. }
  101. rp := sRebuildParam{}
  102. err := data.Unmarshal(&rp)
  103. if err != nil {
  104. return errors.Wrap(err, hostutils.ParamsError.Error())
  105. }
  106. host, _, err := as.getHostAndDatastore(ctx, rp.SHostDatastore)
  107. if err != nil {
  108. return errors.Wrap(err, "as.getHostAndDatastore")
  109. }
  110. ivm, err := host.GetIVMById(rp.GuestExtId)
  111. if err != nil {
  112. return errors.Wrapf(err, "host.GetIVMById of id '%s'", rp.GuestExtId)
  113. }
  114. if len(rp.Desc.Disks) == 0 {
  115. return errors.Wrap(hostutils.ParamsError, "agentRebuildRoot data.desc.disks is empty")
  116. }
  117. imagePath := rp.Desc.Disks[0].ImagePath
  118. uefi := false
  119. var newPath string
  120. if len(imagePath) == 0 {
  121. vm, err := host.SearchTemplateVM(rp.Desc.Disks[0].ImageInfo.ImageExternalId)
  122. if err != nil {
  123. return errors.Wrapf(err, "SearchTemplateVM(%s)", rp.Desc.Disks[0].ImageInfo.ImageExternalId)
  124. }
  125. uefi = vm.GetBios() == cloudprovider.UEFI
  126. newPath, err = vm.GetRootImagePath()
  127. if err != nil {
  128. return errors.Wrapf(err, "GetRootImagePath")
  129. }
  130. } else {
  131. newPath, err = host.FileUrlPathToDsPath(imagePath)
  132. if err != nil {
  133. return err
  134. }
  135. }
  136. diskId := rp.Desc.Disks[0].DiskId
  137. vm := ivm.(*esxi.SVirtualMachine)
  138. return vm.DoRebuildRoot(ctx, newPath, diskId, uefi)
  139. }
  140. func (as *SAgentStorage) agentCreateGuest(ctx context.Context, data jsonutils.JSONObject) (bool, error) {
  141. hd := SHostDatastore{}
  142. err := data.Unmarshal(&hd)
  143. if err != nil {
  144. return false, errors.Wrap(err, hostutils.ParamsError.Error())
  145. }
  146. host, ds, err := as.getHostAndDatastore(ctx, hd)
  147. if err != nil {
  148. return false, err
  149. }
  150. desc, _ := data.Get("desc")
  151. descDict, ok := desc.(*jsonutils.JSONDict)
  152. if !ok {
  153. return false, errors.Wrap(hostutils.ParamsError, "agentCreateGuest data format error")
  154. }
  155. createParam := esxi.SCreateVMParam{}
  156. err = descDict.Unmarshal(&createParam)
  157. if err != nil {
  158. return false, errors.Wrapf(err, "%s: fail to unmarshal to esxi.SCreateVMParam", hostutils.ParamsError)
  159. }
  160. if !esxi_options.Options.EnableFolderNameUUID {
  161. createParam.Uuid = ""
  162. }
  163. needDeploy, vm, err := host.CreateVM2(ctx, ds, createParam)
  164. if err != nil {
  165. return false, errors.Wrap(err, "SHost.CreateVM2")
  166. }
  167. tags := map[string]string{}
  168. data.Unmarshal(&tags, "tags")
  169. if len(tags) > 0 {
  170. err = vm.SetTags(tags, true)
  171. if err != nil {
  172. log.Warningf("set tags for vm %s error: %v", createParam.Name, err)
  173. }
  174. }
  175. if esxi_options.Options.EnableFolderNameUUID && len(createParam.Uuid) > 0 {
  176. err = as.tryRenameVm(ctx, vm, createParam.Name)
  177. if err != nil {
  178. return false, errors.Wrapf(err, "RenameVm name '%s'", createParam.Name)
  179. }
  180. }
  181. return needDeploy, nil
  182. }
  183. func (as *SAgentStorage) tryRenameVm(ctx context.Context, vm *esxi.SVirtualMachine, name string) error {
  184. var (
  185. tried = 0
  186. err error
  187. )
  188. alterName := fmt.Sprintf("%s-%s", name, timeutils.ShortDate(time.Now()))
  189. cands := []string{name, alterName}
  190. for tried < 10 {
  191. var n string
  192. if tried < len(cands) {
  193. n = cands[tried]
  194. } else {
  195. n = fmt.Sprintf("%s-%d", alterName, tried-len(cands)+1)
  196. }
  197. tried += 1
  198. err = vm.DoRename(ctx, n)
  199. if err == nil {
  200. return nil
  201. }
  202. }
  203. return err
  204. }
  205. type esxiVm struct {
  206. UUID string
  207. Name string
  208. }
  209. func (self esxiVm) Keyword() string {
  210. return "server"
  211. }
  212. func (self esxiVm) GetId() string {
  213. return self.UUID
  214. }
  215. func (self esxiVm) GetName() string {
  216. return self.Name
  217. }
  218. type SHostDatastore struct {
  219. HostIp string
  220. Datastore vcenter.SVCenterAccessInfo
  221. }
  222. type SDeployInfo struct {
  223. Action string
  224. SHostDatastore
  225. Desc desc.SGuestDesc
  226. PublicKey string
  227. DeletePublicKey string
  228. AdminPublicKey string
  229. ProjectPublicKey string
  230. GuestExtId string
  231. GuestId string
  232. ResetPassword bool
  233. Password string
  234. EnableCloudInit bool
  235. LoginAccount string
  236. DeployTelegraf bool
  237. TelegrafConf string
  238. Deploys []struct {
  239. Path string
  240. Content string
  241. Action string
  242. }
  243. }
  244. func (as *SAgentStorage) AgentDeployGuest(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
  245. init := false
  246. dataDict := data.(*jsonutils.JSONDict)
  247. log.Debugf("dataDict: %s", dataDict)
  248. deployInfo := SDeployInfo{}
  249. err := dataDict.Unmarshal(&deployInfo)
  250. if err != nil {
  251. return nil, errors.Wrap(err, "Unmarshal DeployInfo")
  252. }
  253. var needDeploy = true
  254. switch deployInfo.Action {
  255. case "create":
  256. needDeploy, err = as.agentCreateGuest(ctx, dataDict)
  257. if err != nil {
  258. return nil, errors.Wrap(err, "agentCreateGuest")
  259. }
  260. init = true
  261. case "rebuild":
  262. err := as.agentRebuildRoot(ctx, dataDict)
  263. if err != nil {
  264. return nil, errors.Wrap(err, "agentRebuildRoot")
  265. }
  266. init = true
  267. }
  268. dc, info, err := esxi.NewESXiClientFromJson(ctx, jsonutils.Marshal(deployInfo.Datastore))
  269. if err != nil {
  270. return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
  271. }
  272. host, err := dc.FindHostByIp(deployInfo.HostIp)
  273. if err != nil {
  274. return nil, errors.Wrap(err, "SDatacenter.FindHostByIp")
  275. }
  276. vmId := deployInfo.GuestExtId
  277. if !esxi_options.Options.EnableFolderNameUUID && deployInfo.Action == "create" {
  278. vmId = deployInfo.Desc.Name
  279. }
  280. realHost := host
  281. ivm, err := host.GetIVMById(vmId)
  282. if err == cloudprovider.ErrNotFound {
  283. // reschedule by DRS, migrate to other host
  284. siblingHosts, err := host.GetSiblingHosts()
  285. if err != nil {
  286. return nil, errors.Wrap(err, "GetSiblingHosts")
  287. }
  288. for _, sh := range siblingHosts {
  289. ivm, err = host.GetIVMById(vmId)
  290. if err == nil {
  291. realHost = sh
  292. break
  293. }
  294. }
  295. }
  296. if err != nil {
  297. return nil, errors.Wrap(err, "SHost.GetIVMById")
  298. }
  299. if ivm == nil {
  300. return nil, errors.Error(fmt.Sprintf("no such vm '%s'", ivm.GetId()))
  301. }
  302. vm := ivm.(*esxi.SVirtualMachine)
  303. disks, err := vm.GetIDisks()
  304. if err != nil {
  305. return nil, errors.Wrap(err, "VM.GetIDisks")
  306. }
  307. if len(disks) == 0 {
  308. return nil, errors.Error(fmt.Sprintf("no such disks for vm %s", vm.GetId()))
  309. }
  310. vmref := vm.GetMoid()
  311. rootPath := disks[0].(*esxi.SVirtualDisk).GetFilename()
  312. log.Debugf("host: %s, port: %d, user: %s, passwd: %s", info.Host, info.Port, info.Account, info.Password)
  313. var deploy *deployapi.DeployGuestFsResponse
  314. if needDeploy {
  315. vddkInfo := deployapi.VDDKConInfo{
  316. Host: info.Host,
  317. Port: int32(info.Port),
  318. User: info.Account,
  319. Passwd: info.Password,
  320. Vmref: vmref,
  321. }
  322. guestDesc := deployapi.GuestDesc{}
  323. err = dataDict.Unmarshal(&guestDesc, "desc")
  324. if err != nil {
  325. return nil, errors.Wrapf(err, "%s: unmarshal to guestDesc", hostutils.ParamsError.Error())
  326. }
  327. guestDesc.Hypervisor = api.HYPERVISOR_ESXI
  328. key := deployapi.SSHKeys{
  329. PublicKey: deployInfo.PublicKey,
  330. DeletePublicKey: deployInfo.DeletePublicKey,
  331. AdminPublicKey: deployInfo.AdminPublicKey,
  332. ProjectPublicKey: deployInfo.ProjectPublicKey,
  333. }
  334. deployArray := make([]*deployapi.DeployContent, 0)
  335. for _, deploy := range deployInfo.Deploys {
  336. deployArray = append(deployArray, &deployapi.DeployContent{
  337. Path: deploy.Path,
  338. Content: deploy.Content,
  339. Action: deploy.Action,
  340. })
  341. }
  342. isRandomPassword := false
  343. passwd := deployInfo.Password
  344. resetPassword := deployInfo.ResetPassword
  345. if resetPassword && len(passwd) == 0 {
  346. passwd = seclib.RandomPassword(12)
  347. isRandomPassword = true
  348. }
  349. if deployInfo.DeployTelegraf && deployInfo.TelegrafConf == "" {
  350. return nil, errors.Errorf("missing telegraf_conf")
  351. }
  352. deployInformation := deployapi.NewDeployInfo(&key, deployArray, passwd, isRandomPassword, init, false,
  353. options.HostOptions.LinuxDefaultRootUser, options.HostOptions.WindowsDefaultAdminUser,
  354. deployInfo.EnableCloudInit, deployInfo.LoginAccount, deployInfo.DeployTelegraf, deployInfo.TelegrafConf,
  355. deployInfo.Desc.UserData,
  356. )
  357. log.Debugf("deployInfo: %s", jsonutils.Marshal(deployInfo))
  358. deploy, err = deployclient.GetDeployClient().DeployGuestFs(ctx, &deployapi.DeployParams{
  359. DiskInfo: &deployapi.DiskInfo{
  360. Path: rootPath,
  361. },
  362. GuestDesc: &guestDesc,
  363. DeployInfo: deployInformation,
  364. VddkInfo: &vddkInfo,
  365. })
  366. customize := false
  367. if err != nil {
  368. model := &esxiVm{}
  369. dataDict.Unmarshal(model, "desc")
  370. logclient.AddSimpleActionLog(model, logclient.ACT_VM_DEPLOY, errors.Wrapf(err, "DeployGuestFs"), hostutils.GetComputeSession(context.Background()).GetToken(), false)
  371. log.Errorf("unable to DeployGuestFs: %v", err)
  372. customize = true
  373. } else if deploy == nil {
  374. log.Errorf("unable to DeployGuestFs: deploy is nil")
  375. customize = true
  376. } else if len(deploy.Os) == 0 {
  377. log.Errorf("unable to DeployGuestFs: os is empty")
  378. customize = true
  379. }
  380. if customize {
  381. as.waitVmToolsVersion(ctx, vm)
  382. err = vm.DoCustomize(ctx, jsonutils.Marshal(deployInfo.Desc))
  383. if err != nil {
  384. log.Errorf("unable to DoCustomize for vm %s: %v", vm.GetId(), err)
  385. }
  386. }
  387. }
  388. array := jsonutils.NewArray()
  389. for _, d := range disks {
  390. disk := d.(*esxi.SVirtualDisk)
  391. diskId := disk.GetId()
  392. diskDict := jsonutils.NewDict()
  393. diskDict.Add(jsonutils.NewString(diskId), "disk_id")
  394. diskDict.Add(jsonutils.NewString(disk.GetGlobalId()), "uuid")
  395. diskDict.Add(jsonutils.NewInt(int64(disk.GetDiskSizeMB())), "size")
  396. diskDict.Add(jsonutils.NewString(disk.GetFilename()), "path")
  397. diskDict.Add(jsonutils.NewString(disk.GetCacheMode()), "cache_mode")
  398. diskDict.Add(jsonutils.NewString(disk.GetDiskType()), "disk_type")
  399. diskDict.Add(jsonutils.NewString(disk.GetDriver()), "driver")
  400. array.Add(diskDict)
  401. }
  402. updated := jsonutils.NewDict()
  403. updated.Add(array, "disks")
  404. updated.Add(jsonutils.NewString(vm.GetGlobalId()), "uuid")
  405. updated.Add(jsonutils.NewString(realHost.GetAccessIp()), "host_ip")
  406. var ret jsonutils.JSONObject
  407. if deploy != nil {
  408. ret = jsonutils.Marshal(deploy)
  409. } else {
  410. ret = jsonutils.NewDict()
  411. }
  412. ret.(*jsonutils.JSONDict).Update(updated)
  413. return ret, nil
  414. }
  415. func (as *SAgentStorage) waitVmToolsVersion(ctx context.Context, vm *esxi.SVirtualMachine) {
  416. timeout := 90 * time.Second
  417. timeUpper := time.Now().Add(timeout)
  418. for len(vm.GetToolsVersion()) == 0 && time.Now().Before(timeUpper) {
  419. if vm.GetStatus() != api.VM_RUNNING {
  420. vm.StartVM(ctx)
  421. time.Sleep(5 * time.Second)
  422. }
  423. }
  424. timeUpper = time.Now().Add(timeout)
  425. for vm.GetStatus() == api.VM_RUNNING && time.Now().Before(timeUpper) {
  426. opts := &cloudprovider.ServerStopOptions{
  427. IsForce: true,
  428. }
  429. vm.StopVM(ctx, opts)
  430. time.Sleep(5 * time.Second)
  431. }
  432. return
  433. }
  434. func (as *SAgentStorage) getHostAndDatastore(ctx context.Context, data SHostDatastore) (*esxi.SHost, *esxi.SDatastore, error) {
  435. client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore)
  436. if err != nil {
  437. return nil, nil, errors.Wrap(err, "fail to generate client")
  438. }
  439. host, err := client.FindHostByIp(data.HostIp)
  440. if err != nil {
  441. return nil, nil, errors.Wrap(err, "fail to find host")
  442. }
  443. ds, err := host.FindDataStoreById(data.Datastore.PrivateId)
  444. if err != nil {
  445. return nil, nil, errors.Wrapf(err, "fail to find datastore")
  446. }
  447. return host, ds, nil
  448. }
  449. func (as *SAgentStorage) isExist(dir string) bool {
  450. _, err := os.Stat(dir)
  451. if err != nil {
  452. if os.IsExist(err) {
  453. return true
  454. }
  455. return false
  456. }
  457. return true
  458. }
  459. func (as *SAgentStorage) checkDirC(dir string) error {
  460. if as.isExist(dir) {
  461. return nil
  462. }
  463. return os.MkdirAll(dir, 0770)
  464. }
  465. func (as *SAgentStorage) checkFileR(file string) error {
  466. if !as.isExist(file) {
  467. return nil
  468. }
  469. return os.Remove(file)
  470. }
  471. func (as *SAgentStorage) PrepareSaveToGlance(ctx context.Context, taskId string, diskInfo jsonutils.JSONObject) (
  472. ret jsonutils.JSONObject, err error) {
  473. type specStruct struct {
  474. Vm vcenter.SVCenterAccessInfo
  475. Disk vcenter.SVCenterAccessInfo
  476. HostIp string
  477. ImageId string
  478. }
  479. spec := specStruct{}
  480. err = diskInfo.Unmarshal(&spec)
  481. if err != nil {
  482. return nil, errors.Wrap(hostutils.ParamsError, err.Error())
  483. }
  484. destDir := as.GetImgsaveBackupPath()
  485. as.checkDirC(destDir)
  486. backupPath := filepath.Join(destDir, fmt.Sprintf("%s.%s", spec.Vm.PrivateId, taskId))
  487. client, err := esxi.NewESXiClientFromAccessInfo(ctx, &spec.Vm)
  488. if err != nil {
  489. return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson")
  490. }
  491. host, err := client.FindHostByIp(spec.HostIp)
  492. if err != nil {
  493. return nil, errors.Wrapf(err, "ESXiClient.FindHostByIp of ip '%s'", spec.HostIp)
  494. }
  495. ivm, err := host.GetIVMById(spec.Vm.PrivateId)
  496. if err != nil {
  497. return nil, errors.Wrapf(err, "esxi.SHost.GetIVMById for '%s'", spec.Vm.PrivateId)
  498. }
  499. vm := ivm.(*esxi.SVirtualMachine)
  500. idisk, err := vm.GetIDiskById(spec.Disk.PrivateId)
  501. if err != nil {
  502. return nil, errors.Wrapf(err, "esxi.SVirtualMachine for '%s'", spec.Disk.PrivateId)
  503. }
  504. disk := idisk.(*esxi.SVirtualDisk)
  505. log.Infof("Export VM image to %s", backupPath)
  506. err = vm.ExportTemplate(ctx, disk.GetIndex(), backupPath)
  507. if err != nil {
  508. return nil, errors.Wrap(err, "VM.ExportTemplate")
  509. }
  510. dict := jsonutils.NewDict()
  511. dict.Add(jsonutils.NewString(backupPath), "backup")
  512. return dict, nil
  513. }
  514. func (as *SAgentStorage) SaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  515. data, ok := params.(jsonutils.JSONObject)
  516. if !ok {
  517. return nil, hostutils.ParamsError
  518. }
  519. var (
  520. imageId, _ = data.GetString("image_id")
  521. imagePath, _ = data.GetString("image_path")
  522. compress = jsonutils.QueryBoolean(data, "compress", true)
  523. format, _ = data.GetString("format")
  524. )
  525. log.Debugf("image path: %s", imagePath)
  526. if err := as.saveToGlance(ctx, imageId, imagePath, compress, format); err != nil {
  527. log.Errorf("Save to glance failed: %s", err)
  528. as.onSaveToGlanceFailed(ctx, imageId, err.Error())
  529. return nil, err
  530. }
  531. // delete the backup image
  532. as.checkFileR(imagePath)
  533. imagecacheManager := as.Manager.LocalStorageImagecacheManager
  534. if len(imagecacheManager.GetId()) > 0 {
  535. err := procutils.NewCommand("rm", "-f", imagePath).Run()
  536. return nil, err
  537. } else {
  538. dstPath := path.Join(imagecacheManager.GetPath(), imageId)
  539. if err := procutils.NewCommand("mv", imagePath, dstPath).Run(); err != nil {
  540. log.Errorf("Fail to move saved image to cache: %s", err)
  541. }
  542. imagecacheManager.LoadImageCache(imageId)
  543. _, err := hostutils.RemoteStoragecacheCacheImage(ctx,
  544. imagecacheManager.GetId(), imageId, "active", dstPath)
  545. if err != nil {
  546. log.Errorf("Fail to remote cache image: %s", err)
  547. }
  548. }
  549. return nil, nil
  550. }
  551. func (as *SAgentStorage) saveToGlance(ctx context.Context, imageId, imagePath string,
  552. compress bool, format string) error {
  553. diskInfo := &deployapi.DiskInfo{
  554. Path: imagePath,
  555. }
  556. ret, err := deployclient.GetDeployClient().SaveToGlance(context.Background(),
  557. &deployapi.SaveToGlanceParams{DiskInfo: diskInfo, Compress: compress})
  558. if err != nil {
  559. return errors.Wrap(err, "DeployClient.SaveToGlance")
  560. }
  561. if compress {
  562. origin, err := qemuimg.NewQemuImage(imagePath)
  563. if err != nil {
  564. log.Errorln(err)
  565. return errors.Wrap(err, "qemuimg.NewQemuImage")
  566. }
  567. if len(format) == 0 {
  568. format = options.HostOptions.DefaultImageSaveFormat
  569. }
  570. if format == string(qemuimgfmt.QCOW2) {
  571. // may be encrypted
  572. if err := origin.Convert2Qcow2(true, "", "", ""); err != nil {
  573. log.Errorln(err)
  574. return err
  575. }
  576. } else {
  577. if err := origin.Convert2Vmdk(true); err != nil {
  578. log.Errorln(err)
  579. return err
  580. }
  581. }
  582. }
  583. f, err := os.Open(imagePath)
  584. if err != nil {
  585. return err
  586. }
  587. defer f.Close()
  588. finfo, err := f.Stat()
  589. if err != nil {
  590. return err
  591. }
  592. size := finfo.Size()
  593. var params = jsonutils.NewDict()
  594. if len(ret.OsInfo) > 0 {
  595. params.Set("os_type", jsonutils.NewString(ret.OsInfo))
  596. }
  597. releaseInfoToParams(ret.ReleaseInfo, params)
  598. params.Set("image_id", jsonutils.NewString(imageId))
  599. _, err = modules.Images.Upload(hostutils.GetImageSession(ctx),
  600. params, f, size)
  601. if err != nil {
  602. return errors.Wrap(err, "Images.Upload")
  603. }
  604. return nil
  605. }