images.go 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454
  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. "fmt"
  18. "io"
  19. "math"
  20. "net/http"
  21. "os"
  22. "path"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "yunion.io/x/jsonutils"
  28. "yunion.io/x/log"
  29. "yunion.io/x/pkg/errors"
  30. "yunion.io/x/pkg/gotypes"
  31. "yunion.io/x/pkg/tristate"
  32. "yunion.io/x/pkg/util/pinyinutils"
  33. "yunion.io/x/pkg/util/qemuimgfmt"
  34. "yunion.io/x/pkg/util/rbacscope"
  35. "yunion.io/x/pkg/util/streamutils"
  36. "yunion.io/x/pkg/util/timeutils"
  37. "yunion.io/x/pkg/utils"
  38. "yunion.io/x/sqlchemy"
  39. "yunion.io/x/onecloud/pkg/apis"
  40. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  41. api "yunion.io/x/onecloud/pkg/apis/image"
  42. noapi "yunion.io/x/onecloud/pkg/apis/notify"
  43. "yunion.io/x/onecloud/pkg/appsrv"
  44. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  45. "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
  46. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  47. "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
  48. common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
  49. deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
  50. "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
  51. "yunion.io/x/onecloud/pkg/httperrors"
  52. "yunion.io/x/onecloud/pkg/image/drivers/s3"
  53. "yunion.io/x/onecloud/pkg/image/options"
  54. "yunion.io/x/onecloud/pkg/mcclient"
  55. "yunion.io/x/onecloud/pkg/mcclient/auth"
  56. "yunion.io/x/onecloud/pkg/mcclient/modulebase"
  57. identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  58. modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
  59. "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
  60. "yunion.io/x/onecloud/pkg/util/cephutils"
  61. "yunion.io/x/onecloud/pkg/util/fileutils2"
  62. "yunion.io/x/onecloud/pkg/util/isoutils"
  63. "yunion.io/x/onecloud/pkg/util/logclient"
  64. "yunion.io/x/onecloud/pkg/util/procutils"
  65. "yunion.io/x/onecloud/pkg/util/qemuimg"
  66. "yunion.io/x/onecloud/pkg/util/qemutils"
  67. "yunion.io/x/onecloud/pkg/util/rbacutils"
  68. "yunion.io/x/onecloud/pkg/util/stringutils2"
  69. )
  70. const (
  71. LocalFilePrefix = api.LocalFilePrefix
  72. )
  73. type SImageManager struct {
  74. db.SSharableVirtualResourceBaseManager
  75. db.SMultiArchResourceBaseManager
  76. db.SEncryptedResourceManager
  77. }
  78. var ImageManager *SImageManager
  79. var imgStreamingWorkerMan *appsrv.SWorkerManager
  80. func init() {
  81. ImageManager = &SImageManager{
  82. SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
  83. SImage{},
  84. "images",
  85. "image",
  86. "images",
  87. ),
  88. }
  89. ImageManager.SetVirtualObject(ImageManager)
  90. }
  91. func InitImageStreamWorkers() {
  92. imgStreamingWorkerMan = appsrv.NewWorkerManager("image_streaming_worker", options.Options.ImageStreamWorkerCount, 1024, true)
  93. }
  94. /*
  95. +------------------+--------------+------+-----+---------+-------+
  96. | Field | Type | Null | Key | Default | Extra |
  97. +------------------+--------------+------+-----+---------+-------+
  98. | id | varchar(36) | NO | PRI | NULL | |
  99. | name | varchar(255) | YES | | NULL | |
  100. | size | bigint(20) | YES | | NULL | |
  101. | status | varchar(30) | NO | | NULL | |
  102. | is_public | tinyint(1) | NO | MUL | NULL | |
  103. | location | text | YES | | NULL | |
  104. | created_at | datetime | NO | | NULL | |
  105. | updated_at | datetime | YES | | NULL | |
  106. | deleted_at | datetime | YES | | NULL | |
  107. | deleted | tinyint(1) | NO | MUL | NULL | |
  108. | parent_id | varchar(36) | YES | | NULL | |
  109. | disk_format | varchar(20) | YES | | NULL | |
  110. | container_format | varchar(20) | YES | | NULL | |
  111. | checksum | varchar(32) | YES | | NULL | |
  112. | owner | varchar(255) | YES | | NULL | |
  113. | min_disk | int(11) | NO | | NULL | |
  114. | min_ram | int(11) | NO | | NULL | |
  115. | protected | tinyint(1) | YES | | NULL | |
  116. | description | varchar(256) | YES | | NULL | |
  117. +------------------+--------------+------+-----+---------+-------+
  118. */
  119. type SImage struct {
  120. db.SSharableVirtualResourceBase
  121. db.SMultiArchResourceBase
  122. db.SEncryptedResource
  123. // 镜像大小, 单位Byte
  124. Size int64 `nullable:"true" list:"user" create:"optional"`
  125. // 存储地址
  126. Location string `nullable:"true"`
  127. // 镜像格式
  128. DiskFormat string `width:"20" charset:"ascii" nullable:"true" list:"user" create:"optional" default:"raw"`
  129. // 校验和
  130. Checksum string `width:"32" charset:"ascii" nullable:"true" get:"user" list:"user"`
  131. FastHash string `width:"32" charset:"ascii" nullable:"true" get:"user"`
  132. // 用户Id
  133. Owner string `width:"255" charset:"ascii" nullable:"true" get:"user"`
  134. // 最小系统盘要求
  135. MinDiskMB int32 `name:"min_disk" nullable:"false" default:"0" list:"user" create:"optional" update:"user"`
  136. // 最小内存要求
  137. MinRamMB int32 `name:"min_ram" nullable:"false" default:"0" list:"user" create:"optional" update:"user"`
  138. // 是否有删除保护
  139. Protected tristate.TriState `default:"true" list:"user" get:"user" create:"optional" update:"user"`
  140. // 是否是标准镜像
  141. IsStandard tristate.TriState `default:"false" list:"user" get:"user" create:"admin_optional" update:"user"`
  142. // 是否是主机镜像
  143. IsGuestImage tristate.TriState `default:"false" create:"optional" list:"user"`
  144. // 是否是数据盘镜像
  145. IsData tristate.TriState `default:"false" create:"optional" list:"user" update:"user"`
  146. // image copy from url, save origin checksum before probe
  147. // 从镜像时长导入的镜像校验和
  148. OssChecksum string `width:"32" charset:"ascii" nullable:"true" get:"user" list:"user"`
  149. // 加密状态, "",encrypting,encrypted
  150. EncryptStatus string `width:"16" charset:"ascii" nullable:"true" get:"user" list:"user"`
  151. }
  152. func (manager *SImageManager) CustomizeHandlerInfo(info *appsrv.SHandlerInfo) {
  153. manager.SSharableVirtualResourceBaseManager.CustomizeHandlerInfo(info)
  154. switch info.GetName(nil) {
  155. case "get_details", "create", "update":
  156. info.SetProcessTimeout(time.Hour * 4).SetWorkerManager(imgStreamingWorkerMan)
  157. }
  158. }
  159. func (manager *SImageManager) FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
  160. data := modules.FetchImageMeta(header)
  161. return data, nil
  162. }
  163. func (manager *SImageManager) FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
  164. return modules.FetchImageMeta(header), nil
  165. }
  166. func (manager *SImageManager) InitializeData() error {
  167. // set cloudregion ID
  168. images := make([]SImage, 0)
  169. q := manager.Query().IsNullOrEmpty("tenant_id")
  170. err := db.FetchModelObjects(manager, q, &images)
  171. if err != nil {
  172. return err
  173. }
  174. for i := 0; i < len(images); i += 1 {
  175. if len(images[i].ProjectId) == 0 {
  176. db.Update(&images[i], func() error {
  177. images[i].ProjectId = images[i].Owner
  178. return nil
  179. })
  180. }
  181. }
  182. return nil
  183. }
  184. func (manager *SImageManager) GetPropertyDetail(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  185. appParams := appsrv.AppContextGetParams(ctx)
  186. appParams.OverrideResponseBodyWrapper = true
  187. queryDict := query.(*jsonutils.JSONDict)
  188. queryDict.Add(jsonutils.JSONTrue, "details")
  189. items, err := db.ListItems(manager, ctx, userCred, queryDict, nil)
  190. if err != nil {
  191. log.Errorf("Fail to list items: %s", err)
  192. return nil, httperrors.NewGeneralError(err)
  193. }
  194. return modulebase.ListResult2JSONWithKey(items, manager.KeywordPlural()), nil
  195. }
  196. func (manager *SImageManager) IsCustomizedGetDetailsBody() bool {
  197. return true
  198. }
  199. func (self *SImage) CustomizedGetDetailsBody(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  200. filePath := self.Location
  201. status := self.Status
  202. if self.IsGuestImage.IsFalse() {
  203. formatStr := jsonutils.GetAnyString(query, []string{"format", "disk_format"})
  204. if len(formatStr) > 0 {
  205. subImages := ImageSubformatManager.GetAllSubImages(self.Id)
  206. if len(subImages) == 1 {
  207. // ignore format field
  208. formatStr = subImages[0].Format
  209. }
  210. subimg := ImageSubformatManager.FetchSubImage(self.Id, formatStr)
  211. if subimg != nil {
  212. if strings.HasPrefix(subimg.Location, api.LocalFilePrefix) {
  213. isTorrent := jsonutils.QueryBoolean(query, "torrent", false)
  214. if !isTorrent {
  215. filePath = subimg.Location
  216. status = subimg.Status
  217. } else {
  218. filePath = subimg.getLocalTorrentLocation()
  219. status = subimg.TorrentStatus
  220. }
  221. } else {
  222. filePath = subimg.Location
  223. status = subimg.Status
  224. }
  225. } else {
  226. return nil, httperrors.NewNotFoundError("format %s not found", formatStr)
  227. }
  228. }
  229. }
  230. if status != api.IMAGE_STATUS_ACTIVE {
  231. return nil, httperrors.NewInvalidStatusError("cannot download in status %s", status)
  232. }
  233. if filePath == "" {
  234. return nil, httperrors.NewInvalidStatusError("empty file path")
  235. }
  236. size, rc, err := GetImage(ctx, filePath)
  237. if err != nil {
  238. return nil, errors.Wrap(err, "get image")
  239. }
  240. defer rc.Close()
  241. appParams := appsrv.AppContextGetParams(ctx)
  242. appParams.Response.Header().Set("Content-Length", strconv.FormatInt(size, 10))
  243. _, err = streamutils.StreamPipe(rc, appParams.Response, false, nil)
  244. if err != nil {
  245. return nil, httperrors.NewGeneralError(err)
  246. }
  247. return nil, nil
  248. }
  249. func (self *SImage) getMoreDetails(out api.ImageDetails) api.ImageDetails {
  250. properties, err := ImagePropertyManager.GetProperties(self.Id)
  251. if err != nil {
  252. log.Errorf("ImagePropertyManager.GetProperties fail %s", err)
  253. }
  254. out.Properties = properties
  255. if self.PendingDeleted {
  256. pendingDeletedAt := self.PendingDeletedAt.Add(time.Second * time.Duration(options.Options.PendingDeleteExpireSeconds))
  257. out.AutoDeleteAt = pendingDeletedAt
  258. }
  259. var ossChksum = self.OssChecksum
  260. if len(self.OssChecksum) == 0 {
  261. ossChksum = self.Checksum
  262. }
  263. out.OssChecksum = ossChksum
  264. out.DisableDelete = self.Protected.Bool()
  265. return out
  266. }
  267. func (manager *SImageManager) FetchCustomizeColumns(
  268. ctx context.Context,
  269. userCred mcclient.TokenCredential,
  270. query jsonutils.JSONObject,
  271. objs []interface{},
  272. fields stringutils2.SSortedStrings,
  273. isList bool,
  274. ) []api.ImageDetails {
  275. rows := make([]api.ImageDetails, len(objs))
  276. virtRows := manager.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  277. encRows := manager.SEncryptedResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  278. for i := range rows {
  279. image := objs[i].(*SImage)
  280. rows[i] = api.ImageDetails{
  281. SharableVirtualResourceDetails: virtRows[i],
  282. EncryptedResourceDetails: encRows[i],
  283. }
  284. rows[i] = image.getMoreDetails(rows[i])
  285. }
  286. return rows
  287. }
  288. func (img *SImage) GetExtraDetailsHeaders(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) map[string]string {
  289. headers := make(map[string]string)
  290. details := ImageManager.FetchCustomizeColumns(ctx, userCred, query, []interface{}{img}, nil, false)
  291. extra := jsonutils.Marshal(details[0]).(*jsonutils.JSONDict)
  292. for _, k := range extra.SortedKeys() {
  293. if k == "properties" {
  294. continue
  295. }
  296. val, _ := extra.GetString(k)
  297. if len(val) > 0 {
  298. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, k)] = val
  299. }
  300. }
  301. jsonDict := jsonutils.Marshal(img).(*jsonutils.JSONDict)
  302. fields, _ := db.GetDetailFields(img.GetModelManager(), userCred)
  303. for _, k := range jsonDict.SortedKeys() {
  304. if utils.IsInStringArray(k, fields) {
  305. val, _ := jsonDict.GetString(k)
  306. if len(val) > 0 {
  307. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, k)] = val
  308. }
  309. }
  310. }
  311. // none of subimage business
  312. var ossChksum = img.OssChecksum
  313. if len(img.OssChecksum) == 0 {
  314. ossChksum = img.Checksum
  315. }
  316. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "oss_checksum")] = ossChksum
  317. formatStr := jsonutils.GetAnyString(query, []string{"format", "disk_format"})
  318. if len(formatStr) > 0 {
  319. subimg := ImageSubformatManager.FetchSubImage(img.Id, formatStr)
  320. if subimg != nil {
  321. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "disk_format")] = formatStr
  322. isTorrent := jsonutils.QueryBoolean(query, "torrent", false)
  323. if !isTorrent {
  324. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "status")] = subimg.Status
  325. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "size")] = fmt.Sprintf("%d", subimg.Size)
  326. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "checksum")] = subimg.Checksum
  327. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "oss_checksum")] = subimg.Checksum
  328. } else {
  329. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "status")] = subimg.TorrentStatus
  330. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "size")] = fmt.Sprintf("%d", subimg.TorrentSize)
  331. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "checksum")] = subimg.TorrentChecksum
  332. }
  333. }
  334. }
  335. properties, _ := ImagePropertyManager.GetProperties(img.Id)
  336. if len(properties) > 0 {
  337. for k, v := range properties {
  338. headers[fmt.Sprintf("%s%s", modules.IMAGE_META_PROPERTY, k)] = v
  339. }
  340. }
  341. if img.PendingDeleted {
  342. pendingDeletedAt := img.PendingDeletedAt.Add(time.Second * time.Duration(options.Options.PendingDeleteExpireSeconds))
  343. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "auto_delete_at")] = timeutils.FullIsoTime(pendingDeletedAt)
  344. }
  345. if options.Options.S3DirectDownload && strings.HasPrefix(img.Location, api.S3Prefix) {
  346. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "s3_info_url")] = s3.GetEndpoint(options.Options.S3Endpoint, options.Options.S3UseSSL)
  347. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "s3_info_access_key")] = options.Options.S3AccessKey
  348. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "s3_info_secret")] = options.Options.S3SecretKey
  349. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "s3_info_bucket")] = options.Options.S3BucketName
  350. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "s3_info_key")] = imagePathToName(img.Location)
  351. headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "s3_info_sign_ver")] = options.Options.S3SignVersion
  352. }
  353. return headers
  354. }
  355. func (manager *SImageManager) ValidateCreateData(
  356. ctx context.Context,
  357. userCred mcclient.TokenCredential,
  358. ownerId mcclient.IIdentityProvider,
  359. query jsonutils.JSONObject,
  360. input api.ImageCreateInput,
  361. ) (api.ImageCreateInput, error) {
  362. var err error
  363. input.SharableVirtualResourceCreateInput, err = manager.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput)
  364. if err != nil {
  365. return input, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ValidateCreateData")
  366. }
  367. input.EncryptedResourceCreateInput, err = manager.SEncryptedResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EncryptedResourceCreateInput)
  368. if err != nil {
  369. return input, errors.Wrap(err, "SEncryptedResourceManager.ValidateCreateData")
  370. }
  371. // If this image is the part of guest image (contains "guest_image_id"),
  372. // we do not need to check and set pending quota
  373. // because that pending quota has been checked and set in SGuestImage.ValidateCreateData
  374. if input.IsGuestImage == nil || !*input.IsGuestImage {
  375. pendingUsage := SQuota{Image: 1}
  376. keys := imageCreateInput2QuotaKeys(input.DiskFormat, ownerId)
  377. pendingUsage.SetKeys(keys)
  378. if err := quotas.CheckSetPendingQuota(ctx, userCred, &pendingUsage); err != nil {
  379. return input, httperrors.NewOutOfQuotaError("%s", err)
  380. }
  381. }
  382. return input, nil
  383. }
  384. func (self *SImage) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  385. err := self.SSharableVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
  386. if err != nil {
  387. return err
  388. }
  389. self.Status = api.IMAGE_STATUS_QUEUED
  390. self.Owner = self.ProjectId
  391. err = self.SEncryptedResource.CustomizeCreate(ctx, userCred, ownerId, data, "image-"+pinyinutils.Text2Pinyin(self.Name))
  392. if err != nil {
  393. return errors.Wrap(err, "SEncryptedResource.CustomizeCreate")
  394. }
  395. return nil
  396. }
  397. func (self *SImage) GetLocalPath(format string) string {
  398. path := filepath.Join(options.Options.FilesystemStoreDatadir, self.Id)
  399. if len(format) > 0 {
  400. path = fmt.Sprintf("%s.%s", path, format)
  401. }
  402. return path
  403. }
  404. func (self *SImage) GetPath(format string) string {
  405. path := filepath.Join(options.Options.FilesystemStoreDatadir, self.Id)
  406. if options.Options.StorageDriver == api.IMAGE_STORAGE_DRIVER_S3 {
  407. path = filepath.Join(options.Options.S3MountPoint, self.Id)
  408. }
  409. if len(format) > 0 {
  410. path = fmt.Sprintf("%s.%s", path, format)
  411. }
  412. return path
  413. }
  414. func (self *SImage) unprotectImage() {
  415. db.Update(self, func() error {
  416. self.Protected = tristate.False
  417. return nil
  418. })
  419. }
  420. func (self *SImage) OnJointFailed(ctx context.Context, userCred mcclient.TokenCredential) {
  421. log.Errorf("create joint of image and guest image failed")
  422. self.SetStatus(ctx, userCred, api.IMAGE_STATUS_KILLED, "")
  423. self.unprotectImage()
  424. }
  425. func (self *SImage) OnSaveFailed(ctx context.Context, userCred mcclient.TokenCredential, msg jsonutils.JSONObject) {
  426. self.saveFailed(userCred, msg)
  427. logclient.AddActionLogWithContext(ctx, self, logclient.ACT_IMAGE_SAVE, msg, userCred, false)
  428. }
  429. func (self *SImage) OnSaveTaskFailed(task taskman.ITask, userCred mcclient.TokenCredential, msg jsonutils.JSONObject) {
  430. self.saveFailed(userCred, msg)
  431. logclient.AddActionLogWithStartable(task, self, logclient.ACT_IMAGE_SAVE, msg, userCred, false)
  432. }
  433. func (self *SImage) OnSaveSuccess(ctx context.Context, userCred mcclient.TokenCredential, msg string) {
  434. self.SetStatus(ctx, userCred, api.IMAGE_STATUS_SAVED, "save success")
  435. self.saveSuccess(userCred, msg)
  436. logclient.AddActionLogWithContext(ctx, self, logclient.ACT_IMAGE_SAVE, msg, userCred, true)
  437. }
  438. func (self *SImage) OnSaveTaskSuccess(task taskman.ITask, userCred mcclient.TokenCredential, msg string) {
  439. self.SetStatus(context.Background(), userCred, api.IMAGE_STATUS_SAVED, "save success")
  440. self.saveSuccess(userCred, msg)
  441. logclient.AddActionLogWithStartable(task, self, logclient.ACT_IMAGE_SAVE, msg, userCred, true)
  442. }
  443. func (self *SImage) saveSuccess(userCred mcclient.TokenCredential, msg string) {
  444. // do not set this status, until image converting complete
  445. // self.SetStatus(ctx,userCred, api.IMAGE_STATUS_ACTIVE, msg)
  446. db.OpsLog.LogEvent(self, db.ACT_SAVE, msg, userCred)
  447. }
  448. func (self *SImage) saveFailed(userCred mcclient.TokenCredential, msg jsonutils.JSONObject) {
  449. log.Errorf("saveFailed: %s", msg.String())
  450. self.SetStatus(context.Background(), userCred, api.IMAGE_STATUS_KILLED, msg.String())
  451. self.unprotectImage()
  452. db.OpsLog.LogEvent(self, db.ACT_SAVE_FAIL, msg, userCred)
  453. }
  454. func (self *SImage) saveImageFromStream(localPath string, reader io.Reader, totalSize int64, calChecksum bool) (*streamutils.SStreamProperty, error) {
  455. fp, err := os.Create(localPath)
  456. if err != nil {
  457. return nil, err
  458. }
  459. defer fp.Close()
  460. lastSaveTime := time.Now()
  461. return streamutils.StreamPipe2(reader, fp, calChecksum, func(saved int64, _ int64) {
  462. now := time.Now()
  463. if now.Sub(lastSaveTime) > 5*time.Second {
  464. self.saveSize(saved, totalSize)
  465. lastSaveTime = now
  466. }
  467. })
  468. }
  469. func (self *SImage) saveSize(newSize, totalSize int64) error {
  470. _, err := db.Update(self, func() error {
  471. self.Size = newSize
  472. if totalSize > 0 {
  473. self.Progress = float32(float64(newSize) / float64(totalSize) * 100.0)
  474. }
  475. return nil
  476. })
  477. if err != nil {
  478. return errors.Wrap(err, "Update size")
  479. }
  480. return nil
  481. }
  482. // Image always do probe and customize after save from stream
  483. func (self *SImage) SaveImageFromStream(reader io.Reader, totalSize int64, calChecksum bool) error {
  484. localPath := self.GetLocalPath("")
  485. err := func() error {
  486. sp, err := self.saveImageFromStream(localPath, reader, totalSize, calChecksum)
  487. if err != nil {
  488. return errors.Wrapf(err, "saveImageFromStream")
  489. }
  490. virtualSizeBytes := int64(0)
  491. format := ""
  492. img, err := qemuimg.NewQemuImage(localPath)
  493. if err != nil {
  494. return errors.Wrapf(err, "NewQemuImage %s", localPath)
  495. }
  496. format = string(img.String2ImageFormat())
  497. virtualSizeBytes = img.SizeBytes
  498. var fastChksum string
  499. if calChecksum {
  500. fastChksum, err = fileutils2.FastCheckSum(localPath)
  501. if err != nil {
  502. return errors.Wrapf(err, "FastCheckSum %s", localPath)
  503. }
  504. }
  505. _, err = db.Update(self, func() error {
  506. self.Size = sp.Size
  507. if calChecksum {
  508. self.Checksum = sp.CheckSum
  509. self.FastHash = fastChksum
  510. }
  511. self.Location = fmt.Sprintf("%s%s", LocalFilePrefix, localPath)
  512. if len(format) > 0 {
  513. self.DiskFormat = format
  514. }
  515. if virtualSizeBytes > 0 {
  516. self.MinDiskMB = int32(math.Ceil(float64(virtualSizeBytes) / 1024 / 1024))
  517. }
  518. return nil
  519. })
  520. if err != nil {
  521. return errors.Wrapf(err, "db.Update")
  522. }
  523. return nil
  524. }()
  525. if err != nil {
  526. if fileutils2.IsFile(localPath) {
  527. if e := os.Remove(localPath); e != nil {
  528. log.Errorf("remove failed file %s error: %v", localPath, err)
  529. }
  530. }
  531. }
  532. return err
  533. }
  534. func (self *SImage) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  535. self.SSharableVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
  536. // if SImage belong to a guest image, pending quota will not be set.
  537. if self.IsGuestImage.IsFalse() {
  538. pendingUsage := SQuota{Image: 1}
  539. inputDiskFormat, _ := data.GetString("disk_format")
  540. keys := imageCreateInput2QuotaKeys(inputDiskFormat, ownerId)
  541. pendingUsage.SetKeys(keys)
  542. cancelUsage := SQuota{Image: 1}
  543. keys = self.GetQuotaKeys()
  544. cancelUsage.SetKeys(keys)
  545. quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &cancelUsage, true)
  546. }
  547. detectedProperties, err := ImagePropertyManager.GetProperties(self.Id)
  548. if err == nil {
  549. if osArch := detectedProperties[api.IMAGE_OS_ARCH]; strings.Contains(osArch, "aarch") {
  550. props, _ := data.Get("properties")
  551. if props != nil {
  552. dict := props.(*jsonutils.JSONDict)
  553. dict.Set(api.IMAGE_OS_ARCH, jsonutils.NewString(osArch))
  554. }
  555. db.Update(self, func() error {
  556. self.OsArch = apis.OS_ARCH_AARCH64
  557. return nil
  558. })
  559. }
  560. }
  561. if data.Contains("properties") {
  562. // update properties
  563. props, _ := data.Get("properties")
  564. err := ImagePropertyManager.SaveProperties(ctx, userCred, self.Id, props)
  565. if err != nil {
  566. log.Warningf("save properties error %s", err)
  567. }
  568. }
  569. appParams := appsrv.AppContextGetParams(ctx)
  570. if appParams.Request.ContentLength > 0 {
  571. db.OpsLog.LogEvent(self, db.ACT_SAVING, "create upload", userCred)
  572. self.SetStatus(ctx, userCred, api.IMAGE_STATUS_SAVING, "create upload")
  573. err := self.SaveImageFromStream(appParams.Request.Body, appParams.Request.ContentLength, false)
  574. if err != nil {
  575. self.OnSaveFailed(ctx, userCred, jsonutils.NewString(fmt.Sprintf("create upload fail %s", err)))
  576. return
  577. }
  578. self.OnSaveSuccess(ctx, userCred, "create upload success")
  579. self.StartImagePipeline(ctx, userCred, false)
  580. } else {
  581. copyFrom := appParams.Request.Header.Get(modules.IMAGE_META_COPY_FROM)
  582. compress := appParams.Request.Header.Get(modules.IMAGE_META_COMPRESS_FORMAT)
  583. if len(copyFrom) > 0 {
  584. self.startImageCopyFromUrlTask(ctx, userCred, copyFrom, compress, "")
  585. }
  586. }
  587. }
  588. // After image probe and customization, image size and checksum changed
  589. // will recalculate checksum in the end
  590. func (image *SImage) StartImagePipeline(
  591. ctx context.Context, userCred mcclient.TokenCredential, skipProbe bool,
  592. ) error {
  593. data := jsonutils.NewDict()
  594. if skipProbe {
  595. data.Set("skip_probe", jsonutils.JSONTrue)
  596. }
  597. task, err := taskman.TaskManager.NewTask(
  598. ctx, "ImagePipelineTask", image, userCred, data, "", "", nil)
  599. if err != nil {
  600. return err
  601. }
  602. task.ScheduleRun(nil)
  603. return nil
  604. }
  605. func (img *SImage) ValidateUpdateData(
  606. ctx context.Context,
  607. userCred mcclient.TokenCredential,
  608. query jsonutils.JSONObject,
  609. input api.ImageUpdateInput,
  610. ) (api.ImageUpdateInput, error) {
  611. if img.Status != api.IMAGE_STATUS_QUEUED {
  612. if !img.CanUpdate(input) {
  613. return input, httperrors.NewForbiddenError("image is the part of guest imgae")
  614. }
  615. appParams := appsrv.AppContextGetParams(ctx)
  616. if appParams != nil && appParams.Request.ContentLength > 0 {
  617. return input, httperrors.NewInvalidStatusError("cannot upload in status %s", img.Status)
  618. }
  619. if input.MinDiskMB != nil && *input.MinDiskMB > 0 && img.DiskFormat != string(qemuimgfmt.ISO) {
  620. img, err := qemuimg.NewQemuImage(img.GetLocalLocation())
  621. if err != nil {
  622. return input, errors.Wrap(err, "open image")
  623. }
  624. virtualSizeMB := img.SizeBytes / 1024 / 1024
  625. if virtualSizeMB > 0 && *input.MinDiskMB < int32(virtualSizeMB) {
  626. return input, httperrors.NewBadRequestError("min disk size must >= %v", virtualSizeMB)
  627. }
  628. }
  629. } else {
  630. appParams := appsrv.AppContextGetParams(ctx)
  631. if appParams != nil {
  632. // always probe
  633. // if self.IsData.IsTrue() {
  634. // isProbe = false
  635. // }
  636. if appParams.Request.ContentLength > 0 {
  637. // upload image
  638. img.SetStatus(ctx, userCred, api.IMAGE_STATUS_SAVING, "update start upload")
  639. // If isProbe is true calculating checksum is not necessary wheng saving from stream,
  640. // otherwise, it is needed.
  641. err := img.SaveImageFromStream(appParams.Request.Body, appParams.Request.ContentLength, img.IsData.IsFalse())
  642. if err != nil {
  643. img.OnSaveFailed(ctx, userCred, jsonutils.NewString(fmt.Sprintf("update upload failed %s", err)))
  644. return input, httperrors.NewGeneralError(err)
  645. }
  646. img.OnSaveSuccess(ctx, userCred, "update upload success")
  647. // data.Remove("status")
  648. // For guest image, DoConvertAfterProbe is not necessary.
  649. img.StartImagePipeline(ctx, userCred, false)
  650. } else {
  651. copyFrom := appParams.Request.Header.Get(modules.IMAGE_META_COPY_FROM)
  652. compress := appParams.Request.Header.Get(modules.IMAGE_META_COMPRESS_FORMAT)
  653. if len(copyFrom) > 0 {
  654. err := img.startImageCopyFromUrlTask(ctx, userCred, copyFrom, compress, "")
  655. if err != nil {
  656. img.OnSaveFailed(ctx, userCred, jsonutils.NewString(fmt.Sprintf("update copy from url failed %s", err)))
  657. return input, httperrors.NewGeneralError(err)
  658. }
  659. }
  660. }
  661. }
  662. }
  663. var err error
  664. input.SharableVirtualResourceBaseUpdateInput, err = img.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.SharableVirtualResourceBaseUpdateInput)
  665. if err != nil {
  666. return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData")
  667. }
  668. return input, nil
  669. }
  670. func (self *SImage) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  671. self.SSharableVirtualResourceBase.PreUpdate(ctx, userCred, query, data)
  672. }
  673. func (self *SImage) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  674. self.SSharableVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
  675. if data.Contains("properties") {
  676. // update properties
  677. props, _ := data.Get("properties")
  678. err := ImagePropertyManager.SaveProperties(ctx, userCred, self.Id, props)
  679. if err != nil {
  680. log.Errorf("save properties error %s", err)
  681. }
  682. }
  683. }
  684. func (self *SImage) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
  685. overridePendingDelete := false
  686. purge := false
  687. if query != nil {
  688. overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false)
  689. purge = jsonutils.QueryBoolean(query, "purge", false)
  690. }
  691. if (overridePendingDelete || purge) && !db.IsAdminAllowDelete(ctx, userCred, self) {
  692. return false
  693. }
  694. return self.IsOwner(userCred) || db.IsAdminAllowDelete(ctx, userCred, self)
  695. }
  696. func (img *SImage) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
  697. if img.Protected.IsTrue() {
  698. return httperrors.NewForbiddenError("image is protected")
  699. }
  700. if img.IsStandard.IsTrue() {
  701. return httperrors.NewForbiddenError("image is standard")
  702. }
  703. guestImgCnt, err := img.getGuestImageCount()
  704. if err != nil {
  705. return errors.Wrap(err, "getGuestImageCount")
  706. }
  707. if img.IsGuestImage.IsTrue() || guestImgCnt > 0 {
  708. return httperrors.NewForbiddenError("image is the part of guest image")
  709. }
  710. // if self.IsShared() {
  711. // return httperrors.NewForbiddenError("image is shared")
  712. // }
  713. return img.SSharableVirtualResourceBase.ValidateDeleteCondition(ctx, nil)
  714. }
  715. func (self *SImage) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  716. log.Infof("image delete do nothing")
  717. return nil
  718. }
  719. func (self *SImage) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
  720. return self.SSharableVirtualResourceBase.Delete(ctx, userCred)
  721. }
  722. func (self *SImage) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  723. overridePendingDelete := false
  724. purge := false
  725. if query != nil {
  726. overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false)
  727. purge = jsonutils.QueryBoolean(query, "purge", false)
  728. }
  729. if utils.IsInStringArray(self.Status, []string{
  730. api.IMAGE_STATUS_KILLED,
  731. api.IMAGE_STATUS_QUEUED,
  732. }) {
  733. overridePendingDelete = true
  734. }
  735. return self.startDeleteImageTask(ctx, userCred, "", purge, overridePendingDelete)
  736. }
  737. func (self *SImage) startDeleteImageTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge bool, overridePendingDelete bool) error {
  738. params := jsonutils.NewDict()
  739. if isPurge {
  740. params.Add(jsonutils.JSONTrue, "purge")
  741. }
  742. if overridePendingDelete {
  743. params.Add(jsonutils.JSONTrue, "override_pending_delete")
  744. }
  745. params.Add(jsonutils.NewString(self.Status), "image_status")
  746. self.SetStatus(ctx, userCred, api.IMAGE_STATUS_DEACTIVATED, "")
  747. task, err := taskman.TaskManager.NewTask(ctx, "ImageDeleteTask", self, userCred, params, parentTaskId, "", nil)
  748. if err != nil {
  749. return err
  750. }
  751. task.ScheduleRun(nil)
  752. return nil
  753. }
  754. func (self *SImage) startImageCopyFromUrlTask(ctx context.Context, userCred mcclient.TokenCredential, copyFrom, compress string, parentTaskId string) error {
  755. params := jsonutils.NewDict()
  756. params.Add(jsonutils.NewString(copyFrom), "copy_from")
  757. params.Add(jsonutils.NewString(compress), "compress_format")
  758. msg := fmt.Sprintf("copy from url %s", copyFrom)
  759. if len(compress) > 0 {
  760. msg += " " + compress
  761. }
  762. self.SetStatus(ctx, userCred, api.IMAGE_STATUS_SAVING, msg)
  763. db.OpsLog.LogEvent(self, db.ACT_SAVING, msg, userCred)
  764. task, err := taskman.TaskManager.NewTask(ctx, "ImageCopyFromUrlTask", self, userCred, params, parentTaskId, "", nil)
  765. if err != nil {
  766. return err
  767. }
  768. task.ScheduleRun(nil)
  769. return nil
  770. }
  771. func (self *SImage) StartImageCheckTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
  772. task, err := taskman.TaskManager.NewTask(ctx, "ImageCheckTask", self, userCred, nil, parentTaskId, "", nil)
  773. if err != nil {
  774. return err
  775. }
  776. return task.ScheduleRun(nil)
  777. }
  778. func (self *SImage) StartPutImageTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
  779. task, err := taskman.TaskManager.NewTask(ctx, "PutImageTask", self, userCred, nil, parentTaskId, "", nil)
  780. if err != nil {
  781. return err
  782. }
  783. return task.ScheduleRun(nil)
  784. }
  785. func (self *SImage) PerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  786. if self.PendingDeleted && !self.Deleted {
  787. err := self.DoCancelPendingDelete(ctx, userCred)
  788. if err != nil {
  789. return nil, errors.Wrap(err, "DoCancelPendingDelete")
  790. }
  791. self.RecoverUsages(ctx, userCred)
  792. }
  793. return nil, nil
  794. }
  795. func (manager *SImageManager) getExpiredPendingDeleteDisks() []SImage {
  796. deadline := time.Now().Add(time.Duration(options.Options.PendingDeleteExpireSeconds*-1) * time.Second)
  797. q := manager.Query()
  798. // those images part of guest image will be clean in GuestImageManager.CleanPendingDeleteImages
  799. q = q.IsTrue("pending_deleted").LT("pending_deleted_at",
  800. deadline).Limit(options.Options.PendingDeleteMaxCleanBatchSize).IsFalse("is_guest_image")
  801. disks := make([]SImage, 0)
  802. err := db.FetchModelObjects(ImageManager, q, &disks)
  803. if err != nil {
  804. log.Errorf("fetch disks error %s", err)
  805. return nil
  806. }
  807. return disks
  808. }
  809. func (manager *SImageManager) CleanPendingDeleteImages(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
  810. disks := manager.getExpiredPendingDeleteDisks()
  811. if disks == nil {
  812. return
  813. }
  814. for i := 0; i < len(disks); i += 1 {
  815. // clean pendingdelete so that overridePendingDelete is true
  816. disks[i].startDeleteImageTask(ctx, userCred, "", false, true)
  817. }
  818. }
  819. func (self *SImage) DoPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
  820. err := self.SSharableVirtualResourceBase.DoPendingDelete(ctx, userCred)
  821. if err != nil {
  822. return err
  823. }
  824. _, err = db.Update(self, func() error {
  825. self.Status = api.IMAGE_STATUS_PENDING_DELETE
  826. return nil
  827. })
  828. return err
  829. }
  830. func (self *SImage) DoCancelPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
  831. err := self.SSharableVirtualResourceBase.DoCancelPendingDelete(ctx, userCred)
  832. if err != nil {
  833. return err
  834. }
  835. _, err = db.Update(self, func() error {
  836. self.Status = api.IMAGE_STATUS_ACTIVE
  837. return nil
  838. })
  839. return err
  840. }
  841. type SImageUsage struct {
  842. Count int64
  843. Size int64
  844. }
  845. func (manager *SImageManager) count(ctx context.Context, scope rbacscope.TRbacScope, ownerId mcclient.IIdentityProvider, status string, isISO tristate.TriState, pendingDelete bool, guestImage tristate.TriState, policyResult rbacutils.SPolicyResult) map[string]SImageUsage {
  846. sq := manager.Query("id")
  847. sq = db.ObjectIdQueryWithPolicyResult(ctx, sq, manager, policyResult)
  848. switch scope {
  849. case rbacscope.ScopeSystem:
  850. // do nothing
  851. case rbacscope.ScopeDomain:
  852. sq = sq.Equals("domain_id", ownerId.GetProjectDomainId())
  853. case rbacscope.ScopeProject:
  854. sq = sq.Equals("tenant_id", ownerId.GetProjectId())
  855. }
  856. // exclude GuestImage!!!
  857. if guestImage.IsTrue() {
  858. sq = sq.IsTrue("is_guest_image")
  859. } else if guestImage.IsFalse() {
  860. sq = sq.IsFalse("is_guest_image")
  861. }
  862. if len(status) > 0 {
  863. sq = sq.Equals("status", status)
  864. }
  865. sq = sq.NotEquals("status", api.IMAGE_STATUS_KILLED)
  866. if pendingDelete {
  867. sq = sq.IsTrue("pending_deleted")
  868. } else {
  869. sq = sq.IsFalse("pending_deleted")
  870. }
  871. if isISO.IsTrue() {
  872. sq = sq.Equals("disk_format", string(qemuimgfmt.ISO))
  873. } else if isISO.IsFalse() {
  874. sq = sq.NotEquals("disk_format", string(qemuimgfmt.ISO))
  875. }
  876. cnt, _ := sq.CountWithError()
  877. subimages := ImageSubformatManager.Query().SubQuery()
  878. q := subimages.Query(subimages.Field("format"),
  879. sqlchemy.COUNT("count"),
  880. sqlchemy.SUM("size", subimages.Field("size")))
  881. q = q.In("image_id", sq.SubQuery())
  882. q = q.GroupBy(subimages.Field("format"))
  883. type sFormatImageUsage struct {
  884. Format string
  885. Count int64
  886. Size int64
  887. }
  888. var usages []sFormatImageUsage
  889. err := q.All(&usages)
  890. if err != nil {
  891. log.Errorf("query usage fail %s", err)
  892. return nil
  893. }
  894. ret := make(map[string]SImageUsage)
  895. totalSize := int64(0)
  896. for _, u := range usages {
  897. ret[u.Format] = SImageUsage{Count: u.Count, Size: u.Size}
  898. totalSize += u.Size
  899. }
  900. ret["total"] = SImageUsage{Count: int64(cnt), Size: totalSize}
  901. return ret
  902. }
  903. func expandUsageCount(usages map[string]int64, prefix, imgType, state string, count map[string]SImageUsage) {
  904. for k, u := range count {
  905. key := []string{}
  906. if len(prefix) > 0 {
  907. key = append(key, prefix)
  908. }
  909. key = append(key, imgType)
  910. if len(state) > 0 {
  911. key = append(key, state)
  912. }
  913. key = append(key, k)
  914. countKey := strings.Join(append(key, "count"), ".")
  915. sizeKey := strings.Join(append(key, "size"), ".")
  916. usages[countKey] = u.Count
  917. usages[sizeKey] = u.Size
  918. }
  919. }
  920. func (manager *SImageManager) Usage(ctx context.Context, scope rbacscope.TRbacScope, ownerId mcclient.IIdentityProvider, prefix string, policyResult rbacutils.SPolicyResult) map[string]int64 {
  921. usages := make(map[string]int64)
  922. count := manager.count(ctx, scope, ownerId, api.IMAGE_STATUS_ACTIVE, tristate.False, false, tristate.False, policyResult)
  923. expandUsageCount(usages, prefix, "img", "", count)
  924. count = manager.count(ctx, scope, ownerId, api.IMAGE_STATUS_ACTIVE, tristate.True, false, tristate.False, policyResult)
  925. expandUsageCount(usages, prefix, string(qemuimgfmt.ISO), "", count)
  926. count = manager.count(ctx, scope, ownerId, api.IMAGE_STATUS_ACTIVE, tristate.None, false, tristate.False, policyResult)
  927. expandUsageCount(usages, prefix, "imgiso", "", count)
  928. count = manager.count(ctx, scope, ownerId, "", tristate.False, true, tristate.False, policyResult)
  929. expandUsageCount(usages, prefix, "img", "pending_delete", count)
  930. count = manager.count(ctx, scope, ownerId, "", tristate.True, true, tristate.False, policyResult)
  931. expandUsageCount(usages, prefix, string(qemuimgfmt.ISO), "pending_delete", count)
  932. count = manager.count(ctx, scope, ownerId, "", tristate.None, true, tristate.False, policyResult)
  933. expandUsageCount(usages, prefix, "imgiso", "pending_delete", count)
  934. return usages
  935. }
  936. func (self *SImage) GetImageType() api.TImageType {
  937. if self.DiskFormat == string(qemuimgfmt.ISO) {
  938. return api.ImageTypeISO
  939. } else if self.DiskFormat == api.IMAGE_DISK_FORMAT_TGZ {
  940. return api.ImageTypeTarGzip
  941. } else {
  942. return api.ImageTypeTemplate
  943. }
  944. }
  945. func (self *SImage) newSubformat(ctx context.Context, format qemuimgfmt.TImageFormat, migrate bool) error {
  946. subformat := &SImageSubformat{}
  947. subformat.SetModelManager(ImageSubformatManager, subformat)
  948. subformat.ImageId = self.Id
  949. subformat.Format = string(format)
  950. if migrate {
  951. subformat.Size = self.Size
  952. subformat.Checksum = self.Checksum
  953. subformat.FastHash = self.FastHash
  954. // saved successfully
  955. subformat.Status = api.IMAGE_STATUS_ACTIVE
  956. subformat.Location = self.Location
  957. } else {
  958. subformat.Status = api.IMAGE_STATUS_QUEUED
  959. }
  960. subformat.TorrentStatus = api.IMAGE_STATUS_QUEUED
  961. err := ImageSubformatManager.TableSpec().Insert(ctx, subformat)
  962. if err != nil {
  963. log.Errorf("fail to make subformat %s: %s", format, err)
  964. return err
  965. }
  966. return nil
  967. }
  968. func (img *SImage) migrateSubImage(ctx context.Context) error {
  969. log.Debugf("migrateSubImage")
  970. if !qemuimgfmt.IsSupportedImageFormat(img.DiskFormat) {
  971. log.Warningf("Unsupported image format %s, no need to migrate", img.DiskFormat)
  972. return nil
  973. }
  974. subimg := ImageSubformatManager.FetchSubImage(img.Id, img.DiskFormat)
  975. if subimg != nil {
  976. return nil
  977. }
  978. imgInst, err := img.getQemuImage()
  979. if err != nil {
  980. return errors.Wrap(err, "getQemuImage")
  981. }
  982. if img.GetImageType() != api.ImageTypeISO && imgInst.IsSparse() && utils.IsInStringArray(img.DiskFormat, options.Options.TargetImageFormats) {
  983. // need to convert again
  984. log.Debugf("migrateImage: image is not iso but sparse, need to convert the image")
  985. return img.newSubformat(ctx, qemuimgfmt.String2ImageFormat(img.DiskFormat), false)
  986. } else {
  987. log.Debugf("migrateImage: no need to convert the image")
  988. localPath := img.GetLocalLocation()
  989. if !strings.HasSuffix(localPath, fmt.Sprintf(".%s", img.DiskFormat)) {
  990. newLocalpath := fmt.Sprintf("%s.%s", localPath, img.DiskFormat)
  991. out, err := procutils.NewCommand("mv", "-f", localPath, newLocalpath).Output()
  992. if err != nil {
  993. return errors.Wrapf(err, "rename file failed %s", out)
  994. }
  995. _, err = db.Update(img, func() error {
  996. img.Location = img.GetNewLocation(newLocalpath)
  997. return nil
  998. })
  999. if err != nil {
  1000. return err
  1001. }
  1002. }
  1003. return img.newSubformat(ctx, qemuimgfmt.String2ImageFormat(img.DiskFormat), true)
  1004. }
  1005. }
  1006. func (img *SImage) isEncrypted() bool {
  1007. return img.DiskFormat == string(qemuimgfmt.QCOW2) && len(img.EncryptKeyId) > 0
  1008. }
  1009. func (self *SImage) makeSubImages(ctx context.Context) error {
  1010. if self.GetImageType() == api.ImageTypeISO || self.GetImageType() == api.ImageTypeTarGzip {
  1011. // do not convert iso
  1012. return nil
  1013. }
  1014. if self.isEncrypted() {
  1015. // do not convert encrypted qcow2
  1016. return nil
  1017. }
  1018. log.Debugf("[MakeSubImages] convert image to %#v", options.Options.TargetImageFormats)
  1019. for _, format := range options.Options.TargetImageFormats {
  1020. if !qemuimgfmt.IsSupportedImageFormat(format) {
  1021. continue
  1022. }
  1023. if format != self.DiskFormat {
  1024. // need to create a record
  1025. subformat := ImageSubformatManager.FetchSubImage(self.Id, format)
  1026. if subformat == nil {
  1027. err := self.newSubformat(ctx, qemuimgfmt.String2ImageFormat(format), false)
  1028. if err != nil {
  1029. return err
  1030. }
  1031. }
  1032. }
  1033. }
  1034. return nil
  1035. }
  1036. func (self *SImage) doConvertAllSubformats() error {
  1037. subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
  1038. for i := 0; i < len(subimgs); i += 1 {
  1039. if subimgs[i].Status == api.IMAGE_STATUS_ACTIVE {
  1040. continue
  1041. }
  1042. if !utils.IsInStringArray(subimgs[i].Format, options.Options.TargetImageFormats) {
  1043. // cleanup
  1044. continue
  1045. }
  1046. err := subimgs[i].doConvert(self)
  1047. if err != nil {
  1048. return errors.Wrap(err, "")
  1049. }
  1050. }
  1051. return nil
  1052. }
  1053. func (self *SImage) GetLocalLocation() string {
  1054. if strings.HasPrefix(self.Location, api.LocalFilePrefix) {
  1055. return self.Location[len(api.LocalFilePrefix):]
  1056. } else if strings.HasPrefix(self.Location, api.S3Prefix) {
  1057. return path.Join(options.Options.S3MountPoint, self.Location[len(api.S3Prefix):])
  1058. } else {
  1059. return ""
  1060. }
  1061. }
  1062. func (self *SImage) GetPrefix() string {
  1063. if strings.HasPrefix(self.Location, api.LocalFilePrefix) {
  1064. return api.LocalFilePrefix
  1065. } else if strings.HasPrefix(self.Location, api.S3Prefix) {
  1066. return api.S3Prefix
  1067. } else {
  1068. return api.LocalFilePrefix
  1069. }
  1070. }
  1071. func (self *SImage) GetNewLocation(newLocalPath string) string {
  1072. if strings.HasPrefix(self.Location, api.S3Prefix) {
  1073. return api.S3Prefix + path.Base(newLocalPath)
  1074. } else {
  1075. return api.LocalFilePrefix + newLocalPath
  1076. }
  1077. }
  1078. func (self *SImage) getQemuImage() (*qemuimg.SQemuImage, error) {
  1079. return qemuimg.NewQemuImageWithIOLevel(self.GetLocalLocation(), qemuimg.IONiceIdle)
  1080. }
  1081. func (self *SImage) StopTorrents() {
  1082. subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
  1083. for i := 0; i < len(subimgs); i += 1 {
  1084. subimgs[i].StopTorrent()
  1085. }
  1086. }
  1087. func (self *SImage) seedTorrents() {
  1088. subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
  1089. for i := 0; i < len(subimgs); i += 1 {
  1090. subimgs[i].seedTorrent(self.Id)
  1091. }
  1092. }
  1093. func (self *SImage) RemoveFile() error {
  1094. filePath := self.GetLocalLocation()
  1095. if len(filePath) == 0 {
  1096. filePath = self.GetPath("")
  1097. }
  1098. if len(filePath) > 0 && fileutils2.IsFile(filePath) {
  1099. return os.Remove(filePath)
  1100. }
  1101. return nil
  1102. }
  1103. func (self *SImage) Remove(ctx context.Context, userCred mcclient.TokenCredential) error {
  1104. subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
  1105. for i := 0; i < len(subimgs); i += 1 {
  1106. err := subimgs[i].cleanup(ctx, userCred)
  1107. if err != nil {
  1108. return errors.Wrapf(err, "remove subimg %s", subimgs[i].GetName())
  1109. }
  1110. }
  1111. // 考虑镜像下载中断情况
  1112. if len(self.Location) == 0 || strings.HasPrefix(self.Location, LocalFilePrefix) {
  1113. return self.RemoveFile()
  1114. } else {
  1115. return RemoveImage(ctx, self.Location)
  1116. }
  1117. }
  1118. func (manager *SImageManager) getAllAliveImages() []SImage {
  1119. images := make([]SImage, 0)
  1120. q := manager.Query().NotIn("status", api.ImageDeadStatus)
  1121. err := db.FetchModelObjects(manager, q, &images)
  1122. if err != nil {
  1123. log.Errorf("fail to query active images %s", err)
  1124. return nil
  1125. }
  1126. return images
  1127. }
  1128. func CheckImages(ctx context.Context) {
  1129. images := ImageManager.getAllAliveImages()
  1130. for i := 0; i < len(images); i += 1 {
  1131. log.Debugf("convert image subformats %s", images[i].Name)
  1132. images[i].StartImageCheckTask(ctx, auth.AdminCredential(), "")
  1133. }
  1134. }
  1135. func (self *SImage) GetDetailsSubformats(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  1136. subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
  1137. ret := make([]SImageSubformatDetails, len(subimgs))
  1138. for i := 0; i < len(subimgs); i += 1 {
  1139. ret[i] = subimgs[i].GetDetails()
  1140. }
  1141. return jsonutils.Marshal(ret), nil
  1142. }
  1143. // 磁盘镜像列表
  1144. func (manager *SImageManager) ListItemFilter(
  1145. ctx context.Context,
  1146. q *sqlchemy.SQuery,
  1147. userCred mcclient.TokenCredential,
  1148. query api.ImageListInput,
  1149. ) (*sqlchemy.SQuery, error) {
  1150. q, err := manager.SSharableVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query.SharableVirtualResourceListInput)
  1151. if err != nil {
  1152. return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ListItemFilter")
  1153. }
  1154. q, err = manager.SMultiArchResourceBaseManager.ListItemFilter(ctx, q, userCred, query.MultiArchResourceBaseListInput)
  1155. if err != nil {
  1156. return nil, errors.Wrap(err, "SMultiArchResourceBaseManager.ListItemFilter")
  1157. }
  1158. if len(query.DiskFormats) > 0 {
  1159. q = q.In("disk_format", query.DiskFormats)
  1160. }
  1161. if len(query.SubFormats) > 0 {
  1162. sq := ImageSubformatManager.Query().SubQuery()
  1163. q = q.Join(sq, sqlchemy.Equals(sq.Field("image_id"), q.Field("id"))).Filter(sqlchemy.In(sq.Field("format"), query.SubFormats))
  1164. }
  1165. if query.Uefi != nil && *query.Uefi {
  1166. imagePropertyQ := ImagePropertyManager.Query().
  1167. Equals("name", api.IMAGE_UEFI_SUPPORT).Equals("value", "true").SubQuery()
  1168. q = q.Join(imagePropertyQ, sqlchemy.Equals(q.Field("id"), imagePropertyQ.Field("image_id")))
  1169. }
  1170. if query.IsStandard != nil {
  1171. if *query.IsStandard {
  1172. q = q.IsTrue("is_standard")
  1173. } else {
  1174. q = q.IsFalse("is_standard")
  1175. }
  1176. }
  1177. if query.Protected != nil {
  1178. if *query.Protected {
  1179. q = q.IsTrue("protected")
  1180. } else {
  1181. q = q.IsFalse("protected")
  1182. }
  1183. }
  1184. if query.IsGuestImage != nil {
  1185. if *query.IsGuestImage {
  1186. q = q.IsTrue("is_guest_image")
  1187. } else {
  1188. q = q.IsFalse("is_guest_image")
  1189. }
  1190. }
  1191. if query.IsData != nil {
  1192. if *query.IsData {
  1193. q = q.IsTrue("is_data")
  1194. } else {
  1195. q = q.IsFalse("is_data")
  1196. }
  1197. }
  1198. propFilter := func(nameKeys []string, vals []string, presiceMatch bool) {
  1199. if len(vals) == 0 {
  1200. return
  1201. }
  1202. propQ := ImagePropertyManager.Query().In("name", nameKeys)
  1203. conds := make([]sqlchemy.ICondition, 0)
  1204. for _, val := range vals {
  1205. field := propQ.Field("value")
  1206. if presiceMatch {
  1207. conds = append(conds, sqlchemy.Equals(field, val))
  1208. } else {
  1209. conds = append(conds,
  1210. sqlchemy.Like(field, val),
  1211. sqlchemy.Contains(field, val))
  1212. }
  1213. }
  1214. propQ.Filter(sqlchemy.OR(conds...))
  1215. propSq := propQ.SubQuery()
  1216. q = q.Join(propSq, sqlchemy.Equals(q.Field("id"), propSq.Field("image_id"))).Distinct()
  1217. }
  1218. propFilter([]string{api.IMAGE_OS_ARCH}, query.OsArchs, query.OsArchPreciseMatch)
  1219. propFilter([]string{api.IMAGE_OS_TYPE}, query.OsTypes, query.OsTypePreciseMatch)
  1220. propFilter([]string{api.IMAGE_OS_DISTRO, "distro"}, query.Distributions, query.DistributionPreciseMatch)
  1221. return q, nil
  1222. }
  1223. func (manager *SImageManager) OrderByExtraFields(
  1224. ctx context.Context,
  1225. q *sqlchemy.SQuery,
  1226. userCred mcclient.TokenCredential,
  1227. query api.ImageListInput,
  1228. ) (*sqlchemy.SQuery, error) {
  1229. var err error
  1230. q, err = manager.SSharableVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.SharableVirtualResourceListInput)
  1231. if err != nil {
  1232. return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.OrderByExtraFields")
  1233. }
  1234. return q, nil
  1235. }
  1236. func (manager *SImageManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  1237. var err error
  1238. q, err = manager.SSharableVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
  1239. if err == nil {
  1240. return q, nil
  1241. }
  1242. return q, httperrors.ErrNotFound
  1243. }
  1244. type sUnactiveReason int
  1245. const (
  1246. FileNoExists sUnactiveReason = iota
  1247. FileSizeMismatch
  1248. FileChecksumMismatch
  1249. Others
  1250. )
  1251. func isActive(localPath string, size int64, chksum string, fastHash string, useFastHash bool, noChecksum bool) (bool, sUnactiveReason) {
  1252. if len(localPath) == 0 || !fileutils2.Exists(localPath) {
  1253. log.Errorf("invalid file: %s", localPath)
  1254. return false, FileNoExists
  1255. }
  1256. if size != fileutils2.FileSize(localPath) {
  1257. log.Errorf("size mistmatch: %s", localPath)
  1258. return false, FileSizeMismatch
  1259. }
  1260. if len(chksum) == 0 || len(fastHash) == 0 || noChecksum {
  1261. return true, Others
  1262. }
  1263. if useFastHash && len(fastHash) > 0 {
  1264. fhash, err := fileutils2.FastCheckSum(localPath)
  1265. if err != nil {
  1266. log.Errorf("IsActive fastChecksum fail %s for %s", err, localPath)
  1267. return false, Others
  1268. }
  1269. if fastHash != fhash {
  1270. log.Errorf("IsActive fastChecksum mismatch for %s", localPath)
  1271. return false, FileChecksumMismatch
  1272. }
  1273. } else {
  1274. md5sum, err := fileutils2.MD5(localPath)
  1275. if err != nil {
  1276. log.Errorf("IsActive md5 fail %s for %s", err, localPath)
  1277. return false, Others
  1278. }
  1279. if chksum != md5sum {
  1280. log.Errorf("IsActive checksum mismatch: %s", localPath)
  1281. return false, FileChecksumMismatch
  1282. }
  1283. }
  1284. return true, Others
  1285. }
  1286. func (self *SImage) IsIso() bool {
  1287. return self.DiskFormat == string(api.ImageTypeISO)
  1288. }
  1289. func (image *SImage) isActive(useFast bool, noChecksum bool) bool {
  1290. active, reason := isActive(image.GetLocalLocation(), image.Size, image.Checksum, image.FastHash, useFast, noChecksum)
  1291. if active || reason != FileChecksumMismatch {
  1292. return active
  1293. }
  1294. data := jsonutils.NewDict()
  1295. data.Set("name", jsonutils.NewString(image.Name))
  1296. notifyclient.SystemExceptionNotifyWithResult(context.TODO(), noapi.ActionChecksumTest, noapi.TOPIC_RESOURCE_IMAGE, noapi.ResultFailed, data)
  1297. return false
  1298. }
  1299. func (image *SImage) DoCheckStatus(ctx context.Context, userCred mcclient.TokenCredential, useFast bool) {
  1300. if utils.IsInStringArray(image.Status, api.ImageDeadStatus) {
  1301. return
  1302. }
  1303. if IsCheckStatusEnabled(image) {
  1304. if image.isActive(useFast, true) {
  1305. if image.Status != api.IMAGE_STATUS_ACTIVE {
  1306. image.SetStatus(ctx, userCred, api.IMAGE_STATUS_ACTIVE, "check active")
  1307. }
  1308. if len(image.FastHash) == 0 {
  1309. fastHash, err := fileutils2.FastCheckSum(image.GetLocalLocation())
  1310. if err != nil {
  1311. log.Errorf("DoCheckStatus fileutils2.FastChecksum fail %s", err)
  1312. } else {
  1313. _, err := db.Update(image, func() error {
  1314. image.FastHash = fastHash
  1315. return nil
  1316. })
  1317. if err != nil {
  1318. log.Errorf("DoCheckStatus save FastHash fail %s", err)
  1319. }
  1320. }
  1321. }
  1322. img, err := qemuimg.NewQemuImage(image.GetLocalLocation())
  1323. if err == nil {
  1324. format := string(img.String2ImageFormat())
  1325. virtualSizeMB := int32(img.SizeBytes / 1024 / 1024)
  1326. if (len(format) > 0 && image.DiskFormat != format) || (virtualSizeMB > 0 && image.MinDiskMB != virtualSizeMB) {
  1327. db.Update(image, func() error {
  1328. if len(format) > 0 {
  1329. image.DiskFormat = format
  1330. }
  1331. if virtualSizeMB > 0 && image.MinDiskMB < virtualSizeMB {
  1332. image.MinDiskMB = virtualSizeMB
  1333. }
  1334. return nil
  1335. })
  1336. }
  1337. } else {
  1338. log.Warningf("fail to check image size of %s(%s)", image.Id, image.Name)
  1339. }
  1340. } else {
  1341. if image.Status != api.IMAGE_STATUS_QUEUED {
  1342. image.SetStatus(ctx, userCred, api.IMAGE_STATUS_QUEUED, "check inactive")
  1343. }
  1344. }
  1345. }
  1346. if image.Status == api.IMAGE_STATUS_ACTIVE {
  1347. image.StartImagePipeline(ctx, userCred, true)
  1348. }
  1349. }
  1350. func (self *SImage) PerformMarkStandard(
  1351. ctx context.Context,
  1352. userCred mcclient.TokenCredential,
  1353. query jsonutils.JSONObject,
  1354. data jsonutils.JSONObject,
  1355. ) (jsonutils.JSONObject, error) {
  1356. if self.IsGuestImage.IsTrue() {
  1357. return nil, errors.Wrap(httperrors.ErrForbidden, "cannot mark standard to a guest image")
  1358. }
  1359. isStandard := jsonutils.QueryBoolean(data, "is_standard", false)
  1360. if !self.IsStandard.IsTrue() && isStandard {
  1361. input := apis.PerformPublicProjectInput{}
  1362. input.Scope = "system"
  1363. _, err := self.PerformPublic(ctx, userCred, query, input)
  1364. if err != nil {
  1365. return nil, errors.Wrap(err, "PerformPublic")
  1366. }
  1367. diff, err := db.Update(self, func() error {
  1368. self.IsStandard = tristate.True
  1369. return nil
  1370. })
  1371. if err != nil {
  1372. return nil, httperrors.NewGeneralError(err)
  1373. }
  1374. db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
  1375. } else if self.IsStandard.IsTrue() && !isStandard {
  1376. diff, err := db.Update(self, func() error {
  1377. self.IsStandard = tristate.False
  1378. return nil
  1379. })
  1380. if err != nil {
  1381. return nil, httperrors.NewGeneralError(err)
  1382. }
  1383. db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
  1384. }
  1385. return nil, nil
  1386. }
  1387. func (self *SImage) PerformUpdateTorrentStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  1388. formatStr, _ := query.GetString("format")
  1389. if len(formatStr) == 0 {
  1390. return nil, httperrors.NewMissingParameterError("format")
  1391. }
  1392. subimg := ImageSubformatManager.FetchSubImage(self.Id, formatStr)
  1393. if subimg == nil {
  1394. return nil, httperrors.NewResourceNotFoundError("format %s not found", formatStr)
  1395. }
  1396. subimg.SetStatusSeeding(true)
  1397. return nil, nil
  1398. }
  1399. func (self *SImage) CanUpdate(input api.ImageUpdateInput) bool {
  1400. dict := jsonutils.Marshal(input).(*jsonutils.JSONDict)
  1401. // Only allow update description for now when Image is part of guest image
  1402. return self.IsGuestImage.IsFalse() || (dict.Length() == 1 && dict.Contains("description"))
  1403. }
  1404. func (img *SImage) GetQuotaKeys() quotas.IQuotaKeys {
  1405. keys := SImageQuotaKeys{}
  1406. keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(rbacscope.ScopeProject, img.GetOwnerId())
  1407. if img.GetImageType() == api.ImageTypeISO {
  1408. keys.Type = string(api.ImageTypeISO)
  1409. } else {
  1410. keys.Type = string(api.ImageTypeTemplate)
  1411. }
  1412. return keys
  1413. }
  1414. func imageCreateInput2QuotaKeys(format string, ownerId mcclient.IIdentityProvider) quotas.IQuotaKeys {
  1415. keys := SImageQuotaKeys{}
  1416. keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(rbacscope.ScopeProject, ownerId)
  1417. if format == string(api.ImageTypeISO) {
  1418. keys.Type = string(api.ImageTypeISO)
  1419. } else if len(format) > 0 {
  1420. keys.Type = string(api.ImageTypeTemplate)
  1421. }
  1422. return keys
  1423. }
  1424. func (img *SImage) GetUsages() []db.IUsage {
  1425. if img.PendingDeleted || img.Deleted {
  1426. return nil
  1427. }
  1428. usage := SQuota{Image: 1}
  1429. keys := img.GetQuotaKeys()
  1430. usage.SetKeys(keys)
  1431. return []db.IUsage{
  1432. &usage,
  1433. }
  1434. }
  1435. func (img *SImage) PerformUpdateStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ImageUpdateStatusInput) (jsonutils.JSONObject, error) {
  1436. if !utils.IsInStringArray(input.Status, api.ImageDeadStatus) {
  1437. return nil, httperrors.NewBadRequestError("can't udpate image to status %s, must in %v", input.Status, api.ImageDeadStatus)
  1438. }
  1439. _, err := db.Update(img, func() error {
  1440. img.Status = input.Status
  1441. return nil
  1442. })
  1443. if err != nil {
  1444. return nil, errors.Wrap(err, "udpate image status")
  1445. }
  1446. db.OpsLog.LogEvent(img, db.ACT_UPDATE_STATUS, input.Reason, userCred)
  1447. logclient.AddSimpleActionLog(img, logclient.ACT_UPDATE_STATUS, input.Reason, userCred, true)
  1448. return nil, nil
  1449. }
  1450. func (img *SImage) PerformSetClassMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformSetClassMetadataInput) (jsonutils.JSONObject, error) {
  1451. ret, err := img.SStandaloneAnonResourceBase.PerformSetClassMetadata(ctx, userCred, query, input)
  1452. if err != nil {
  1453. return ret, err
  1454. }
  1455. task, err := taskman.TaskManager.NewTask(ctx, "ImageSyncClassMetadataTask", img, userCred, nil, "", "", nil)
  1456. if err != nil {
  1457. return nil, err
  1458. } else {
  1459. task.ScheduleRun(nil)
  1460. }
  1461. return nil, nil
  1462. }
  1463. func (img *SImage) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicProjectInput) (jsonutils.JSONObject, error) {
  1464. if img.IsGuestImage.IsTrue() {
  1465. return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform public for guest image")
  1466. }
  1467. if img.EncryptStatus != api.IMAGE_ENCRYPT_STATUS_UNENCRYPTED {
  1468. return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform public for encrypted image")
  1469. }
  1470. return img.performPublic(ctx, userCred, query, input)
  1471. }
  1472. func (img *SImage) performPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicProjectInput) (jsonutils.JSONObject, error) {
  1473. if img.IsStandard.IsTrue() {
  1474. return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform public for standard image")
  1475. }
  1476. return img.SSharableVirtualResourceBase.PerformPublic(ctx, userCred, query, input)
  1477. }
  1478. func (img *SImage) performPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) {
  1479. if img.IsStandard.IsTrue() {
  1480. return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform private for standard image")
  1481. }
  1482. return img.SSharableVirtualResourceBase.PerformPrivate(ctx, userCred, query, input)
  1483. }
  1484. func (img *SImage) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) {
  1485. if img.IsGuestImage.IsTrue() {
  1486. return nil, errors.Wrap(httperrors.ErrForbidden, "cannot perform private for guest image")
  1487. }
  1488. return img.performPrivate(ctx, userCred, query, input)
  1489. }
  1490. func (img *SImage) PerformProbe(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.PerformProbeInput) (jsonutils.JSONObject, error) {
  1491. if img.Status != api.IMAGE_STATUS_ACTIVE && img.Status != api.IMAGE_STATUS_SAVED {
  1492. return nil, httperrors.NewInvalidStatusError("cannot probe in status %s", img.Status)
  1493. }
  1494. img.SetStatus(ctx, userCred, api.IMAGE_STATUS_PROBING, "perform probe")
  1495. err := img.StartImagePipeline(ctx, userCred, false)
  1496. if err != nil {
  1497. return nil, errors.Wrap(err, "ImageProbeAndCustomization")
  1498. }
  1499. return nil, nil
  1500. }
  1501. func (img *SImage) PerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformChangeProjectOwnerInput) (jsonutils.JSONObject, error) {
  1502. ret, err := img.SVirtualResourceBase.PerformChangeOwner(ctx, userCred, query, input)
  1503. if err != nil {
  1504. return nil, err
  1505. }
  1506. task, err := taskman.TaskManager.NewTask(ctx, "ImageSyncClassMetadataTask", img, userCred, nil, "", "", nil)
  1507. if err != nil {
  1508. return nil, err
  1509. } else {
  1510. task.ScheduleRun(nil)
  1511. }
  1512. return ret, nil
  1513. }
  1514. func UpdateImageConfigTargetImageFormats(ctx context.Context, userCred mcclient.TokenCredential) error {
  1515. s := auth.GetSession(ctx, userCred, options.Options.Region)
  1516. serviceId, err := common_options.GetServiceIdByType(s, api.SERVICE_TYPE, "")
  1517. if err != nil {
  1518. return errors.Wrap(err, "get service id")
  1519. }
  1520. defConf, err := common_options.GetServiceConfig(s, serviceId)
  1521. if err != nil {
  1522. return errors.Wrap(err, "GetServiceConfig")
  1523. }
  1524. targetFormats := make([]string, 0)
  1525. err = defConf.Unmarshal(&targetFormats, "target_image_formats")
  1526. if err != nil {
  1527. return errors.Wrap(err, "get target_image_formats")
  1528. }
  1529. if !utils.IsInStringArray(string(qemuimgfmt.VMDK), targetFormats) {
  1530. targetFormats = append(targetFormats, string(qemuimgfmt.VMDK))
  1531. }
  1532. defConfDict := defConf.(*jsonutils.JSONDict)
  1533. defConfDict.Set("target_image_formats", jsonutils.NewStringArray(targetFormats))
  1534. nconf := jsonutils.NewDict()
  1535. nconf.Add(defConfDict, "config", "default")
  1536. _, err = identity_modules.ServicesV3.PerformAction(s, serviceId, "config", nconf)
  1537. if err != nil {
  1538. return errors.Wrap(err, "fail to save config")
  1539. }
  1540. return nil
  1541. }
  1542. func (m *SImageManager) PerformVmwareAccountAdded(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformChangeProjectOwnerInput) (jsonutils.JSONObject, error) {
  1543. log.Infof("perform vmware account added")
  1544. if !utils.IsInStringArray(string(qemuimgfmt.VMDK), options.Options.TargetImageFormats) {
  1545. if err := UpdateImageConfigTargetImageFormats(ctx, userCred); err != nil {
  1546. log.Errorf("failed update target_image_formats %s", err)
  1547. } else {
  1548. options.Options.TargetImageFormats = append(options.Options.TargetImageFormats, string(qemuimgfmt.VMDK))
  1549. }
  1550. }
  1551. return nil, nil
  1552. }
  1553. /*func (image *SImage) getRealPath() string {
  1554. diskPath := image.GetPath("")
  1555. if !fileutils2.Exists(diskPath) {
  1556. diskPath = image.GetPath(image.DiskFormat)
  1557. if !fileutils2.Exists(diskPath) {
  1558. return ""
  1559. }
  1560. }
  1561. return diskPath
  1562. }*/
  1563. func (image *SImage) doProbeImageInfo(ctx context.Context, userCred mcclient.TokenCredential) (bool, error) {
  1564. if image.IsIso() {
  1565. imagePath := image.GetLocalLocation()
  1566. if len(imagePath) == 0 {
  1567. return false, errors.Wrapf(httperrors.ErrNotFound, "image file %s not found", image.Location)
  1568. }
  1569. fp, err := os.Open(imagePath)
  1570. if err != nil {
  1571. return false, errors.Wrap(err, "Open image file")
  1572. }
  1573. defer fp.Close()
  1574. isoInfo, err := isoutils.DetectOSFromISO(fp)
  1575. if err != nil {
  1576. return false, errors.Wrap(err, "DetectOSFromISO")
  1577. }
  1578. err = image.updateIsoInfo(ctx, userCred, isoInfo)
  1579. if err != nil {
  1580. return false, errors.Wrap(err, "updateIsoInfo")
  1581. }
  1582. return true, nil
  1583. }
  1584. if image.IsData.IsTrue() {
  1585. // no need to probe
  1586. return false, nil
  1587. }
  1588. diskPath := image.GetLocalLocation()
  1589. if len(diskPath) == 0 {
  1590. return false, errors.Wrap(httperrors.ErrNotFound, "disk file not found")
  1591. }
  1592. if deployclient.GetDeployClient() == nil {
  1593. return false, fmt.Errorf("deploy client not init")
  1594. }
  1595. diskInfo := &deployapi.DiskInfo{
  1596. Path: diskPath,
  1597. }
  1598. if image.IsEncrypted() {
  1599. key, err := image.GetEncryptInfo(ctx, userCred)
  1600. if err != nil {
  1601. return false, errors.Wrap(err, "GetEncryptInfo")
  1602. }
  1603. diskInfo.EncryptPassword = key.Key
  1604. diskInfo.EncryptAlg = string(key.Alg)
  1605. }
  1606. imageInfo, err := deployclient.GetDeployClient().ProbeImageInfo(ctx, &deployapi.ProbeImageInfoPramas{DiskInfo: diskInfo})
  1607. if err != nil {
  1608. return false, errors.Wrap(err, "ProbeImageInfo")
  1609. }
  1610. log.Infof("image probe info: %s", jsonutils.Marshal(imageInfo))
  1611. err = image.updateImageInfo(ctx, userCred, imageInfo)
  1612. if err != nil {
  1613. return false, errors.Wrap(err, "updateImageInfo")
  1614. }
  1615. return true, nil
  1616. }
  1617. func (image *SImage) updateImageInfo(
  1618. ctx context.Context,
  1619. userCred mcclient.TokenCredential,
  1620. imageInfo *deployapi.ImageInfo,
  1621. ) error {
  1622. if imageInfo.OsInfo == nil || gotypes.IsNil(imageInfo.OsInfo) {
  1623. log.Warningln("imageInfo.OsInfo is empty!")
  1624. return nil
  1625. }
  1626. db.Update(image, func() error {
  1627. image.OsArch = imageInfo.OsInfo.Arch
  1628. return nil
  1629. })
  1630. imageProperties := jsonutils.Marshal(imageInfo.OsInfo).(*jsonutils.JSONDict)
  1631. imageProperties.Set(api.IMAGE_OS_ARCH, jsonutils.NewString(imageInfo.OsInfo.Arch))
  1632. imageProperties.Set(api.IMAGE_OS_VERSION, jsonutils.NewString(imageInfo.OsInfo.Version))
  1633. imageProperties.Set(api.IMAGE_OS_DISTRO, jsonutils.NewString(imageInfo.OsInfo.Distro))
  1634. imageProperties.Set(api.IMAGE_OS_LANGUAGE, jsonutils.NewString(imageInfo.OsInfo.Language))
  1635. if imageInfo.OsInfo.CurrentVersion != "" {
  1636. imageProperties.Set(api.IMAGE_OS_CURRENT_VERSION, jsonutils.NewString(imageInfo.OsInfo.CurrentVersion))
  1637. }
  1638. imageProperties.Set(api.IMAGE_OS_TYPE, jsonutils.NewString(imageInfo.OsType))
  1639. imageProperties.Set(api.IMAGE_PARTITION_TYPE, jsonutils.NewString(imageInfo.PhysicalPartitionType))
  1640. imageProperties.Set(api.IMAGE_UEFI_SUPPORT, jsonutils.NewBool(imageInfo.IsUefiSupport))
  1641. imageProperties.Set(api.IMAGE_BIOS_SUPPORT, jsonutils.NewBool(imageInfo.IsBiosSupport))
  1642. imageProperties.Set(api.IMAGE_IS_LVM_PARTITION, jsonutils.NewBool(imageInfo.IsLvmPartition))
  1643. imageProperties.Set(api.IMAGE_IS_READONLY, jsonutils.NewBool(imageInfo.IsReadonly))
  1644. imageProperties.Set(api.IMAGE_INSTALLED_CLOUDINIT, jsonutils.NewBool(imageInfo.IsInstalledCloudInit))
  1645. return ImagePropertyManager.SaveProperties(ctx, userCred, image.Id, imageProperties)
  1646. }
  1647. func (image *SImage) updateIsoInfo(ctx context.Context, userCred mcclient.TokenCredential, imageInfo *isoutils.ISOInfo) error {
  1648. if gotypes.IsNil(imageInfo) || len(imageInfo.Distro) == 0 {
  1649. return nil
  1650. }
  1651. change := false
  1652. imageProperties := jsonutils.Marshal(imageInfo).(*jsonutils.JSONDict)
  1653. if len(imageInfo.Arch) > 0 {
  1654. imageProperties.Set(api.IMAGE_OS_ARCH, jsonutils.NewString(imageInfo.Arch))
  1655. change = true
  1656. db.Update(image, func() error {
  1657. image.OsArch = imageInfo.Arch
  1658. return nil
  1659. })
  1660. }
  1661. if len(imageInfo.Version) > 0 {
  1662. imageProperties.Set(api.IMAGE_OS_VERSION, jsonutils.NewString(imageInfo.Version))
  1663. change = true
  1664. }
  1665. if len(imageInfo.Distro) > 0 {
  1666. imageProperties.Set(api.IMAGE_OS_DISTRO, jsonutils.NewString(imageInfo.Distro))
  1667. change = true
  1668. }
  1669. if len(imageInfo.Language) > 0 {
  1670. imageProperties.Set(api.IMAGE_OS_LANGUAGE, jsonutils.NewString(imageInfo.Language))
  1671. change = true
  1672. }
  1673. if change {
  1674. return ImagePropertyManager.SaveProperties(ctx, userCred, image.Id, imageProperties)
  1675. }
  1676. return nil
  1677. }
  1678. func (image *SImage) updateChecksum() error {
  1679. imagePath := image.GetLocalLocation()
  1680. if len(imagePath) == 0 {
  1681. return errors.Wrapf(httperrors.ErrNotFound, "image file %s not found", image.Location)
  1682. }
  1683. fp, err := os.Open(imagePath)
  1684. if err != nil {
  1685. return err
  1686. }
  1687. defer fp.Close()
  1688. stat, err := fp.Stat()
  1689. if err != nil {
  1690. return errors.Wrapf(err, "stat %s", imagePath)
  1691. }
  1692. chksum, err := fileutils2.MD5(imagePath)
  1693. if err != nil {
  1694. return errors.Wrapf(err, "md5 %s", imagePath)
  1695. }
  1696. fastchksum, err := fileutils2.FastCheckSum(imagePath)
  1697. if err != nil {
  1698. return err
  1699. }
  1700. _, err = db.Update(image, func() error {
  1701. image.Size = stat.Size()
  1702. image.Checksum = chksum
  1703. image.FastHash = fastchksum
  1704. return nil
  1705. })
  1706. if err != nil {
  1707. return errors.Wrap(err, "Update image")
  1708. }
  1709. // also update the corresponding subformats
  1710. subimg := ImageSubformatManager.FetchSubImage(image.Id, image.DiskFormat)
  1711. if subimg != nil {
  1712. _, err = db.Update(subimg, func() error {
  1713. subimg.Size = stat.Size()
  1714. subimg.Checksum = chksum
  1715. subimg.FastHash = fastchksum
  1716. return nil
  1717. })
  1718. if err != nil {
  1719. return errors.Wrap(err, "Update subformat")
  1720. }
  1721. }
  1722. return nil
  1723. }
  1724. func (image *SImage) isLocal() bool {
  1725. return strings.HasPrefix(image.Location, LocalFilePrefix)
  1726. }
  1727. func (image *SImage) doUploadPermanentStorage(ctx context.Context, userCred mcclient.TokenCredential) (bool, error) {
  1728. uploaded := false
  1729. if image.isLocal() {
  1730. imagePath := image.GetLocalLocation()
  1731. image.SetStatus(ctx, userCred, api.IMAGE_STATUS_SAVING, "save image to specific storage")
  1732. storage := GetStorage()
  1733. location, err := storage.SaveImage(ctx, imagePath, nil)
  1734. if err != nil {
  1735. log.Errorf("Failed save image to specific storage %s", err)
  1736. errStr := fmt.Sprintf("save image to storage %s: %v", storage.Type(), err)
  1737. image.SetStatus(ctx, userCred, api.IMAGE_STATUS_SAVE_FAIL, errStr)
  1738. return false, errors.Wrapf(err, "save image to storage %s", storage.Type())
  1739. }
  1740. if location != image.Location {
  1741. uploaded = true
  1742. // save success! to update the location
  1743. _, err = db.Update(image, func() error {
  1744. image.Location = location
  1745. return nil
  1746. })
  1747. if err != nil {
  1748. log.Errorf("failed update image location %s", err)
  1749. return false, errors.Wrap(err, "update image location")
  1750. }
  1751. // update location success, remove local copy
  1752. if err = procutils.NewCommand("rm", "-f", imagePath).Run(); err != nil {
  1753. log.Errorf("failed remove file %s: %s", imagePath, err)
  1754. }
  1755. }
  1756. image.SetStatus(ctx, userCred, api.IMAGE_STATUS_ACTIVE, "save image to specific storage complete")
  1757. }
  1758. subimgs := ImageSubformatManager.GetAllSubImages(image.Id)
  1759. for i := 0; i < len(subimgs); i++ {
  1760. if !subimgs[i].isLocal() {
  1761. continue
  1762. }
  1763. if subimgs[i].Format == image.DiskFormat && subimgs[i].Checksum == image.Checksum {
  1764. _, err := db.Update(&subimgs[i], func() error {
  1765. subimgs[i].Location = image.Location
  1766. subimgs[i].Status = api.IMAGE_STATUS_ACTIVE
  1767. return nil
  1768. })
  1769. if err != nil {
  1770. log.Errorf("failed update subimg %s", err)
  1771. }
  1772. } else {
  1773. imagePath := subimgs[i].GetLocalLocation()
  1774. storage := GetStorage()
  1775. location, err := storage.SaveImage(ctx, imagePath, nil)
  1776. if err != nil {
  1777. log.Errorf("Failed save image to sepcific storage %s", err)
  1778. subimgs[i].SetStatus(api.IMAGE_STATUS_SAVE_FAIL)
  1779. return false, errors.Wrapf(err, "save sub image %s to storage %s", subimgs[i].Format, storage.Type())
  1780. } else if subimgs[i].Location != location {
  1781. uploaded = true
  1782. _, err := db.Update(&subimgs[i], func() error {
  1783. subimgs[i].Location = location
  1784. return nil
  1785. })
  1786. if err != nil {
  1787. log.Errorf("failed update subimg %s", err)
  1788. }
  1789. if err = procutils.NewCommand("rm", "-f", imagePath).Run(); err != nil {
  1790. log.Errorf("failed remove file %s: %s", imagePath, err)
  1791. }
  1792. }
  1793. db.Update(&subimgs[i], func() error {
  1794. subimgs[i].Status = api.IMAGE_STATUS_ACTIVE
  1795. return nil
  1796. })
  1797. }
  1798. }
  1799. return uploaded, nil
  1800. }
  1801. func (img *SImage) doConvert(ctx context.Context, userCred mcclient.TokenCredential) (bool, error) {
  1802. if img.IsGuestImage.IsTrue() {
  1803. // for image the part of a guest image, convert is not necessary.
  1804. return false, nil
  1805. }
  1806. needConvert := false
  1807. subimgs := ImageSubformatManager.GetAllSubImages(img.Id)
  1808. if len(subimgs) == 0 {
  1809. needConvert = true
  1810. } else {
  1811. supportedFormats := make([]string, 0)
  1812. for i := 0; i < len(subimgs); i += 1 {
  1813. if !utils.IsInStringArray(subimgs[i].Format, options.Options.TargetImageFormats) && subimgs[i].Format != img.DiskFormat {
  1814. // no need to have this subformat
  1815. err := subimgs[i].cleanup(ctx, userCred)
  1816. if err != nil {
  1817. return false, errors.Wrap(err, "cleanup sub image")
  1818. }
  1819. continue
  1820. }
  1821. subimgs[i].checkStatus(true, false)
  1822. if subimgs[i].Status != api.IMAGE_STATUS_ACTIVE {
  1823. needConvert = true
  1824. }
  1825. supportedFormats = append(supportedFormats, subimgs[i].Format)
  1826. }
  1827. if len(supportedFormats) < len(options.Options.TargetImageFormats) {
  1828. needConvert = true
  1829. }
  1830. }
  1831. log.Debugf("doConvert imageStatus %s %v", img.Status, needConvert)
  1832. if (img.Status == api.IMAGE_STATUS_SAVED || img.Status == api.IMAGE_STATUS_ACTIVE || img.Status == api.IMAGE_STATUS_PROBING) && needConvert {
  1833. err := img.migrateSubImage(ctx)
  1834. if err != nil {
  1835. return false, errors.Wrap(err, "migrateSubImage")
  1836. }
  1837. err = img.makeSubImages(ctx)
  1838. if err != nil {
  1839. return false, errors.Wrap(err, "makeSubImages")
  1840. }
  1841. err = img.doConvertAllSubformats()
  1842. if err != nil {
  1843. return false, errors.Wrap(err, "doConvertAllSubformats")
  1844. }
  1845. }
  1846. return needConvert, nil
  1847. }
  1848. func (img *SImage) doEncrypt(ctx context.Context, userCred mcclient.TokenCredential) (bool, error) {
  1849. if len(img.EncryptKeyId) == 0 {
  1850. return false, nil
  1851. }
  1852. if img.DiskFormat != string(qemuimgfmt.QCOW2) {
  1853. // only qcow2 support encryption
  1854. return false, nil
  1855. }
  1856. if img.EncryptStatus == api.IMAGE_ENCRYPT_STATUS_ENCRYPTED {
  1857. return false, nil
  1858. }
  1859. session := auth.GetSession(ctx, userCred, options.Options.Region)
  1860. keyObj, err := identity_modules.Credentials.GetById(session, img.EncryptKeyId, nil)
  1861. if err != nil {
  1862. return false, errors.Wrap(err, "GetByEncryptKeyId")
  1863. }
  1864. key, err := identity_modules.DecodeEncryptKey(keyObj)
  1865. if err != nil {
  1866. return false, errors.Wrap(err, "DecodeEncryptKey")
  1867. }
  1868. qemuImg, err := img.getQemuImage()
  1869. if err != nil {
  1870. return false, errors.Wrap(err, "getQemuImage")
  1871. }
  1872. var failMsg string
  1873. if qemuImg.Encrypted {
  1874. qemuImg.SetPassword(key.Key)
  1875. err = qemuImg.Check()
  1876. if err != nil {
  1877. failMsg = "check encrypted image fail"
  1878. }
  1879. } else {
  1880. img.setEncryptStatus(userCred, api.IMAGE_ENCRYPT_STATUS_ENCRYPTING, db.ACT_ENCRYPT_START, "start encrypt")
  1881. err = qemuImg.Convert2Qcow2(true, key.Key, qemuimg.EncryptFormatLuks, key.Alg)
  1882. if err != nil {
  1883. failMsg = "Convert1Qcow2 fail"
  1884. }
  1885. }
  1886. if err != nil {
  1887. img.setEncryptStatus(userCred, api.IMAGE_ENCRYPT_STATUS_UNENCRYPTED, db.ACT_ENCRYPT_FAIL, fmt.Sprintf("%s %s", failMsg, err))
  1888. return false, errors.Wrap(err, failMsg)
  1889. }
  1890. img.setEncryptStatus(userCred, api.IMAGE_ENCRYPT_STATUS_ENCRYPTED, db.ACT_ENCRYPT_DONE, "success")
  1891. return true, nil
  1892. }
  1893. func (img *SImage) setEncryptStatus(userCred mcclient.TokenCredential, status string, event string, reason string) error {
  1894. _, err := db.Update(img, func() error {
  1895. img.EncryptStatus = status
  1896. return nil
  1897. })
  1898. if err != nil {
  1899. return errors.Wrap(err, "update encrypted")
  1900. }
  1901. db.OpsLog.LogEvent(img, event, reason, userCred)
  1902. switch event {
  1903. case db.ACT_ENCRYPT_FAIL:
  1904. logclient.AddSimpleActionLog(img, logclient.ACT_ENCRYPTION, reason, userCred, false)
  1905. case db.ACT_ENCRYPT_DONE:
  1906. logclient.AddSimpleActionLog(img, logclient.ACT_ENCRYPTION, reason, userCred, true)
  1907. }
  1908. return nil
  1909. }
  1910. func (img *SImage) Pipeline(ctx context.Context, userCred mcclient.TokenCredential, skipProbe bool) error {
  1911. updated := false
  1912. needChecksum := false
  1913. // do probe
  1914. if !skipProbe {
  1915. alterd, err := img.doProbeImageInfo(ctx, userCred)
  1916. if err != nil {
  1917. log.Errorf("fail to doProbeImageInfo %s", err)
  1918. }
  1919. if alterd {
  1920. needChecksum = true
  1921. }
  1922. } else {
  1923. log.Debugf("skipProbe image...")
  1924. }
  1925. // do encrypt
  1926. {
  1927. altered, err := img.doEncrypt(ctx, userCred)
  1928. if err != nil {
  1929. return errors.Wrap(err, "doEncrypt")
  1930. }
  1931. if altered {
  1932. needChecksum = true
  1933. }
  1934. }
  1935. if needChecksum || len(img.Checksum) == 0 || len(img.FastHash) == 0 {
  1936. err := img.updateChecksum()
  1937. if err != nil {
  1938. return errors.Wrap(err, "updateChecksum")
  1939. }
  1940. }
  1941. {
  1942. // do convert
  1943. converted, err := img.doConvert(ctx, userCred)
  1944. if err != nil {
  1945. return errors.Wrap(err, "doConvert")
  1946. }
  1947. if converted {
  1948. updated = true
  1949. }
  1950. }
  1951. {
  1952. // do doUploadPermanent
  1953. uploaded, err := img.doUploadPermanentStorage(ctx, userCred)
  1954. if err != nil {
  1955. return errors.Wrap(err, "doUploadPermanentStorage")
  1956. }
  1957. if uploaded {
  1958. updated = true
  1959. }
  1960. }
  1961. {
  1962. // do cache to ceph storages
  1963. if img.GetImageType() != api.ImageTypeTarGzip {
  1964. img.cacheToCephStorages(ctx)
  1965. }
  1966. }
  1967. if img.Status != api.IMAGE_STATUS_ACTIVE {
  1968. img.SetStatus(ctx, userCred, api.IMAGE_STATUS_ACTIVE, "image pipeline complete")
  1969. }
  1970. if updated && img.IsGuestImage.IsFalse() {
  1971. kwargs := jsonutils.NewDict()
  1972. kwargs.Set("name", jsonutils.NewString(img.GetName()))
  1973. osType, err := ImagePropertyManager.GetProperty(img.Id, api.IMAGE_OS_TYPE)
  1974. if err == nil {
  1975. kwargs.Set("os_type", jsonutils.NewString(osType.Value))
  1976. }
  1977. notifyclient.SystemNotifyWithCtx(ctx, notify.NotifyPriorityNormal, notifyclient.IMAGE_ACTIVED, kwargs)
  1978. notifyclient.NotifyImportantWithCtx(ctx, []string{userCred.GetUserId()}, false, notifyclient.IMAGE_ACTIVED, kwargs)
  1979. notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
  1980. Obj: img,
  1981. Action: notifyclient.ActionCreate,
  1982. })
  1983. }
  1984. return nil
  1985. }
  1986. func (img *SImage) cacheToCephStorages(ctx context.Context) {
  1987. // skip if image converting
  1988. localPath := img.GetPath(img.DiskFormat)
  1989. if procutils.NewRemoteCommandAsFarAsPossible("sh", "-c",
  1990. fmt.Sprintf("ps -ef | grep [q]emu-img | grep convert | grep %s", localPath)) == nil {
  1991. log.Warningf("image %s has converting progress", img.Id)
  1992. return
  1993. }
  1994. cephStorages := GetCephStorages()
  1995. if cephStorages == nil || len(cephStorages.StorageIdConf) == 0 {
  1996. return
  1997. }
  1998. for fsid, storageIds := range cephStorages.CephFsidStorageId {
  1999. storageCachedImages := map[string]*cephutils.SImage{}
  2000. var cachedRbdimgStorageId string
  2001. for i := range storageIds {
  2002. storageConf := cephStorages.StorageIdConf[storageIds[i]]
  2003. rbdimg, err := img.getCephImage(storageConf, "")
  2004. if err != nil {
  2005. log.Errorf("failed get img %s by storage conf %#v: %s", img.Id, storageConf, err)
  2006. continue
  2007. }
  2008. if rbdimg != nil && cachedRbdimgStorageId == "" {
  2009. cachedRbdimgStorageId = storageIds[i]
  2010. }
  2011. if rbdimg != nil {
  2012. log.Infof("image %s has been cached at ceph pool: %s", img.Id, storageConf.Pool)
  2013. }
  2014. storageCachedImages[storageIds[i]] = rbdimg
  2015. }
  2016. if len(storageCachedImages) == 0 {
  2017. // ceph storage unreachable
  2018. log.Errorf("all of cpeh storage with fsid %s failed get ceph image", fsid)
  2019. continue
  2020. }
  2021. if cachedRbdimgStorageId == "" {
  2022. // do cache img to ceph storage
  2023. for storageId := range storageCachedImages {
  2024. storageConf := cephStorages.StorageIdConf[storageId]
  2025. imgTmpName := "image_cache_" + img.Id + ".tmp"
  2026. if !fileutils2.Exists(localPath) {
  2027. log.Errorf("image localpath %s not exist", localPath)
  2028. continue
  2029. }
  2030. // remove tmp image first
  2031. if err := img.removeCephImage(storageConf, imgTmpName); err != nil {
  2032. log.Errorf("remove existing tmp img %s failed: %s", imgTmpName, err)
  2033. continue
  2034. }
  2035. storageConfString := cephutils.CephConfString(
  2036. storageConf.MonHost,
  2037. storageConf.Key,
  2038. storageConf.RadosMonOpTimeout,
  2039. storageConf.RadosOsdOpTimeout,
  2040. storageConf.ClientMountTimeout,
  2041. )
  2042. rbdPath := fmt.Sprintf("rbd:%s/%s%s", storageConf.Pool, imgTmpName, storageConfString)
  2043. log.Infof("convert local image %s to rbd pool %s", img.Id, storageConf.Pool)
  2044. out, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuImg(),
  2045. "convert", "-W", "-m", "16", "-O", "raw", localPath, rbdPath).Output()
  2046. if err != nil {
  2047. log.Errorf("convert local image %s to rbd pool %s failed: %s %s", img.Id, storageConf.Pool, out, err)
  2048. continue
  2049. }
  2050. log.Infof("Success cached img %s to pool %s by convert", imgTmpName, storageConf.Pool)
  2051. rbdimg, err := img.getCephImage(storageConf, imgTmpName)
  2052. if err != nil {
  2053. log.Errorf("failed get ceph image %s after convert to ceph: %s", imgTmpName, err)
  2054. continue
  2055. } else if rbdimg == nil {
  2056. log.Errorf("failed get ceph image %s after convert to ceph, rbdimage not found", imgTmpName)
  2057. continue
  2058. } else {
  2059. imgName := "image_cache_" + img.Id
  2060. if err = img.renameCephImage(storageConf, imgTmpName, imgName); err != nil {
  2061. log.Errorf("failed rename from tmp image %s to %s: %s", imgTmpName, imgName, err)
  2062. continue
  2063. }
  2064. cachedRbdimgStorageId = storageId
  2065. delete(storageCachedImages, storageId)
  2066. break
  2067. }
  2068. }
  2069. }
  2070. if cachedRbdimgStorageId == "" {
  2071. log.Errorf("failed cache img %s to ceph storages fsid: %s", img.Id, fsid)
  2072. continue
  2073. }
  2074. for storageId := range storageCachedImages {
  2075. if storageId != cachedRbdimgStorageId && storageCachedImages[storageId] == nil {
  2076. srcConf := cephStorages.StorageIdConf[cachedRbdimgStorageId]
  2077. destConf := cephStorages.StorageIdConf[storageId]
  2078. err := img.cloneToCephStorage(ctx, srcConf.MonHost, srcConf.Key, srcConf.Pool, srcConf.EnableMessengerV2, destConf.Pool)
  2079. if err != nil {
  2080. log.Errorf("failed cache img %s to pool %s: %s", img.Id, destConf.Pool, err)
  2081. continue
  2082. }
  2083. log.Infof("Success cached img %s to pool %s by clone", img.Id, destConf.Pool)
  2084. }
  2085. }
  2086. }
  2087. }
  2088. func (img *SImage) removeCephImage(storageConf *computeapi.RbdStorageConf, imgName string) error {
  2089. cli, err := cephutils.NewClient(storageConf.MonHost, storageConf.Key, storageConf.Pool, storageConf.EnableMessengerV2, 0, 0, 0)
  2090. if err != nil {
  2091. return errors.Wrap(err, "cephutils.NewClient")
  2092. }
  2093. defer cli.Close()
  2094. rbdimg, err := cli.GetImage(imgName)
  2095. if err != nil {
  2096. if errors.Cause(err) == errors.ErrNotFound {
  2097. return nil
  2098. }
  2099. return errors.Wrapf(err, "GetImage")
  2100. }
  2101. return rbdimg.Remove()
  2102. }
  2103. func (img *SImage) renameCephImage(storageConf *computeapi.RbdStorageConf, srcImgName, destImgName string) error {
  2104. cli, err := cephutils.NewClient(storageConf.MonHost, storageConf.Key, storageConf.Pool, storageConf.EnableMessengerV2, 0, 0, 0)
  2105. if err != nil {
  2106. return errors.Wrap(err, "cephutils.NewClient")
  2107. }
  2108. defer cli.Close()
  2109. rbdimg, err := cli.GetImage(srcImgName)
  2110. if err != nil {
  2111. return errors.Wrapf(err, "GetImage")
  2112. }
  2113. return rbdimg.Rename(destImgName)
  2114. }
  2115. func (img *SImage) getCephImage(storageConf *computeapi.RbdStorageConf, imgName string) (*cephutils.SImage, error) {
  2116. if imgName == "" {
  2117. imgName = "image_cache_" + img.Id
  2118. }
  2119. cli, err := cephutils.NewClient(storageConf.MonHost, storageConf.Key, storageConf.Pool, storageConf.EnableMessengerV2, 0, 0, 0)
  2120. if err != nil {
  2121. return nil, errors.Wrap(err, "cephutils.NewClient")
  2122. }
  2123. defer cli.Close()
  2124. rbdimg, err := cli.GetImage(imgName)
  2125. if err != nil {
  2126. if errors.Cause(err) == errors.ErrNotFound {
  2127. return nil, nil
  2128. }
  2129. return nil, errors.Wrapf(err, "GetImage")
  2130. }
  2131. storageConfString := cephutils.CephConfString(
  2132. storageConf.MonHost,
  2133. storageConf.Key,
  2134. storageConf.RadosMonOpTimeout,
  2135. storageConf.RadosOsdOpTimeout,
  2136. storageConf.ClientMountTimeout,
  2137. )
  2138. rbdPath := fmt.Sprintf("rbd:%s/%s%s", storageConf.Pool, imgName, storageConfString)
  2139. origin, err := qemuimg.NewQemuImage(rbdPath)
  2140. if err != nil {
  2141. return nil, errors.Wrapf(err, "NewQemuImage %s", rbdPath)
  2142. }
  2143. if !origin.IsValid() {
  2144. return nil, errors.Errorf("rbd img %s is invalid", rbdPath)
  2145. }
  2146. return rbdimg, nil
  2147. }
  2148. func (img *SImage) cloneToCephStorage(ctx context.Context, monHost, key, pool string, enableMessengerV2 bool, destPool string) error {
  2149. imgName := "image_cache_" + img.Id
  2150. cli, err := cephutils.NewClient(monHost, key, pool, enableMessengerV2, 0, 0, 0)
  2151. if err != nil {
  2152. return errors.Wrap(err, "cephutils.NewClient")
  2153. }
  2154. defer cli.Close()
  2155. rbdimg, err := cli.GetImage(imgName)
  2156. if err != nil {
  2157. return errors.Wrap(err, "cli.GetImage(imgName)")
  2158. }
  2159. _, err = rbdimg.Clone(ctx, destPool, imgName)
  2160. if err != nil {
  2161. return errors.Wrapf(err, "rbdimg.Clone to destPool %s", destPool)
  2162. }
  2163. return nil
  2164. }
  2165. func (img *SImage) getGuestImageCount() (int, error) {
  2166. gis, err := GuestImageJointManager.GetByImageId(img.Id)
  2167. if err != nil {
  2168. return -1, errors.Wrap(err, "GuestImageJointManager.GetByImageId")
  2169. }
  2170. return len(gis), nil
  2171. }
  2172. func (img *SImage) markDataImage(userCred mcclient.TokenCredential) error {
  2173. if img.IsData.IsTrue() {
  2174. return nil
  2175. }
  2176. diff, err := db.Update(img, func() error {
  2177. img.IsData = tristate.True
  2178. return nil
  2179. })
  2180. if err != nil {
  2181. return errors.Wrap(err, "update")
  2182. }
  2183. db.OpsLog.LogEvent(img, db.ACT_UPDATE, diff, userCred)
  2184. return nil
  2185. }
  2186. func (manager *SImageManager) FetchImages(filter func(q *sqlchemy.SQuery) *sqlchemy.SQuery) ([]SImage, error) {
  2187. q := manager.Query()
  2188. if filter != nil {
  2189. q = filter(q)
  2190. }
  2191. images := make([]SImage, 0)
  2192. err := db.FetchModelObjects(manager, q, &images)
  2193. if err != nil {
  2194. return nil, errors.Wrap(err, "db.FetchModelObjects")
  2195. }
  2196. return images, nil
  2197. }
  2198. func (manager *SImageManager) VerifyActiveImageStatus(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
  2199. images, err := manager.FetchImages(func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  2200. return q.In("status", []string{api.IMAGE_STATUS_ACTIVE, api.IMAGE_STATUS_UNKNOWN})
  2201. })
  2202. if err != nil {
  2203. log.Errorf("FetchImages failed: %s", err)
  2204. return
  2205. }
  2206. for i := range images {
  2207. img := &images[i]
  2208. err := img.verifyStatus(ctx, userCred)
  2209. if err != nil {
  2210. log.Errorf("VerifyStatus %s(%s) failed: %s", img.Id, img.Name, err)
  2211. }
  2212. }
  2213. }
  2214. func (img *SImage) verifyStatus(ctx context.Context, userCred mcclient.TokenCredential) error {
  2215. errs := make([]error, 0)
  2216. subImages := ImageSubformatManager.GetAllSubImages(img.Id)
  2217. for i := range subImages {
  2218. err := subImages[i].verifyStatusSelf(ctx)
  2219. if err != nil {
  2220. errs = append(errs, err)
  2221. }
  2222. }
  2223. err := img.verifyStatusSelf(ctx, userCred)
  2224. if err != nil {
  2225. errs = append(errs, err)
  2226. }
  2227. if len(errs) > 0 {
  2228. return errors.NewAggregate(errs)
  2229. }
  2230. return nil
  2231. }
  2232. func (img *SImage) verifyStatusSelf(ctx context.Context, userCred mcclient.TokenCredential) error {
  2233. if len(img.Location) == 0 {
  2234. return nil
  2235. }
  2236. filePath := img.Location
  2237. _, rc, err := GetImage(ctx, filePath)
  2238. if err != nil {
  2239. img.SetStatus(ctx, userCred, api.IMAGE_STATUS_UNKNOWN, errors.Wrap(err, "verifyStatusSelf").Error())
  2240. return errors.Wrap(err, "GetImage")
  2241. }
  2242. defer rc.Close()
  2243. if img.Status != api.IMAGE_STATUS_ACTIVE {
  2244. img.SetStatus(ctx, userCred, api.IMAGE_STATUS_ACTIVE, "verifyStatusSelf")
  2245. }
  2246. return nil
  2247. }