image_subs.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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 models
  15. import (
  16. "context"
  17. "database/sql"
  18. "fmt"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. api "yunion.io/x/onecloud/pkg/apis/image"
  26. noapi "yunion.io/x/onecloud/pkg/apis/notify"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
  29. "yunion.io/x/onecloud/pkg/image/options"
  30. "yunion.io/x/onecloud/pkg/image/torrent"
  31. "yunion.io/x/onecloud/pkg/mcclient"
  32. "yunion.io/x/onecloud/pkg/util/fileutils2"
  33. "yunion.io/x/onecloud/pkg/util/torrentutils"
  34. )
  35. // +onecloud:swagger-gen-ignore
  36. type SImageSubformatManager struct {
  37. db.SResourceBaseManager
  38. }
  39. var ImageSubformatManager *SImageSubformatManager
  40. func init() {
  41. ImageSubformatManager = &SImageSubformatManager{
  42. SResourceBaseManager: db.NewResourceBaseManager(
  43. SImageSubformat{},
  44. "image_subformats",
  45. "image_subformat",
  46. "image_subformats",
  47. ),
  48. }
  49. ImageSubformatManager.SetVirtualObject(ImageSubformatManager)
  50. ImageSubformatManager.TableSpec().AddIndex(true, "image_id", "format")
  51. }
  52. // +onecloud:swagger-gen-ignore
  53. type SImageSubformat struct {
  54. SImagePeripheral
  55. Format string `width:"20" charset:"ascii" nullable:"true"`
  56. Size int64 `nullable:"true"`
  57. Location string `nullable:"true"`
  58. Checksum string `width:"32" charset:"ascii" nullable:"true"`
  59. FastHash string `width:"32" charset:"ascii" nullable:"true"`
  60. Status string `nullable:"false"`
  61. TorrentSize int64 `nullable:"true"`
  62. TorrentLocation string `nullable:"true"`
  63. TorrentChecksum string `width:"32" charset:"ascii" nullable:"true"`
  64. TorrentStatus string `nullable:"false"`
  65. }
  66. func (manager *SImageSubformatManager) FetchSubImage(id string, format string) *SImageSubformat {
  67. q := manager.Query().Equals("image_id", id).Equals("format", format)
  68. subImgObj, err := db.NewModelObject(manager)
  69. if err != nil {
  70. log.Errorf("new subformatfail %s", err)
  71. return nil
  72. }
  73. subImg := subImgObj.(*SImageSubformat)
  74. err = q.First(subImg)
  75. if err != nil {
  76. if err != sql.ErrNoRows {
  77. log.Errorf("query subimage fail! %s", err)
  78. }
  79. return nil
  80. }
  81. return subImg
  82. }
  83. func (manager *SImageSubformatManager) GetAllSubImages(id string) []SImageSubformat {
  84. q := manager.Query().Equals("image_id", id)
  85. var subImgs []SImageSubformat
  86. err := db.FetchModelObjects(manager, q, &subImgs)
  87. if err != nil {
  88. log.Errorf("query subimage fail!")
  89. return nil
  90. }
  91. return subImgs
  92. }
  93. func (self *SImageSubformat) isLocal() bool {
  94. return strings.HasPrefix(self.Location, LocalFilePrefix)
  95. }
  96. func (self *SImageSubformat) doConvert(image *SImage) error {
  97. err := self.Save(image)
  98. if err != nil {
  99. log.Errorf("fail to convert image %s", err)
  100. return err
  101. }
  102. if options.Options.EnableTorrentService {
  103. err = self.SaveTorrent()
  104. if err != nil {
  105. log.Errorf("fail to convert image torrent %s", err)
  106. return err
  107. }
  108. err = self.seedTorrent(image.Id)
  109. if err != nil {
  110. log.Errorf("fail to seed torrent %s", err)
  111. return err
  112. }
  113. }
  114. // log.Infof("Start seeding...")
  115. return nil
  116. }
  117. func (self *SImageSubformat) Save(image *SImage) error {
  118. var err error
  119. defer func() {
  120. if err != nil {
  121. db.Update(self, func() error {
  122. self.Status = api.IMAGE_STATUS_SAVE_FAIL
  123. return nil
  124. })
  125. }
  126. }()
  127. if self.Status == api.IMAGE_STATUS_ACTIVE {
  128. return nil
  129. }
  130. _, err = db.Update(self, func() error {
  131. self.Status = api.IMAGE_STATUS_SAVING
  132. return nil
  133. })
  134. if err != nil {
  135. log.Errorf("updateStatus fail %s", err)
  136. return err
  137. }
  138. info, err := storage.ConvertImage(context.Background(), image, self.Format, nil)
  139. if err != nil {
  140. return errors.Wrap(err, "unable to ConvertImage")
  141. }
  142. location := image.GetPath(self.Format)
  143. checksum, err := fileutils2.MD5(location)
  144. if err != nil {
  145. log.Errorf("fileutils2.Md5 fail %s", err)
  146. return err
  147. }
  148. fastHash, err := fileutils2.FastCheckSum(location)
  149. if err != nil {
  150. log.Errorf("fileutils2.fastChecksum fail %s", err)
  151. return err
  152. }
  153. _, err = db.Update(self, func() error {
  154. self.Location = info.Location
  155. self.Checksum = checksum
  156. self.FastHash = fastHash
  157. self.Size = info.SizeBytes
  158. self.Status = api.IMAGE_STATUS_ACTIVE
  159. return nil
  160. })
  161. if err != nil {
  162. log.Errorf("updateStatus fail %s", err)
  163. return err
  164. }
  165. return nil
  166. }
  167. func (self *SImageSubformat) SaveTorrent() error {
  168. if self.TorrentStatus == api.IMAGE_STATUS_ACTIVE {
  169. return nil
  170. }
  171. // if self.TorrentStatus != api.IMAGE_STATUS_QUEUED {
  172. // return nil // httperrors.NewInvalidStatusError("cannot save torrent in status %s", self.Status)
  173. // }
  174. imgPath := self.GetLocalLocation()
  175. torrentPath := filepath.Join(options.Options.TorrentStoreDir, fmt.Sprintf("%s.torrent", filepath.Base(imgPath)))
  176. _, err := db.Update(self, func() error {
  177. self.TorrentStatus = api.IMAGE_STATUS_SAVING
  178. self.TorrentLocation = fmt.Sprintf("%s%s", LocalFilePrefix, torrentPath)
  179. return nil
  180. })
  181. if err != nil {
  182. log.Errorf("updateStatus fail %s", err)
  183. return err
  184. }
  185. _, err = torrentutils.GenerateTorrent(imgPath, torrent.GetTrackers(), torrentPath)
  186. if err != nil {
  187. log.Errorf("torrentutils.GenerateTorrent %s fail %s", imgPath, err)
  188. return err
  189. }
  190. checksum, err := fileutils2.MD5(torrentPath)
  191. if err != nil {
  192. log.Errorf("fileutils2.Md5 fail %s", err)
  193. return err
  194. }
  195. _, err = db.Update(self, func() error {
  196. self.TorrentStatus = api.IMAGE_STATUS_ACTIVE
  197. self.TorrentLocation = fmt.Sprintf("%s%s", LocalFilePrefix, torrentPath)
  198. self.TorrentChecksum = checksum
  199. self.TorrentSize = fileutils2.FileSize(torrentPath)
  200. return nil
  201. })
  202. if err != nil {
  203. log.Errorf("updateStatus fail %s", err)
  204. return err
  205. }
  206. return nil
  207. }
  208. func (self *SImageSubformat) GetLocalLocation() string {
  209. if len(self.Location) > len(LocalFilePrefix) {
  210. return self.Location[len(LocalFilePrefix):]
  211. }
  212. return ""
  213. }
  214. func (self *SImageSubformat) getLocalTorrentLocation() string {
  215. if len(self.TorrentLocation) > len(LocalFilePrefix) {
  216. return self.TorrentLocation[len(LocalFilePrefix):]
  217. }
  218. return ""
  219. }
  220. func (self *SImageSubformat) seedTorrent(imageId string) error {
  221. file := self.getLocalTorrentLocation()
  222. log.Debugf("add torrent %s to seed...", file)
  223. return torrent.SeedTorrent(file, imageId, self.Format)
  224. }
  225. func (self *SImageSubformat) StopTorrent() {
  226. if len(self.TorrentLocation) > 0 {
  227. torrent.RemoveTorrent(self.getLocalTorrentLocation())
  228. }
  229. }
  230. func (self *SImageSubformat) cleanup(ctx context.Context, userCred mcclient.TokenCredential) error {
  231. err := self.removeFiles(ctx)
  232. if err != nil {
  233. return errors.Wrap(err, "removeFiles")
  234. }
  235. err = self.Delete(ctx, userCred)
  236. if err != nil {
  237. return errors.Wrap(err, "delete")
  238. }
  239. return nil
  240. }
  241. func (self *SImageSubformat) removeFiles(ctx context.Context) error {
  242. self.StopTorrent()
  243. location := self.getLocalTorrentLocation()
  244. if len(location) > 0 && fileutils2.IsFile(location) {
  245. err := os.Remove(location)
  246. if err != nil {
  247. return err
  248. }
  249. }
  250. if err := RemoveImage(ctx, self.Location); err != nil {
  251. if strings.Contains(err.Error(), "no such file or directory") {
  252. return nil
  253. }
  254. return err
  255. }
  256. return nil
  257. }
  258. type SImageSubformatDetails struct {
  259. Format string
  260. Size int64
  261. Checksum string
  262. FastHash string
  263. Status string
  264. TorrentSize int64
  265. TorrentChecksum string
  266. TorrentStatus string
  267. TorrentSeeding bool
  268. }
  269. func (self *SImageSubformat) GetDetails() SImageSubformatDetails {
  270. details := SImageSubformatDetails{}
  271. details.Format = self.Format
  272. details.Size = self.Size
  273. details.Checksum = self.Checksum
  274. details.FastHash = self.FastHash
  275. details.Status = self.Status
  276. details.TorrentSize = self.TorrentSize
  277. details.TorrentChecksum = self.TorrentChecksum
  278. details.TorrentStatus = self.TorrentStatus
  279. filePath := self.getLocalTorrentLocation()
  280. if len(filePath) > 0 {
  281. details.TorrentSeeding = torrent.GetTorrentSeeding(filePath)
  282. }
  283. return details
  284. }
  285. func (self *SImageSubformat) isActive(useFast bool, noCheckum bool) bool {
  286. active, reason := isActive(self.GetLocalLocation(), self.Size, self.Checksum, self.FastHash, useFast, noCheckum)
  287. if active || reason != FileChecksumMismatch {
  288. return active
  289. }
  290. data := jsonutils.NewDict()
  291. data.Set("name", jsonutils.NewString(self.ImageId))
  292. notifyclient.SystemExceptionNotifyWithResult(context.TODO(), noapi.ActionChecksumTest, noapi.TOPIC_RESOURCE_IMAGE, noapi.ResultFailed, data)
  293. return false
  294. }
  295. func (self *SImageSubformat) isTorrentActive() bool {
  296. active, _ := isActive(self.getLocalTorrentLocation(), self.TorrentSize, self.TorrentChecksum, "", false, false)
  297. return active
  298. }
  299. func (self *SImageSubformat) SetStatus(status string) error {
  300. _, err := db.Update(self, func() error {
  301. self.Status = status
  302. return nil
  303. })
  304. return err
  305. }
  306. func (self *SImageSubformat) setTorrentStatus(status string) error {
  307. _, err := db.Update(self, func() error {
  308. self.TorrentStatus = status
  309. return nil
  310. })
  311. return err
  312. }
  313. func (self *SImageSubformat) checkStatus(useFast bool, noChecksum bool) {
  314. if strings.HasPrefix(self.Location, LocalFilePrefix) {
  315. if self.isActive(useFast, noChecksum) {
  316. if self.Status != api.IMAGE_STATUS_ACTIVE {
  317. self.SetStatus(api.IMAGE_STATUS_ACTIVE)
  318. }
  319. if len(self.FastHash) == 0 {
  320. fastHash, err := fileutils2.FastCheckSum(self.GetLocalLocation())
  321. if err != nil {
  322. log.Errorf("checkStatus fileutils2.FastChecksum fail %s", err)
  323. } else {
  324. _, err := db.Update(self, func() error {
  325. self.FastHash = fastHash
  326. return nil
  327. })
  328. if err != nil {
  329. log.Errorf("checkStatus save FastHash fail %s", err)
  330. }
  331. }
  332. }
  333. } else {
  334. if self.Status != api.IMAGE_STATUS_QUEUED {
  335. self.SetStatus(api.IMAGE_STATUS_QUEUED)
  336. }
  337. }
  338. if self.isTorrentActive() {
  339. if self.TorrentStatus != api.IMAGE_STATUS_ACTIVE {
  340. self.setTorrentStatus(api.IMAGE_STATUS_ACTIVE)
  341. }
  342. } else {
  343. if self.TorrentStatus != api.IMAGE_STATUS_QUEUED {
  344. self.setTorrentStatus(api.IMAGE_STATUS_QUEUED)
  345. }
  346. }
  347. }
  348. }
  349. func (self *SImageSubformat) SetStatusSeeding(seeding bool) {
  350. filePath := self.getLocalTorrentLocation()
  351. if len(filePath) > 0 {
  352. torrent.SetTorrentSeeding(filePath, seeding)
  353. }
  354. }
  355. func (subimg *SImageSubformat) verifyStatusSelf(ctx context.Context) error {
  356. if len(subimg.Location) == 0 {
  357. return nil
  358. }
  359. filePath := subimg.Location
  360. _, rc, err := GetImage(ctx, filePath)
  361. if err != nil {
  362. subimg.SetStatus(api.IMAGE_STATUS_UNKNOWN)
  363. return errors.Wrap(err, "GetImage")
  364. }
  365. defer rc.Close()
  366. subimg.SetStatus(api.IMAGE_STATUS_ACTIVE)
  367. return nil
  368. }