storage_local.go 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  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. "io/ioutil"
  19. "math"
  20. "os"
  21. "path"
  22. "path/filepath"
  23. "strings"
  24. "time"
  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/timeutils"
  30. "yunion.io/x/onecloud/pkg/apis"
  31. api "yunion.io/x/onecloud/pkg/apis/compute"
  32. hostapi "yunion.io/x/onecloud/pkg/apis/host"
  33. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  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/hostutils/kubelet"
  39. "yunion.io/x/onecloud/pkg/hostman/options"
  40. "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
  41. "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile"
  42. "yunion.io/x/onecloud/pkg/httperrors"
  43. "yunion.io/x/onecloud/pkg/mcclient/auth"
  44. modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  45. identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  46. "yunion.io/x/onecloud/pkg/mcclient/modules/image"
  47. "yunion.io/x/onecloud/pkg/util/fileutils2"
  48. "yunion.io/x/onecloud/pkg/util/procutils"
  49. "yunion.io/x/onecloud/pkg/util/qemuimg"
  50. "yunion.io/x/onecloud/pkg/util/seclib2"
  51. "yunion.io/x/onecloud/pkg/util/zeroclean"
  52. )
  53. var (
  54. _FUSE_MOUNT_PATH_ = "fusemnt"
  55. _FUSE_TMP_PATH_ = "fusetmp"
  56. )
  57. type SLocalStorage struct {
  58. SBaseStorage
  59. Index int
  60. }
  61. func NewLocalStorage(manager *SStorageManager, path string, index int) *SLocalStorage {
  62. var ret = new(SLocalStorage)
  63. ret.SBaseStorage = *NewBaseStorage(manager, path)
  64. ret.Index = index
  65. return ret
  66. }
  67. func (s *SLocalStorage) StorageType() string {
  68. return api.STORAGE_LOCAL
  69. }
  70. func (s *SLocalStorage) IsLocal() bool {
  71. return true
  72. }
  73. func (s *SLocalStorage) GetSnapshotDir() string {
  74. return path.Join(s.Path, _SNAPSHOT_PATH_)
  75. }
  76. func (s *SLocalStorage) GetSnapshotPathByIds(diskId, snapshotId string) string {
  77. return path.Join(s.GetSnapshotDir(), diskId+options.HostOptions.SnapshotDirSuffix, snapshotId)
  78. }
  79. func (s *SLocalStorage) IsSnapshotExist(diskId, snapshotId string) (bool, error) {
  80. return fileutils2.Exists(s.GetSnapshotPathByIds(diskId, snapshotId)), nil
  81. }
  82. func (s *SLocalStorage) GetComposedName() string {
  83. return fmt.Sprintf("host_%s_%s_storage_%d", s.Manager.host.GetMasterIp(), s.StorageType(), s.Index)
  84. }
  85. func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
  86. err := doRestoreDisk(ctx, s, input, disk, disk.GetPath())
  87. if err != nil {
  88. return errors.Wrap(err, "doRestoreDisk")
  89. }
  90. /*info := input.DiskInfo
  91. backupDir := s.GetBackupDir()
  92. if !fileutils2.Exists(backupDir) {
  93. output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output()
  94. if err != nil {
  95. return errors.Wrapf(err, "mkdir %s failed: %s", backupDir, output)
  96. }
  97. }
  98. backupPath := path.Join(s.GetBackupDir(), info.Backup.BackupId)
  99. if !fileutils2.Exists(backupPath) {
  100. _, err := s.storageBackupRecovery(ctx, &SStorageBackup{
  101. BackupId: input.DiskInfo.Backup.BackupId,
  102. BackupStorageId: input.DiskInfo.Backup.BackupStorageId,
  103. BackupStorageAccessInfo: input.DiskInfo.Backup.BackupStorageAccessInfo.Copy(),
  104. })
  105. if err != nil {
  106. return errors.Wrap(err, "unable to storageBackupRecovery")
  107. }
  108. }*/
  109. /*img, err := qemuimg.NewQemuImage(backupPath)
  110. if err != nil {
  111. log.Errorf("unable to new qemu image for %s: %s", backupPath, err.Error())
  112. return errors.Wrapf(err, "unable to new qemu image for %s", backupPath)
  113. }
  114. if info.Encryption {
  115. img.SetPassword(info.EncryptInfo.Key)
  116. }
  117. _, err = img.Clone(disk.GetLvPath(), qemuimg.QCOW2, false)*/
  118. /*img, err := qemuimg.NewQemuImage(disk.GetPath())
  119. if err != nil {
  120. log.Errorf("NewQemuImage fail %s %s", disk.GetPath(), err)
  121. return errors.Wrapf(err, "unable to new qemu image for %s", disk.GetPath())
  122. }
  123. var (
  124. encKey string
  125. encFmt qemuimg.TEncryptFormat
  126. encAlg seclib2.TSymEncAlg
  127. )
  128. if info.Encryption {
  129. encKey = info.EncryptInfo.Key
  130. encFmt = qemuimg.EncryptFormatLuks
  131. encAlg = info.EncryptInfo.Alg
  132. }
  133. err = img.CreateQcow2(0, false, backupPath, encKey, encFmt, encAlg)
  134. if err != nil {
  135. log.Errorf("CreateQcow2 fail %s", err)
  136. return errors.Wrapf(err, "CreateQcow2 %s fail", backupPath)
  137. }*/
  138. return nil
  139. }
  140. /*func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  141. sbParams := params.(*SStorageBackup)
  142. backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
  143. if err != nil {
  144. return nil, err
  145. }
  146. backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId)
  147. err = backupStorage.SaveBackupFrom(ctx, backupPath, sbParams.BackupId)
  148. if err != nil {
  149. return nil, err
  150. }
  151. // remove local backup
  152. output, err := procutils.NewCommand("rm", backupPath).Output()
  153. if err != nil {
  154. log.Errorf("rm %s failed %s", backupPath, output)
  155. return nil, errors.Wrapf(err, "rm %s failed %s", backupPath, output)
  156. }
  157. return nil, nil
  158. }*/
  159. func (s *SLocalStorage) storageBackupRecovery(ctx context.Context, sbParams *SStorageBackup) (jsonutils.JSONObject, error) {
  160. backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
  161. if err != nil {
  162. return nil, err
  163. }
  164. backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId)
  165. return nil, backupStorage.RestoreBackupTo(ctx, backupPath, sbParams.BackupId)
  166. }
  167. func (s *SLocalStorage) StorageBackupRecovery(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  168. sbParams := params.(*SStorageBackup)
  169. return s.storageBackupRecovery(ctx, sbParams)
  170. }
  171. func (s *SLocalStorage) GetAvailSizeMb() int {
  172. sizeMb := s.SBaseStorage.GetAvailSizeMb()
  173. kubeletConf := s.Manager.GetKubeletConfig()
  174. if kubeletConf == nil {
  175. return sizeMb
  176. }
  177. // available size should aware of kubelet hard eviction threshold
  178. hardThresholds := kubeletConf.GetEvictionConfig().GetHard()
  179. nodeFs := hardThresholds.GetNodeFsAvailable()
  180. imageFs := hardThresholds.GetImageFsAvailable()
  181. storageDev, err := kubelet.GetDirectoryMountDevice(s.GetPath())
  182. if err != nil {
  183. log.Errorf("Get directory %s mount device: %v", s.GetPath(), err)
  184. return sizeMb
  185. }
  186. usablePercent := 1.0
  187. if kubeletConf.HasDedicatedImageFs() {
  188. if storageDev == kubeletConf.GetImageFsDevice() {
  189. usablePercent = 1 - float64(imageFs.Value.Percentage)
  190. log.Infof("Storage %s and kubelet imageFs %s share same device %s", s.GetPath(), kubeletConf.GetImageFs(), storageDev)
  191. }
  192. } else {
  193. // nodeFs and imageFs use same device
  194. if storageDev == kubeletConf.GetNodeFsDevice() {
  195. maxPercent := math.Max(float64(nodeFs.Value.Percentage), float64(imageFs.Value.Percentage))
  196. usablePercent = 1 - maxPercent
  197. log.Infof("Storage %s and kubelet nodeFs share same device %s", s.GetPath(), storageDev)
  198. }
  199. }
  200. sizeMb = int(float64(sizeMb) * usablePercent)
  201. log.Infof("Storage %s sizeMb %d, usablePercent %f", s.GetPath(), sizeMb, usablePercent)
  202. return sizeMb
  203. }
  204. func (s *SLocalStorage) GetMediumType() (string, error) {
  205. out, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", fmt.Sprintf("lsblk -d -o name,rota $(df -P %s | awk 'NR==2{print $1}') | awk 'NR==2{print $2}'", s.GetPath())).Output()
  206. if err != nil {
  207. return api.DISK_TYPE_ROTATE, errors.Wrapf(err, "failed get medium type %s", out)
  208. }
  209. if strings.TrimSpace(string(out)) == "0" {
  210. return api.DISK_TYPE_SSD, nil
  211. } else {
  212. return api.DISK_TYPE_ROTATE, nil
  213. }
  214. }
  215. func (s *SLocalStorage) getHardwareInfo() (*api.StorageHardwareInfo, error) {
  216. cmd := fmt.Sprintf("df -P %s | awk 'NR==2 {print $1}'", s.GetPath())
  217. partName, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output()
  218. if err != nil {
  219. return nil, errors.Wrapf(err, "execute command: %s", cmd)
  220. }
  221. partPath := strings.TrimSuffix(string(partName), "\n")
  222. sysPath := "/sys/class/block"
  223. getPartPathCmd := fmt.Sprintf("readlink -f %s", filepath.Join(sysPath, filepath.Base(partPath)))
  224. partRealPath, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", getPartPathCmd).Output()
  225. if err != nil {
  226. return nil, errors.Wrapf(err, "get partition sys path: %s", getPartPathCmd)
  227. }
  228. partRealPathStr := strings.TrimSuffix(string(partRealPath), "\n")
  229. blockPath := filepath.Dir(partRealPathStr)
  230. devicePath := filepath.Join(blockPath, "device")
  231. modelPath := filepath.Join(devicePath, "model")
  232. errs := []error{}
  233. ret := &api.StorageHardwareInfo{}
  234. model, err := ioutil.ReadFile(modelPath)
  235. if err != nil {
  236. errs = append(errs, errors.Wrapf(err, "read model file: %s", modelPath))
  237. } else {
  238. modelStr := string(model)
  239. ret.Model = &modelStr
  240. }
  241. vendorPath := filepath.Join(devicePath, "vendor")
  242. vendor, err := ioutil.ReadFile(vendorPath)
  243. if err != nil {
  244. errs = append(errs, errors.Wrapf(err, "read vendor file: %s", vendorPath))
  245. } else {
  246. vendorStr := string(vendor)
  247. ret.Vendor = &vendorStr
  248. }
  249. return ret, errors.NewAggregate(errs)
  250. }
  251. func (s *SLocalStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
  252. content := jsonutils.NewDict()
  253. name := s.GetName(s.GetComposedName)
  254. content.Set("name", jsonutils.NewString(name))
  255. content.Set("capacity", jsonutils.NewInt(int64(s.GetAvailSizeMb())))
  256. content.Set("actual_capacity_used", jsonutils.NewInt(int64(s.GetUsedSizeMb())))
  257. content.Set("storage_type", jsonutils.NewString(s.StorageType()))
  258. content.Set("zone", jsonutils.NewString(s.GetZoneId()))
  259. if len(s.Manager.LocalStorageImagecacheManager.GetId()) > 0 {
  260. content.Set("storagecache_id",
  261. jsonutils.NewString(s.Manager.LocalStorageImagecacheManager.GetId()))
  262. }
  263. hardwareInfo, err := s.getHardwareInfo()
  264. if err != nil {
  265. log.Warningf("get hardware info: storage: %s, %v", name, err)
  266. }
  267. if hardwareInfo != nil {
  268. content.Set("hardware_info", jsonutils.Marshal(hardwareInfo))
  269. }
  270. var (
  271. res jsonutils.JSONObject
  272. )
  273. log.Infof("Sync storage info %s/%s", s.StorageId, name)
  274. if len(s.StorageId) > 0 {
  275. res, err = modules.Storages.Put(
  276. hostutils.GetComputeSession(context.Background()),
  277. s.StorageId, content)
  278. if err != nil {
  279. log.Errorf("SyncStorageInfo Failed: %s: %s", content, err)
  280. return nil, errors.Wrapf(err, "Storages.Put %s", s.StorageId)
  281. }
  282. } else {
  283. res, err = modules.Storages.GetByName(hostutils.GetComputeSession(context.Background()), name, nil)
  284. if err == nil {
  285. return res, nil
  286. }
  287. var mediumType string
  288. mediumType, err = s.GetMediumType()
  289. if err != nil {
  290. log.Errorf("failed get medium type %s %s", s.GetPath(), err)
  291. } else {
  292. content.Set("medium_type", jsonutils.NewString(mediumType))
  293. }
  294. res, err = modules.Storages.Create(
  295. hostutils.GetComputeSession(context.Background()), content)
  296. if err != nil {
  297. log.Errorf("SyncStorageInfo Failed: %s: %s", content, err)
  298. return nil, errors.Wrapf(err, "Storages.Create %s", content)
  299. }
  300. }
  301. return res, nil
  302. }
  303. func (s *SLocalStorage) GetDiskById(diskId string) (IDisk, error) {
  304. s.DiskLock.Lock()
  305. defer s.DiskLock.Unlock()
  306. for i := 0; i < len(s.Disks); i++ {
  307. if s.Disks[i].GetId() == diskId {
  308. return s.Disks[i], s.Disks[i].Probe()
  309. }
  310. }
  311. var disk = NewLocalDisk(s, diskId)
  312. err := disk.Probe()
  313. if err == nil {
  314. s.Disks = append(s.Disks, disk)
  315. return disk, nil
  316. }
  317. return nil, errors.Wrapf(errors.ErrNotFound, "probe: %s", err)
  318. }
  319. func (s *SLocalStorage) CreateDisk(diskId string) IDisk {
  320. s.DiskLock.Lock()
  321. defer s.DiskLock.Unlock()
  322. disk := NewLocalDisk(s, diskId)
  323. s.Disks = append(s.Disks, disk)
  324. return disk
  325. }
  326. func (s *SLocalStorage) Accessible() error {
  327. var c = make(chan error)
  328. go func() {
  329. if !fileutils2.Exists(s.Path) {
  330. if err := procutils.NewCommand("mkdir", "-p", s.Path).Run(); err != nil {
  331. c <- err
  332. return
  333. }
  334. }
  335. if !fileutils2.IsDir(s.Path) {
  336. c <- fmt.Errorf("path %s isn't directory", s.Path)
  337. return
  338. }
  339. if err := s.BindMountStoragePath(s.Path); err != nil {
  340. c <- err
  341. return
  342. }
  343. if !fileutils2.Writable(s.Path) {
  344. c <- fmt.Errorf("dir %s not writable", s.Path)
  345. return
  346. }
  347. c <- nil
  348. }()
  349. var err error
  350. select {
  351. case err = <-c:
  352. break
  353. case <-time.After(time.Second * 10):
  354. err = ErrStorageTimeout
  355. }
  356. return err
  357. }
  358. func (s *SLocalStorage) Detach() error {
  359. return nil
  360. }
  361. func (s *SLocalStorage) deleteBackendFile(diskpath string, skipRecycle bool) error {
  362. backendPath := diskpath + ".backend"
  363. if !fileutils2.Exists(backendPath) {
  364. return nil
  365. }
  366. disk, err := qemuimg.NewQemuImage(diskpath)
  367. if err != nil {
  368. return errors.Wrapf(err, "qemuimg.NewQemuImage(%s)", diskpath)
  369. }
  370. if disk.BackFilePath != backendPath {
  371. return nil
  372. }
  373. destDir := s.getRecyclePath()
  374. if options.HostOptions.RecycleDiskfile && (!skipRecycle || options.HostOptions.AlwaysRecycleDiskfile) {
  375. if err := procutils.NewCommand("mkdir", "-p", destDir).Run(); err != nil {
  376. log.Errorf("Fail to mkdir %s for recycle: %s", destDir, err)
  377. return err
  378. }
  379. backendDestFile := fmt.Sprintf("%s.%d", path.Base(backendPath), time.Now().Unix())
  380. log.Infof("Move deleted disk file %s to recycle %s", backendPath, destDir)
  381. return procutils.NewCommand("mv", "-f", backendPath, path.Join(destDir, backendDestFile)).Run()
  382. } else {
  383. log.Infof("Delete disk file %s immediately", backendPath)
  384. if options.HostOptions.ZeroCleanDiskData {
  385. // try to zero clean files in subdir
  386. err := zeroclean.ZeroDir(backendPath)
  387. if err != nil {
  388. log.Errorf("zeroclean disk %s fail %s", backendPath, err)
  389. } else {
  390. log.Debugf("zeroclean disk %s success!", backendPath)
  391. }
  392. }
  393. return procutils.NewCommand("rm", "-rf", backendPath).Run()
  394. }
  395. }
  396. func (s *SLocalStorage) DeleteDiskfile(diskpath string, skipRecycle bool) error {
  397. log.Infof("Start Delete %s", diskpath)
  398. if err := s.deleteBackendFile(diskpath, skipRecycle); err != nil {
  399. return err
  400. }
  401. if options.HostOptions.RecycleDiskfile && (!skipRecycle || options.HostOptions.AlwaysRecycleDiskfile) {
  402. var (
  403. destDir = s.getRecyclePath()
  404. destFile = fmt.Sprintf("%s.%d", path.Base(diskpath), time.Now().Unix())
  405. )
  406. if err := procutils.NewCommand("mkdir", "-p", destDir).Run(); err != nil {
  407. log.Errorf("Fail to mkdir %s for recycle: %s", destDir, err)
  408. return err
  409. }
  410. log.Infof("Move deleted disk file %s to recycle %s", diskpath, destDir)
  411. return procutils.NewCommand("mv", "-f", diskpath, path.Join(destDir, destFile)).Run()
  412. } else {
  413. log.Infof("Delete disk file %s immediately", diskpath)
  414. if options.HostOptions.ZeroCleanDiskData {
  415. // try to zero clean files in subdir
  416. err := zeroclean.ZeroDir(diskpath)
  417. if err != nil {
  418. log.Errorf("zeroclean disk %s fail %s", diskpath, err)
  419. } else {
  420. log.Debugf("zeroclean disk %s success!", diskpath)
  421. }
  422. }
  423. return procutils.NewCommand("rm", "-rf", diskpath).Run()
  424. }
  425. }
  426. func (s *SLocalStorage) getRecyclePath() string {
  427. return s.getSubdirPath(_RECYCLE_BIN_)
  428. }
  429. func (s *SLocalStorage) getSubdirPath(subdir string) string {
  430. spath := path.Join(s.Path, subdir)
  431. today := timeutils.CompactTime(time.Now())
  432. return path.Join(spath, today)
  433. }
  434. func (s *SLocalStorage) GetImgsaveBackupPath() string {
  435. return s.getSubdirPath(_IMGSAVE_BACKUPS_)
  436. }
  437. func (s *SLocalStorage) SaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  438. info, ok := params.(SStorageSaveToGlanceInfo)
  439. if !ok {
  440. return nil, hostutils.ParamsError
  441. }
  442. data := info.DiskInfo
  443. var (
  444. imageId, _ = data.GetString("image_id")
  445. imagePath, _ = data.GetString("image_path")
  446. compress = jsonutils.QueryBoolean(data, "compress", true)
  447. format, _ = data.GetString("format")
  448. encKeyId, _ = data.GetString("encrypt_key_id")
  449. )
  450. var (
  451. encKey string
  452. encFormat qemuimg.TEncryptFormat
  453. encAlg seclib2.TSymEncAlg
  454. )
  455. if len(encKeyId) > 0 {
  456. session := auth.GetSession(ctx, info.UserCred, consts.GetRegion())
  457. key, err := identity_modules.Credentials.GetEncryptKey(session, encKeyId)
  458. if err != nil {
  459. return nil, errors.Wrap(err, "GetEncryptKey")
  460. }
  461. encKey = key.Key
  462. encFormat = qemuimg.EncryptFormatLuks
  463. encAlg = key.Alg
  464. }
  465. if err := s.saveToGlance(ctx, imageId, imagePath, compress, format, encKey, encFormat, encAlg); err != nil {
  466. log.Errorf("Save to glance failed: %s", err)
  467. s.onSaveToGlanceFailed(ctx, imageId, err.Error())
  468. return nil, errors.Wrap(err, "saveToGlance")
  469. }
  470. imagecacheManager := s.Manager.LocalStorageImagecacheManager
  471. if len(imagecacheManager.GetId()) > 0 {
  472. return nil, procutils.NewCommand("rm", "-f", imagePath).Run()
  473. } else {
  474. dstPath := path.Join(imagecacheManager.GetPath(), imageId)
  475. if err := procutils.NewCommand("mv", imagePath, dstPath).Run(); err != nil {
  476. log.Errorf("Fail to move saved image to cache: %s", err)
  477. }
  478. imagecacheManager.LoadImageCache(imageId)
  479. _, err := hostutils.RemoteStoragecacheCacheImage(ctx,
  480. imagecacheManager.GetId(), imageId, "active", dstPath)
  481. if err != nil {
  482. log.Errorf("Fail to remote cache image: %s", err)
  483. }
  484. }
  485. return nil, nil
  486. }
  487. func (s *SLocalStorage) saveToGlance(ctx context.Context, imageId, imagePath string,
  488. compress bool, format string, encryptKey string, encFormat qemuimg.TEncryptFormat, encAlg seclib2.TSymEncAlg) error {
  489. log.Infof("saveToGlance %s", imagePath)
  490. diskInfo := &deployapi.DiskInfo{
  491. Path: imagePath,
  492. }
  493. if len(encryptKey) > 0 {
  494. diskInfo.EncryptPassword = encryptKey
  495. diskInfo.EncryptFormat = string(encFormat)
  496. diskInfo.EncryptAlg = string(encAlg)
  497. }
  498. ret, err := deployclient.GetDeployClient().SaveToGlance(ctx,
  499. &deployapi.SaveToGlanceParams{DiskInfo: diskInfo, Compress: compress})
  500. if err != nil {
  501. log.Errorf("GetDeployClient.SaveToGlance fail %s", err)
  502. return err
  503. }
  504. if compress {
  505. origin, err := qemuimg.NewQemuImage(imagePath)
  506. if err != nil {
  507. log.Errorln(err)
  508. return err
  509. }
  510. if len(encryptKey) > 0 {
  511. origin.SetPassword(encryptKey)
  512. }
  513. if len(format) == 0 {
  514. format = options.HostOptions.DefaultImageSaveFormat
  515. }
  516. if format == "qcow2" {
  517. if err := origin.Convert2Qcow2(true, encryptKey, encFormat, encAlg); err != nil {
  518. log.Errorln(err)
  519. return err
  520. }
  521. } else {
  522. if err := origin.Convert2Vmdk(true); err != nil {
  523. log.Errorln(err)
  524. return err
  525. }
  526. }
  527. }
  528. f, err := os.Open(imagePath)
  529. if err != nil {
  530. return err
  531. }
  532. defer f.Close()
  533. finfo, err := f.Stat()
  534. if err != nil {
  535. return err
  536. }
  537. size := finfo.Size()
  538. var params = jsonutils.NewDict()
  539. if len(ret.OsInfo) > 0 {
  540. params.Set("os_type", jsonutils.NewString(ret.OsInfo))
  541. }
  542. releaseInfoToParams(ret.ReleaseInfo, params)
  543. params.Set("image_id", jsonutils.NewString(imageId))
  544. _, err = image.Images.Upload(hostutils.GetImageSession(ctx),
  545. params, f, size)
  546. return err
  547. }
  548. func (s *SLocalStorage) onSaveToGlanceFailed(ctx context.Context, imageId string, reason string) {
  549. params := jsonutils.NewDict()
  550. params.Set("status", jsonutils.NewString("killed"))
  551. params.Set("reason", jsonutils.NewString(reason))
  552. _, err := image.Images.PerformAction(
  553. hostutils.GetImageSession(ctx),
  554. imageId, "update-status", params,
  555. )
  556. if err != nil {
  557. log.Errorln(err)
  558. }
  559. }
  560. func (s *SLocalStorage) CreateSnapshotFormUrl(
  561. ctx context.Context, snapshotUrl, diskId, snapshotPath string,
  562. ) error {
  563. remoteFile := remotefile.NewRemoteFile(ctx, snapshotUrl, snapshotPath,
  564. false, "", -1, nil, "", "")
  565. err := remoteFile.Fetch(nil)
  566. return errors.Wrapf(err, "fetch snapshot from %s", snapshotUrl)
  567. }
  568. func (s *SLocalStorage) DeleteSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  569. input, ok := params.(*SStorageDeleteSnapshots)
  570. if !ok {
  571. return nil, hostutils.ParamsError
  572. }
  573. snapshotDir := path.Join(s.GetSnapshotDir(), input.DiskId+options.HostOptions.SnapshotDirSuffix)
  574. output, err := procutils.NewCommand("rm", "-rf", snapshotDir).Output()
  575. if err != nil {
  576. return nil, fmt.Errorf("Delete snapshot dir failed: %s", output)
  577. }
  578. return nil, nil
  579. }
  580. func (s *SLocalStorage) DeleteSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  581. input, ok := params.(*SStorageDeleteSnapshot)
  582. if !ok {
  583. return nil, hostutils.ParamsError
  584. }
  585. log.Errorf("input %s", jsonutils.Marshal(input))
  586. snapshotDir := path.Join(s.GetSnapshotDir(), input.DiskId+options.HostOptions.SnapshotDirSuffix)
  587. diskPath := path.Join(s.GetPath(), input.DiskId)
  588. err := DeleteLocalSnapshot(snapshotDir, input.SnapshotId, diskPath, input.ConvertSnapshot, input.BlockStream)
  589. if err != nil {
  590. return nil, err
  591. }
  592. res := jsonutils.NewDict()
  593. res.Set("deleted", jsonutils.JSONTrue)
  594. return res, nil
  595. }
  596. func DeleteLocalSnapshot(snapshotDir, snapshotId, diskPath, convertSnapshot string, blockStream bool) error {
  597. //snapshotDir := d.GetSnapshotDir()
  598. snapshotPath := path.Join(snapshotDir, snapshotId)
  599. if blockStream {
  600. //diskPath := d.getPath()
  601. output := diskPath + ".tmp"
  602. if fileutils2.Exists(output) {
  603. procutils.NewCommand("rm", "-f", output).Run()
  604. }
  605. img, err := qemuimg.NewQemuImage(diskPath)
  606. if err != nil {
  607. return errors.Wrap(err, "NewQemuImage")
  608. }
  609. if err = img.Convert2Qcow2To(output, false, "", "", ""); err != nil {
  610. log.Errorf("convert image %s to %s: %s", img.Path, output, err)
  611. procutils.NewCommand("rm", "-f", output).Run()
  612. return err
  613. }
  614. if err = procutils.NewCommand("rm", "-f", diskPath).Run(); err != nil {
  615. log.Errorf("rm convert disk file %s: %s", diskPath, err)
  616. return err
  617. }
  618. if err = procutils.NewCommand("mv", "-f", output, diskPath).Run(); err != nil {
  619. log.Errorf("mv disk file %s to %s: %s", output, diskPath, err)
  620. return err
  621. }
  622. } else if len(convertSnapshot) > 0 {
  623. if !fileutils2.Exists(snapshotDir) {
  624. err := procutils.NewCommand("mkdir", "-p", snapshotDir).Run()
  625. if err != nil {
  626. log.Errorln(err)
  627. return err
  628. }
  629. }
  630. convertSnapshotPath := path.Join(snapshotDir, convertSnapshot)
  631. output := convertSnapshotPath + ".tmp"
  632. if fileutils2.Exists(output) {
  633. procutils.NewCommand("rm", "-f", output).Run()
  634. }
  635. img, err := qemuimg.NewQemuImage(convertSnapshotPath)
  636. if err != nil {
  637. return errors.Wrap(err, "NewQemuImage")
  638. }
  639. if err = img.Convert2Qcow2To(output, false, "", "", ""); err != nil {
  640. log.Errorf("convert image %s to %s: %s", img.Path, output, err)
  641. procutils.NewCommand("rm", "-f", output).Run()
  642. return err
  643. }
  644. if err = procutils.NewCommand("rm", "-f", convertSnapshotPath).Run(); err != nil {
  645. log.Errorf("rm convert snapshot file %s: %s", convertSnapshotPath, err)
  646. return err
  647. }
  648. if err = procutils.NewCommand("mv", "-f", output, convertSnapshotPath).Run(); err != nil {
  649. log.Errorf("mv snapshot file %s to %s: %s", output, convertSnapshotPath, err)
  650. return err
  651. }
  652. }
  653. if fileutils2.Exists(snapshotPath) {
  654. out, err := procutils.NewCommand("rm", "-f", snapshotPath).Output()
  655. if err != nil {
  656. log.Errorf("rm snapshot file: %s %s", out, err)
  657. return errors.Wrap(err, "rm snapshot file")
  658. }
  659. }
  660. return nil
  661. }
  662. func (s *SLocalStorage) DestinationPrepareMigrate(
  663. ctx context.Context, liveMigrate bool, disksUri string, snapshotsUri string,
  664. disksBackingFile, diskSnapsChain, outChainSnaps jsonutils.JSONObject,
  665. rebaseDisks bool,
  666. diskinfo *desc.SGuestDisk,
  667. serverId string, idx, totalDiskCount int,
  668. encInfo *apis.SEncryptInfo, sysDiskHasTemplate bool,
  669. ) error {
  670. var (
  671. diskId = diskinfo.DiskId
  672. snapshots, _ = diskSnapsChain.GetArray(diskId)
  673. disk = s.CreateDisk(diskId)
  674. diskOutChainSnaps, _ = outChainSnaps.GetArray(diskId)
  675. )
  676. if disk == nil {
  677. return fmt.Errorf(
  678. "Storage %s create disk %s failed", s.GetId(), diskId)
  679. }
  680. templateId := diskinfo.TemplateId
  681. // prepare disk snapshot dir
  682. if len(snapshots) > 0 && !fileutils2.Exists(disk.GetSnapshotDir()) {
  683. output, err := procutils.NewCommand("mkdir", "-p", disk.GetSnapshotDir()).Output()
  684. if err != nil {
  685. return errors.Wrapf(err, "mkdir %s failed: %s", disk.GetSnapshotDir(), output)
  686. }
  687. }
  688. // create snapshots form remote url
  689. var (
  690. diskStorageId = diskinfo.StorageId
  691. baseImagePath string
  692. )
  693. for i, snapshotId := range snapshots {
  694. snapId, _ := snapshotId.GetString()
  695. snapshotUrl := fmt.Sprintf("%s/%s/%s/%s",
  696. snapshotsUri, diskStorageId, diskId, snapId)
  697. snapshotPath := path.Join(disk.GetSnapshotDir(), snapId)
  698. log.Infof("Disk %s snapshot %s url: %s", diskId, snapId, snapshotUrl)
  699. if err := s.CreateSnapshotFormUrl(ctx, snapshotUrl, diskId, snapshotPath); err != nil {
  700. return errors.Wrap(err, "create from snapshot url failed")
  701. }
  702. if i == 0 && len(templateId) > 0 && sysDiskHasTemplate {
  703. templatePath := path.Join(storageManager.LocalStorageImagecacheManager.GetPath(), templateId)
  704. // check if template is encrypted
  705. img, err := qemuimg.NewQemuImage(templatePath)
  706. if err != nil {
  707. return errors.Wrap(err, "template image probe fail")
  708. }
  709. if img.Encrypted {
  710. templatePath = qemuimg.GetQemuFilepath(templatePath, "sec0", qemuimg.EncryptFormatLuks)
  711. }
  712. if err := doRebaseDisk(snapshotPath, templatePath, encInfo); err != nil {
  713. return err
  714. }
  715. } else if rebaseDisks && len(baseImagePath) > 0 {
  716. if encInfo != nil {
  717. baseImagePath = qemuimg.GetQemuFilepath(baseImagePath, "sec0", qemuimg.EncryptFormatLuks)
  718. }
  719. if err := doRebaseDisk(snapshotPath, baseImagePath, encInfo); err != nil {
  720. return err
  721. }
  722. }
  723. baseImagePath = snapshotPath
  724. }
  725. for _, snapshotId := range diskOutChainSnaps {
  726. snapId, _ := snapshotId.GetString()
  727. snapshotUrl := fmt.Sprintf("%s/%s/%s/%s",
  728. snapshotsUri, diskStorageId, diskId, snapId)
  729. snapshotPath := path.Join(disk.GetSnapshotDir(), snapId)
  730. log.Infof("Disk %s snapshot %s url: %s", diskId, snapId, snapshotUrl)
  731. if err := s.CreateSnapshotFormUrl(ctx, snapshotUrl, diskId, snapshotPath); err != nil {
  732. return errors.Wrap(err, "create from snapshot url failed")
  733. }
  734. }
  735. if liveMigrate {
  736. // create local disk
  737. backingFile, _ := disksBackingFile.GetString(diskId)
  738. _, err := disk.CreateRaw(ctx, int(diskinfo.Size), "qcow2", "", nil, encInfo, "", backingFile)
  739. if err != nil {
  740. log.Errorln(err)
  741. return err
  742. }
  743. } else {
  744. // download disk form remote url
  745. diskUrl := fmt.Sprintf("%s/%s/%s", disksUri, diskStorageId, diskId)
  746. err := disk.CreateFromUrl(ctx, diskUrl, 0, func(progress, progressMbps float64, totalSizeMb int64) {
  747. log.Debugf("[%.2f / %d] disk %s create %.2f with speed %.2fMbps", progress*float64(totalSizeMb)/100, totalSizeMb, disk.GetId(), progress, progressMbps)
  748. newProgress := float64(idx-1)/float64(totalDiskCount)*100.0 + 1/float64(totalDiskCount)*progress
  749. if len(serverId) > 0 {
  750. log.Debugf("server %s migrate %.2f with speed %.2fMbps", serverId, newProgress, progressMbps)
  751. hostutils.UpdateServerProgress(context.Background(), serverId, newProgress, progressMbps)
  752. }
  753. })
  754. if err != nil {
  755. return errors.Wrap(err, "CreateFromUrl")
  756. }
  757. }
  758. if rebaseDisks && len(templateId) > 0 && len(baseImagePath) == 0 && sysDiskHasTemplate {
  759. templatePath := path.Join(storageManager.LocalStorageImagecacheManager.GetPath(), templateId)
  760. // check if template is encrypted
  761. img, err := qemuimg.NewQemuImage(templatePath)
  762. if err != nil {
  763. return errors.Wrap(err, "template image probe fail")
  764. }
  765. if img.Encrypted {
  766. templatePath = qemuimg.GetQemuFilepath(templatePath, "sec0", qemuimg.EncryptFormatLuks)
  767. }
  768. if err := doRebaseDisk(disk.GetPath(), templatePath, encInfo); err != nil {
  769. return err
  770. }
  771. } else if rebaseDisks && len(baseImagePath) > 0 {
  772. if encInfo != nil {
  773. baseImagePath = qemuimg.GetQemuFilepath(baseImagePath, "sec0", qemuimg.EncryptFormatLuks)
  774. }
  775. if err := doRebaseDisk(disk.GetPath(), baseImagePath, encInfo); err != nil {
  776. return err
  777. }
  778. }
  779. diskinfo.Path = disk.GetPath()
  780. return nil
  781. }
  782. func doRebaseDisk(diskPath, newBasePath string, encInfo *apis.SEncryptInfo) error {
  783. img, err := qemuimg.NewQemuImage(diskPath)
  784. if err != nil {
  785. return errors.Wrap(err, "failed open disk as qemu image")
  786. }
  787. if encInfo != nil {
  788. img.SetPassword(encInfo.Key)
  789. }
  790. if err = img.Rebase(newBasePath, true); err != nil {
  791. return errors.Wrap(err, "failed rebase disk backing file")
  792. }
  793. log.Infof("rebase disk %s backing file to %s ", diskPath, newBasePath)
  794. return nil
  795. }
  796. func (s *SLocalStorage) DiskMigrate(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  797. input := params.(*SDiskMigrate)
  798. disk := s.CreateDisk(input.DiskId)
  799. snapshots := input.SnapsChain
  800. diskOutChainSnaps := input.OutChainSnaps
  801. // prepare disk snapshot dir
  802. if len(snapshots) > 0 && !fileutils2.Exists(disk.GetSnapshotDir()) {
  803. output, err := procutils.NewCommand("mkdir", "-p", disk.GetSnapshotDir()).Output()
  804. if err != nil {
  805. return nil, errors.Wrapf(err, "mkdir %s failed: %s", disk.GetSnapshotDir(), output)
  806. }
  807. }
  808. baseImagePath := ""
  809. templateId := input.TemplateId
  810. for i, snapshotId := range snapshots {
  811. snapId, _ := snapshotId.GetString()
  812. snapshotUrl := fmt.Sprintf("%s/%s/%s/%s",
  813. input.SnapshotsUri, input.SrcStorageId, input.DiskId, snapId)
  814. snapshotPath := path.Join(disk.GetSnapshotDir(), snapId)
  815. log.Infof("Disk %s snapshot %s url: %s", input.DiskId, snapId, snapshotUrl)
  816. if err := s.CreateSnapshotFormUrl(ctx, snapshotUrl, input.DiskId, snapshotPath); err != nil {
  817. return nil, errors.Wrap(err, "create from snapshot url failed")
  818. }
  819. if i == 0 && len(templateId) > 0 && input.SysDiskHasTemplate {
  820. templatePath := path.Join(storageManager.LocalStorageImagecacheManager.GetPath(), templateId)
  821. if err := doRebaseDisk(snapshotPath, templatePath, nil); err != nil {
  822. return nil, err
  823. }
  824. } else if len(baseImagePath) > 0 {
  825. if err := doRebaseDisk(snapshotPath, baseImagePath, nil); err != nil {
  826. return nil, err
  827. }
  828. }
  829. baseImagePath = snapshotPath
  830. }
  831. for _, snapshotId := range diskOutChainSnaps {
  832. snapId, _ := snapshotId.GetString()
  833. snapshotUrl := fmt.Sprintf("%s/%s/%s/%s",
  834. input.SnapshotsUri, input.SrcStorageId, input.DiskId, snapId)
  835. snapshotPath := path.Join(disk.GetSnapshotDir(), snapId)
  836. log.Infof("Disk %s snapshot %s url: %s", input.DiskId, snapId, snapshotUrl)
  837. if err := s.CreateSnapshotFormUrl(ctx, snapshotUrl, input.DiskId, snapshotPath); err != nil {
  838. return nil, errors.Wrap(err, "create from snapshot url failed")
  839. }
  840. }
  841. // download disk form remote url
  842. diskUrl := fmt.Sprintf("%s/%s/%s", input.DiskUri, input.SrcStorageId, input.DiskId)
  843. err := disk.CreateFromUrl(ctx, diskUrl, 0, func(progress, progressMbps float64, totalSizeMb int64) {
  844. log.Debugf("[%.2f / %d] disk %s create %.2f with speed %.2fMbps",
  845. progress*float64(totalSizeMb)/100, totalSizeMb, disk.GetId(), progress, progressMbps)
  846. })
  847. if err != nil {
  848. return nil, errors.Wrap(err, "CreateFromUrl")
  849. }
  850. if len(templateId) > 0 && len(baseImagePath) == 0 {
  851. templatePath := path.Join(storageManager.LocalStorageImagecacheManager.GetPath(), templateId)
  852. if err := doRebaseDisk(disk.GetPath(), templatePath, nil); err != nil {
  853. return nil, err
  854. }
  855. } else if len(baseImagePath) > 0 {
  856. if err := doRebaseDisk(disk.GetPath(), baseImagePath, nil); err != nil {
  857. return nil, err
  858. }
  859. }
  860. res := jsonutils.NewDict()
  861. res.Set("disk_path", jsonutils.NewString(disk.GetPath()))
  862. return nil, nil
  863. }
  864. func (s *SLocalStorage) CreateDiskFromSnapshot(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) {
  865. info := input.DiskInfo
  866. if info.Protocol == "fuse" {
  867. var encryptInfo *apis.SEncryptInfo
  868. if info.Encryption {
  869. encryptInfo = &info.EncryptInfo
  870. }
  871. err := disk.CreateFromRemoteHostImage(ctx, info.SnapshotUrl, int64(info.DiskSizeMb), encryptInfo)
  872. if err != nil {
  873. return nil, errors.Wrapf(err, "CreateFromRemoteHostImage")
  874. }
  875. return disk.GetDiskDesc(), nil
  876. }
  877. return nil, httperrors.NewUnsupportOperationError("Unsupport protocol %s for Local storage", info.Protocol)
  878. }
  879. func (s *SLocalStorage) CreateDiskFromExistingPath(
  880. ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo,
  881. ) error {
  882. err := os.Link(input.DiskInfo.ExistingPath, disk.GetPath())
  883. if err != nil {
  884. return errors.Wrap(err, "os.link")
  885. }
  886. return nil
  887. }
  888. func (s *SLocalStorage) GetCloneTargetDiskPath(ctx context.Context, targetDiskId string) string {
  889. return path.Join(s.GetPath(), targetDiskId)
  890. }
  891. func (s *SLocalStorage) CloneDiskFromStorage(ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string, fullCopy bool, encInfo apis.SEncryptInfo) (*hostapi.ServerCloneDiskFromStorageResponse, error) {
  892. srcDiskPath := srcDisk.GetPath()
  893. srcImg, err := qemuimg.NewQemuImage(srcDiskPath)
  894. if err != nil {
  895. return nil, errors.Wrapf(err, "Get source image %q info", srcDiskPath)
  896. }
  897. if encInfo.Id != "" {
  898. srcImg.SetPassword(encInfo.Key)
  899. }
  900. // start create target disk. if full copy is false, just create
  901. // empty target disk with same size and format
  902. accessPath := s.GetCloneTargetDiskPath(ctx, targetDiskId)
  903. if fullCopy {
  904. _, err = srcImg.Clone(s.GetCloneTargetDiskPath(ctx, targetDiskId), qemuimgfmt.QCOW2, false)
  905. } else {
  906. newImg, err := qemuimg.NewQemuImage(accessPath)
  907. if err != nil {
  908. return nil, errors.Wrap(err, "failed new qemu image")
  909. }
  910. err = newImg.CreateQcow2(srcImg.GetSizeMB(), false, "", encInfo.Key, qemuimg.EncryptFormatLuks, encInfo.Alg)
  911. }
  912. if err != nil {
  913. return nil, errors.Wrap(err, "Clone source disk to target local storage")
  914. }
  915. return &hostapi.ServerCloneDiskFromStorageResponse{
  916. TargetAccessPath: accessPath,
  917. TargetFormat: qemuimgfmt.QCOW2.String(),
  918. }, nil
  919. }
  920. func (s *SLocalStorage) CleanRecycleDiskfiles(ctx context.Context) {
  921. cleanDailyFiles(s.Path, _RECYCLE_BIN_, options.HostOptions.RecycleDiskfileKeepDays)
  922. cleanDailyFiles(s.Path, _IMGSAVE_BACKUPS_, options.HostOptions.RecycleDiskfileKeepDays)
  923. }