guesthandler.go 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  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 guesthandlers
  15. import (
  16. "context"
  17. "fmt"
  18. "net/http"
  19. "strings"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/pkg/gotypes"
  23. "yunion.io/x/onecloud/pkg/apis"
  24. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  25. hostapi "yunion.io/x/onecloud/pkg/apis/host"
  26. schedapi "yunion.io/x/onecloud/pkg/apis/scheduler"
  27. "yunion.io/x/onecloud/pkg/appsrv"
  28. "yunion.io/x/onecloud/pkg/hostman/guestman"
  29. "yunion.io/x/onecloud/pkg/hostman/guestman/desc"
  30. "yunion.io/x/onecloud/pkg/hostman/hostutils"
  31. "yunion.io/x/onecloud/pkg/hostman/monitor"
  32. "yunion.io/x/onecloud/pkg/hostman/storageman"
  33. "yunion.io/x/onecloud/pkg/httperrors"
  34. "yunion.io/x/onecloud/pkg/mcclient"
  35. "yunion.io/x/onecloud/pkg/mcclient/auth"
  36. )
  37. type strDict map[string]string
  38. type actionFunc func(context.Context, mcclient.TokenCredential, string, jsonutils.JSONObject) (interface{}, error)
  39. var (
  40. keyWords = []string{"servers"}
  41. )
  42. func AddGuestTaskHandler(prefix string, app *appsrv.Application) {
  43. for _, keyWord := range keyWords {
  44. app.AddHandler("GET",
  45. fmt.Sprintf("%s/%s/<sid>/status", prefix, keyWord),
  46. auth.Authenticate(getStatus))
  47. app.AddHandler("POST",
  48. fmt.Sprintf("%s/%s/cpu-node-balance", prefix, keyWord),
  49. auth.Authenticate(cpusetBalance))
  50. app.AddHandler("POST",
  51. fmt.Sprintf("%s/%s/prepare-import-from-libvirt", prefix, keyWord),
  52. auth.Authenticate(guestPrepareImportFormLibvirt))
  53. app.AddHandler("POST",
  54. fmt.Sprintf("%s/%s/upload-status", prefix, keyWord),
  55. auth.Authenticate(uploadStatus))
  56. app.AddHandler("DELETE",
  57. fmt.Sprintf("%s/%s/<sid>", prefix, keyWord),
  58. auth.Authenticate(deleteGuest))
  59. for action, f := range map[string]actionFunc{
  60. "create": guestCreate,
  61. "deploy": guestDeploy,
  62. "rebuild": guestRebuild,
  63. "start": guestStart,
  64. "stop": guestStop,
  65. "monitor": guestMonitor,
  66. "sync": guestSync,
  67. "suspend": guestSuspend,
  68. "io-throttle": guestIoThrottle,
  69. "snapshot": guestSnapshot,
  70. "delete-snapshot": guestDeleteSnapshot,
  71. "reload-disk-snapshot": guestReloadDiskSnapshot,
  72. "src-prepare-migrate": guestSrcPrepareMigrate,
  73. "dest-prepare-migrate": guestDestPrepareMigrate,
  74. "live-migrate": guestLiveMigrate,
  75. "cancel-live-migrate": guestCancelLiveMigrate,
  76. "resume": guestResume,
  77. "block-replication": guestBlockReplication,
  78. "slave-block-stream-disks": slaveGuestBlockStreamDisks,
  79. "hotplug-cpu-mem": guestHotplugCpuMem,
  80. "cancel-block-jobs": guestCancelBlockJobs,
  81. "cancel-block-replication": guestCancelBlockReplication,
  82. "create-from-libvirt": guestCreateFromLibvirt,
  83. "create-form-esxi": guestCreateFromEsxi,
  84. "create-from-cloudpods": guestCreateFromCloudpods,
  85. "open-forward": guestOpenForward,
  86. "list-forward": guestListForward,
  87. "close-forward": guestCloseForward,
  88. "storage-clone-disk": guestStorageCloneDisk,
  89. "live-change-disk": guestLiveChangeDisk,
  90. "cpuset": guestCPUSet,
  91. "cpuset-remove": guestCPUSetRemove,
  92. "memory-snapshot": guestMemorySnapshot,
  93. "memory-snapshot-reset": guestMemorySnapshotReset,
  94. "qga-set-password": qgaGuestSetPassword,
  95. "qga-guest-ping": qgaGuestPing,
  96. "qga-command": qgaCommand,
  97. "reset-nic-traffic-limit": guestResetNicTrafficLimit,
  98. "set-nic-traffic-limit": guestSetNicTrafficLimit,
  99. "qga-guest-info-task": qgaGuestInfoTask,
  100. "qga-get-network": qgaGetNetwork,
  101. "qga-set-network": qgaSetNetwork,
  102. "qga-get-os-info": qgaGetOsInfo,
  103. "start-rescue": guestStartRescue,
  104. "guest-screen-dump": guestScreenDump,
  105. "upload-status": guestUploadStatus,
  106. } {
  107. app.AddHandler("POST",
  108. fmt.Sprintf("%s/%s/<sid>/%s", prefix, keyWord, action),
  109. auth.Authenticate(guestActions(f)),
  110. )
  111. }
  112. app.AddHandler("DELETE",
  113. fmt.Sprintf("%s/%s/memory-snapshot", prefix, keyWord),
  114. auth.Authenticate(guestMemorySnapshotDelete))
  115. }
  116. }
  117. func guestActions(f actionFunc) appsrv.FilterHandler {
  118. return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  119. params, _, body := appsrv.FetchEnv(ctx, w, r)
  120. userCred := auth.FetchUserCredential(ctx, nil)
  121. if body == nil {
  122. body = jsonutils.NewDict()
  123. }
  124. var sid = params["<sid>"]
  125. res, err := f(ctx, userCred, sid, body)
  126. if err != nil {
  127. hostutils.Response(ctx, w, err)
  128. } else if !gotypes.IsNil(res) {
  129. hostutils.Response(ctx, w, res)
  130. } else {
  131. hostutils.ResponseOk(ctx, w)
  132. }
  133. }
  134. }
  135. func getStatus(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  136. params, _, _ := appsrv.FetchEnv(ctx, w, r)
  137. sid := params["<sid>"]
  138. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().GetGuestStatus, sid)
  139. hostutils.ResponseOk(ctx, w)
  140. }
  141. func cpusetBalance(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  142. hostutils.DelayTask(ctx, guestman.GetGuestManager().CpusetBalance, nil)
  143. hostutils.ResponseOk(ctx, w)
  144. }
  145. func deleteGuest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  146. params, _, body := appsrv.FetchEnv(ctx, w, r)
  147. var sid = params["<sid>"]
  148. var migrated bool
  149. if body != nil {
  150. migrated = jsonutils.QueryBoolean(body, "migrated", false)
  151. }
  152. guest, err := guestman.GetGuestManager().Delete(sid)
  153. if err != nil {
  154. hostutils.Response(ctx, w, err)
  155. } else {
  156. hostutils.DelayTask(ctx, guest.CleanGuest, migrated)
  157. hostutils.Response(ctx, w, map[string]bool{"delay_clean": true})
  158. }
  159. }
  160. func guestCreate(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  161. if guestman.GetGuestManager().IsGuestExist(sid) {
  162. return nil, httperrors.NewBadRequestError("Guest %s is exist", sid)
  163. }
  164. hostutils.DelayTaskWithWorker(ctx,
  165. guestman.GetGuestManager().GuestCreate,
  166. &guestman.SGuestDeploy{
  167. UserCred: userCred,
  168. Sid: sid,
  169. Body: body,
  170. IsInit: true,
  171. },
  172. guestman.NbdWorker,
  173. )
  174. return nil, nil
  175. }
  176. func guestDeploy(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  177. err := guestman.GetGuestManager().PrepareDeploy(sid)
  178. if err != nil {
  179. return nil, err
  180. }
  181. hostutils.DelayTaskWithWorker(ctx,
  182. guestman.GetGuestManager().GuestDeploy,
  183. &guestman.SGuestDeploy{
  184. UserCred: userCred,
  185. Sid: sid,
  186. Body: body,
  187. IsInit: false,
  188. },
  189. guestman.NbdWorker,
  190. )
  191. return nil, nil
  192. }
  193. func guestRebuild(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  194. err := guestman.GetGuestManager().PrepareDeploy(sid)
  195. if err != nil {
  196. return nil, err
  197. }
  198. hostutils.DelayTaskWithWorker(ctx,
  199. guestman.GetGuestManager().GuestDeploy,
  200. &guestman.SGuestDeploy{
  201. UserCred: userCred,
  202. Sid: sid,
  203. Body: body,
  204. IsInit: true,
  205. },
  206. guestman.NbdWorker,
  207. )
  208. return nil, nil
  209. }
  210. func guestStart(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  211. return guestman.GetGuestManager().GuestStart(ctx, userCred, sid, body)
  212. }
  213. func guestStop(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  214. timeout, err := body.Int("timeout")
  215. if err != nil {
  216. timeout = 30
  217. }
  218. return nil, guestman.GetGuestManager().GuestStop(ctx, sid, timeout)
  219. }
  220. func guestMonitor(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  221. if !guestman.GetGuestManager().IsGuestExist(sid) {
  222. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  223. }
  224. if body.Contains("cmd") {
  225. var c = make(chan string)
  226. cb := func(res string) {
  227. c <- res
  228. }
  229. cmd, _ := body.GetString("cmd")
  230. qmp := jsonutils.QueryBoolean(body, "qmp", false)
  231. err := guestman.GetGuestManager().Monitor(sid, cmd, qmp, cb)
  232. if err != nil {
  233. return nil, err
  234. } else {
  235. var res = <-c
  236. lines := strings.Split(res, "\\r\\n")
  237. return strDict{"results": strings.Join(lines, "\n")}, nil
  238. }
  239. } else {
  240. return nil, httperrors.NewMissingParameterError("cmd")
  241. }
  242. }
  243. func guestSync(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  244. if !guestman.GetGuestManager().IsGuestExist(sid) {
  245. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  246. }
  247. hostutils.DelayTask(ctx, guestman.GetGuestManager().GuestSync, &guestman.SBaseParams{
  248. Sid: sid,
  249. Body: body,
  250. })
  251. return nil, nil
  252. }
  253. func guestSuspend(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  254. if !guestman.GetGuestManager().IsGuestExist(sid) {
  255. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  256. }
  257. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().GuestSuspend, sid)
  258. return nil, nil
  259. }
  260. func guestIoThrottle(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  261. guest, ok := guestman.GetGuestManager().GetServer(sid)
  262. if !ok {
  263. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  264. }
  265. if !guest.IsRunning() {
  266. return nil, httperrors.NewInvalidStatusError("Not running")
  267. }
  268. input := new(computeapi.ServerSetDiskIoThrottleInput)
  269. if err := body.Unmarshal(input); err != nil {
  270. return nil, httperrors.NewInputParameterError("unmarshal params failed %s", err)
  271. }
  272. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().GuestIoThrottle, &guestman.SGuestIoThrottle{
  273. Sid: sid,
  274. Input: input,
  275. })
  276. return nil, nil
  277. }
  278. func guestSrcPrepareMigrate(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  279. if !guestman.GetGuestManager().IsGuestExist(sid) {
  280. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  281. }
  282. liveMigrate := jsonutils.QueryBoolean(body, "live_migrate", false)
  283. liveMigrateEnableTls := jsonutils.QueryBoolean(body, "enable_tls", false)
  284. hostutils.DelayTask(ctx, guestman.GetGuestManager().SrcPrepareMigrate,
  285. &guestman.SSrcPrepareMigrate{
  286. Sid: sid,
  287. LiveMigrate: liveMigrate,
  288. LiveMigrateUseTLS: liveMigrateEnableTls,
  289. })
  290. return nil, nil
  291. }
  292. func guestDestPrepareMigrate(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  293. err := guestDestPrepareMigrateInternal(ctx, userCred, sid, body)
  294. if err != nil {
  295. guestman.GetGuestManager().CleanServer(sid)
  296. return nil, errors.Wrapf(err, "guestDestPrepareMigrateInternal %s", sid)
  297. }
  298. return nil, nil
  299. }
  300. func guestDestPrepareMigrateInternal(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) error {
  301. if !guestman.GetGuestManager().CanMigrate(sid) {
  302. return httperrors.NewBadRequestError("Guest exist")
  303. }
  304. var guestDesc = new(desc.SGuestDesc)
  305. err := body.Unmarshal(guestDesc, "desc")
  306. if err != nil {
  307. return httperrors.NewBadRequestError("Failed unmarshal guest desc %s", err)
  308. }
  309. var params = &guestman.SDestPrepareMigrate{}
  310. qemuVersion, err := body.GetString("qemu_version")
  311. if err != nil {
  312. return httperrors.NewMissingParameterError("qemu_version")
  313. }
  314. liveMigrate := jsonutils.QueryBoolean(body, "live_migrate", false)
  315. if liveMigrate {
  316. var sourceDesc = new(desc.SGuestDesc)
  317. err := body.Unmarshal(sourceDesc, "src_desc")
  318. if err != nil {
  319. return httperrors.NewBadRequestError("Failed unmarshal guest source desc %s", err)
  320. }
  321. params.SrcDesc = sourceDesc
  322. }
  323. isLocal, err := body.Bool("is_local_storage")
  324. if err != nil {
  325. return httperrors.NewMissingParameterError("is_local_storage")
  326. }
  327. params.Sid = sid
  328. params.Desc = guestDesc
  329. params.QemuVersion = qemuVersion
  330. params.LiveMigrate = liveMigrate
  331. params.EnableTLS = jsonutils.QueryBoolean(body, "enable_tls", false)
  332. if params.EnableTLS {
  333. certsObj, err := body.Get("migrate_certs")
  334. if err != nil {
  335. return httperrors.NewMissingParameterError("migrate_certs")
  336. }
  337. certs := map[string]string{}
  338. if err := certsObj.Unmarshal(&certs); err != nil {
  339. return httperrors.NewInputParameterError("unmarshal migrate_certs to map: %s", err)
  340. }
  341. params.MigrateCerts = certs
  342. }
  343. if isLocal {
  344. serverUrl, err := body.GetString("server_url")
  345. if err != nil {
  346. return httperrors.NewMissingParameterError("server_url")
  347. } else {
  348. params.ServerUrl = serverUrl
  349. }
  350. snapshotsUri, err := body.GetString("snapshots_uri")
  351. if err != nil {
  352. return httperrors.NewMissingParameterError("snapshots_uri")
  353. } else {
  354. params.SnapshotsUri = snapshotsUri
  355. }
  356. disksUri, err := body.GetString("disks_uri")
  357. if err != nil {
  358. return httperrors.NewMissingParameterError("disks_uri")
  359. } else {
  360. params.DisksUri = disksUri
  361. }
  362. diskSnapsChain, err := body.Get("disk_snaps_chain")
  363. if err != nil {
  364. params.DiskSnapsChain = jsonutils.NewDict()
  365. } else {
  366. params.DiskSnapsChain = diskSnapsChain
  367. }
  368. outChainSnaps, err := body.Get("out_chain_snapshots")
  369. if err != nil {
  370. params.OutChainSnaps = jsonutils.NewDict()
  371. } else {
  372. params.OutChainSnaps = outChainSnaps
  373. }
  374. params.SysDiskHasTemplate = jsonutils.QueryBoolean(body, "sys_disk_has_template", false)
  375. disksBack, err := body.Get("disks_back")
  376. if err != nil {
  377. params.DisksBackingFile = jsonutils.NewDict()
  378. } else {
  379. params.DisksBackingFile = disksBack
  380. }
  381. disks := guestDesc.Disks
  382. if disks == nil {
  383. return httperrors.NewInputParameterError("Get desc disks error")
  384. } else {
  385. targetStorageIds := []string{}
  386. for i := 0; i < len(disks); i++ {
  387. targetStorageId := disks[i].TargetStorageId
  388. if len(targetStorageId) == 0 {
  389. return httperrors.NewMissingParameterError("target_storage_id")
  390. }
  391. targetStorageIds = append(targetStorageIds, targetStorageId)
  392. params.TargetStorageIds = targetStorageIds
  393. }
  394. }
  395. params.RebaseDisks = jsonutils.QueryBoolean(body, "rebase_disks", false)
  396. }
  397. msUri, err := body.GetString("memory_snapshots_uri")
  398. if err != nil {
  399. return httperrors.NewMissingParameterError("memory_snapshots_uri")
  400. }
  401. params.MemorySnapshotsUri = msUri
  402. msIds, _ := jsonutils.GetStringArray(body, "src_memory_snapshots")
  403. params.SrcMemorySnapshots = msIds
  404. params.UserCred = userCred
  405. hostutils.DelayTask(ctx, guestman.GetGuestManager().DestPrepareMigrate, params)
  406. return nil
  407. }
  408. func guestLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  409. if !guestman.GetGuestManager().IsGuestExist(sid) {
  410. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  411. }
  412. destPort, err := body.Int("live_migrate_dest_port")
  413. if err != nil {
  414. return nil, httperrors.NewMissingParameterError("live_migrate_dest_port")
  415. }
  416. var nbdServerPort int64 = -1
  417. if body.Contains("nbd_server_port") {
  418. nbdServerPort, err = body.Int("nbd_server_port")
  419. if err != nil {
  420. return nil, httperrors.NewMissingParameterError("live_migrate_dest_port")
  421. }
  422. }
  423. destIp, err := body.GetString("dest_ip")
  424. if err != nil {
  425. return nil, httperrors.NewMissingParameterError("dest_ip")
  426. }
  427. isLocal, err := body.Bool("is_local_storage")
  428. if err != nil {
  429. return nil, httperrors.NewMissingParameterError("is_local_storage")
  430. }
  431. enableTLS := jsonutils.QueryBoolean(body, "enable_tls", false)
  432. quicklyFinish := jsonutils.QueryBoolean(body, "quickly_finish", false)
  433. params := &guestman.SLiveMigrate{
  434. Sid: sid,
  435. DestPort: int(destPort),
  436. NbdServerPort: int(nbdServerPort),
  437. DestIp: destIp,
  438. IsLocal: isLocal,
  439. EnableTLS: enableTLS,
  440. QuicklyFinish: quicklyFinish,
  441. }
  442. if body.Contains("max_bandwidth_mb") {
  443. maxBandwidthMb, _ := body.Int("max_bandwidth_mb")
  444. params.MaxBandwidthMB = &maxBandwidthMb
  445. }
  446. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().LiveMigrate, params)
  447. return nil, nil
  448. }
  449. func guestCancelLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  450. guest, ok := guestman.GetGuestManager().GetKVMServer(sid)
  451. if !ok {
  452. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  453. }
  454. if guest.MigrateTask == nil {
  455. return nil, httperrors.NewBadRequestError("Guest %s not in migrating", sid)
  456. }
  457. guest.MigrateTask.SetLiveMigrateCancelled()
  458. var c = make(chan string)
  459. cb := func(res string) {
  460. c <- res
  461. }
  462. guest.Monitor.MigrateCancel(cb)
  463. var res = <-c
  464. lines := strings.Split(res, "\\r\\n")
  465. return strDict{"results": strings.Join(lines, "\n")}, nil
  466. }
  467. func guestResume(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  468. if !guestman.GetGuestManager().IsGuestExist(sid) {
  469. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  470. }
  471. isLiveMigrate := jsonutils.QueryBoolean(body, "live_migrate", false)
  472. cleanTLS := jsonutils.QueryBoolean(body, "clean_tls", false)
  473. guestman.GetGuestManager().Resume(ctx, sid, isLiveMigrate, cleanTLS)
  474. return nil, nil
  475. }
  476. // func guestStartNbdServer(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) {
  477. // if !guestManger.IsGuestExist(sid) {
  478. // return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  479. // }
  480. // hostutils.DelayTask(ctx, guestManger.StartNbdServer, sid)
  481. // return nil, nil
  482. // }
  483. func guestBlockReplication(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  484. if !guestman.GetGuestManager().IsGuestExist(sid) {
  485. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  486. }
  487. backupNbdServerUri, err := body.GetString("backup_nbd_server_uri")
  488. if err != nil {
  489. return nil, httperrors.NewMissingParameterError("backup_nbd_server_uri")
  490. }
  491. var guestDesc = new(desc.SGuestDesc)
  492. err = body.Unmarshal(guestDesc, "desc")
  493. if err != nil {
  494. return nil, httperrors.NewInputParameterError("failed unmarshal desc %s", err)
  495. }
  496. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().StartBlockReplication,
  497. &guestman.SDriverMirror{
  498. Sid: sid,
  499. NbdServerUri: backupNbdServerUri,
  500. Desc: guestDesc,
  501. })
  502. return nil, nil
  503. }
  504. func slaveGuestBlockStreamDisks(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  505. guest, ok := guestman.GetGuestManager().GetKVMServer(sid)
  506. if !ok {
  507. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  508. }
  509. return nil, guest.SlaveDisksBlockStream()
  510. }
  511. func guestCancelBlockJobs(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  512. if !guestman.GetGuestManager().IsGuestExist(sid) {
  513. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  514. }
  515. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().CancelBlockJobs, sid)
  516. return nil, nil
  517. }
  518. func guestCancelBlockReplication(
  519. ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject,
  520. ) (interface{}, error) {
  521. if !guestman.GetGuestManager().IsGuestExist(sid) {
  522. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  523. }
  524. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().CancelBlockReplication, sid)
  525. return nil, nil
  526. }
  527. func guestHotplugCpuMem(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  528. if !guestman.GetGuestManager().IsGuestExist(sid) {
  529. return nil, httperrors.NewNotFoundError("Guest %s not found", sid)
  530. }
  531. if guestman.GetGuestManager().Status(sid) != "running" {
  532. return nil, httperrors.NewBadRequestError("Guest %s not running", sid)
  533. }
  534. addCpuCount, _ := body.Int("add_cpu")
  535. addMemSize, _ := body.Int("add_mem")
  536. input := &guestman.SGuestHotplugCpuMem{
  537. Sid: sid,
  538. AddCpuCount: addCpuCount,
  539. AddMemSize: addMemSize,
  540. }
  541. totalMemSize, err := body.Int("total_mem")
  542. if err == nil {
  543. input.TotalMemSize = &totalMemSize
  544. }
  545. totalCpuCount, err := body.Int("total_cpu")
  546. if err == nil {
  547. input.TotalCpuCount = &totalCpuCount
  548. }
  549. if body.Contains("cpu_numa_pin") {
  550. cpuNumaPin := make([]schedapi.SCpuNumaPin, 0)
  551. if err := body.Unmarshal(&cpuNumaPin, "cpu_numa_pin"); err != nil {
  552. return nil, httperrors.NewInputParameterError("failed parse cpu_numa_pin %s", err)
  553. }
  554. if len(cpuNumaPin) > 0 {
  555. descCpuNumaPin := make([]*desc.SCpuNumaPin, len(cpuNumaPin))
  556. for i := range cpuNumaPin {
  557. if cpuNumaPin[i].MemSizeMB != nil {
  558. descCpuNumaPin[i].SizeMB = int64(*cpuNumaPin[i].MemSizeMB)
  559. nodeId := uint16(cpuNumaPin[i].NodeId)
  560. descCpuNumaPin[i].NodeId = &nodeId
  561. }
  562. if len(cpuNumaPin[i].CpuPin) > 0 {
  563. vcpuPin := make([]desc.SVCpuPin, len(cpuNumaPin[i].CpuPin))
  564. for j := range vcpuPin {
  565. vcpuPin[j].Pcpu = cpuNumaPin[i].CpuPin[j]
  566. }
  567. descCpuNumaPin[i].VcpuPin = vcpuPin
  568. }
  569. }
  570. input.CpuNumaPin = descCpuNumaPin
  571. }
  572. }
  573. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().HotplugCpuMem, input)
  574. return nil, nil
  575. }
  576. func guestReloadDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  577. diskId, err := body.GetString("disk_id")
  578. if err != nil {
  579. return nil, httperrors.NewMissingParameterError("disk_id")
  580. }
  581. guest, ok := guestman.GetGuestManager().GetKVMServer(sid)
  582. if !ok {
  583. return nil, httperrors.NewNotFoundError("guest %s not found", sid)
  584. }
  585. var disk storageman.IDisk
  586. disks := guest.Desc.Disks
  587. for _, d := range disks {
  588. if diskId == d.DiskId {
  589. disk, _ = storageman.GetManager().GetDiskByPath(d.Path)
  590. break
  591. }
  592. }
  593. if disk == nil {
  594. return nil, httperrors.NewNotFoundError("Disk not found")
  595. }
  596. hostutils.DelayTask(ctx, guestman.GetGuestManager().ReloadDiskSnapshot, &guestman.SReloadDisk{
  597. Sid: sid,
  598. Disk: disk,
  599. })
  600. return nil, nil
  601. }
  602. func guestSnapshot(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  603. snapshotId, err := body.GetString("snapshot_id")
  604. if err != nil {
  605. return nil, httperrors.NewMissingParameterError("snapshot_id")
  606. }
  607. diskId, err := body.GetString("disk_id")
  608. if err != nil {
  609. return nil, httperrors.NewMissingParameterError("disk_id")
  610. }
  611. guest, ok := guestman.GetGuestManager().GetServer(sid)
  612. if !ok {
  613. return nil, httperrors.NewNotFoundError("guest %s not found", sid)
  614. }
  615. var disk storageman.IDisk
  616. disks := guest.GetDesc().Disks
  617. for _, d := range disks {
  618. if diskId == d.DiskId {
  619. disk, err = storageman.GetManager().GetDiskById(diskId)
  620. if err != nil {
  621. return nil, errors.Wrapf(err, "GetDiskById(%s)", diskId)
  622. }
  623. break
  624. }
  625. }
  626. if disk == nil {
  627. return nil, httperrors.NewNotFoundError("Disk not found")
  628. }
  629. input := &guestman.SDiskSnapshot{
  630. UserCred: userCred,
  631. Sid: sid,
  632. SnapshotId: snapshotId,
  633. Disk: disk,
  634. }
  635. if body.Contains("backup_disk_config") {
  636. backupDiskConfig := new(guestman.SBackupDiskConfig)
  637. if err := body.Unmarshal(backupDiskConfig, "backup_disk_config"); err != nil {
  638. return nil, httperrors.NewInputParameterError("unmarshal backup_disk_config")
  639. }
  640. input.BackupDiskConfig = backupDiskConfig
  641. }
  642. hostutils.DelayBackupTask(ctx, guestman.GetGuestManager().DoSnapshot, input)
  643. return nil, nil
  644. }
  645. func guestDeleteSnapshot(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  646. deleteSnapshot, err := body.GetString("delete_snapshot")
  647. if err != nil {
  648. return nil, httperrors.NewMissingParameterError("delete_snapshot")
  649. }
  650. diskId, err := body.GetString("disk_id")
  651. if err != nil {
  652. return nil, httperrors.NewMissingParameterError("disk_id")
  653. }
  654. var totalCnt, deletedCnt int64 = 1, 0
  655. if body.Contains("snapshot_total_count") {
  656. totalCnt, _ = body.Int("snapshot_total_count")
  657. deletedCnt, _ = body.Int("deleted_snapshot_count")
  658. }
  659. guest, ok := guestman.GetGuestManager().GetServer(sid)
  660. if !ok {
  661. return nil, httperrors.NewNotFoundError("guest %s not found", sid)
  662. }
  663. var disk storageman.IDisk
  664. disks := guest.GetDesc().Disks
  665. for _, d := range disks {
  666. if diskId == d.DiskId {
  667. disk, err = storageman.GetManager().GetDiskById(diskId)
  668. if err != nil {
  669. return nil, errors.Wrapf(err, "GetDiskById(%s)", diskId)
  670. }
  671. break
  672. }
  673. }
  674. if disk == nil {
  675. return nil, httperrors.NewNotFoundError("Disk not found")
  676. }
  677. params := &guestman.SDeleteDiskSnapshot{
  678. Sid: sid,
  679. DeleteSnapshot: deleteSnapshot,
  680. Disk: disk,
  681. TotalDeleteSnapshotCount: int(totalCnt),
  682. DeletedSnapshotCount: int(deletedCnt),
  683. }
  684. if body.Contains("encrypt_info") {
  685. encryptInfo := apis.SEncryptInfo{}
  686. if err = body.Unmarshal(&encryptInfo, "encrypt_info"); err != nil {
  687. return nil, httperrors.NewInputParameterError("unmarshal encrypt_info failed %s", err)
  688. }
  689. params.EncryptInfo = encryptInfo
  690. }
  691. // blockStream indicate snapshot<-disk
  692. blockStream := jsonutils.QueryBoolean(body, "block_stream", false)
  693. autoDeleted := jsonutils.QueryBoolean(body, "auto_deleted", false)
  694. if !blockStream && !autoDeleted {
  695. convertSnapshot, err := body.GetString("convert_snapshot")
  696. if err != nil {
  697. return nil, httperrors.NewMissingParameterError("convert_snapshot")
  698. }
  699. params.ConvertSnapshot = convertSnapshot
  700. }
  701. params.BlockStream = blockStream
  702. hostutils.DelayTask(ctx, guestman.GetGuestManager().DeleteSnapshot, params)
  703. return nil, nil
  704. }
  705. func formatCloneDiskParams(sid string, body jsonutils.JSONObject) (*guestman.SStorageCloneDisk, error) {
  706. input := new(computeapi.ServerChangeDiskStorageInternalInput)
  707. if err := body.Unmarshal(input); err != nil {
  708. return nil, err
  709. }
  710. srcStorage := storageman.GetManager().GetStorage(input.StorageId)
  711. if srcStorage == nil {
  712. return nil, httperrors.NewNotFoundError("Source storage %q not found", input.StorageId)
  713. }
  714. srcDisk, err := srcStorage.GetDiskById(input.DiskId)
  715. if err != nil {
  716. return nil, errors.Wrapf(err, "Get source disk %q on storage %q", input.DiskId, srcStorage.GetId())
  717. }
  718. targetStorage := storageman.GetManager().GetStorage(input.TargetStorageId)
  719. if targetStorage == nil {
  720. return nil, httperrors.NewNotFoundError("Target storage %s not found", input.TargetStorageId)
  721. }
  722. if input.TargetDiskId == "" {
  723. return nil, httperrors.NewMissingParameterError("Target disk id is empty")
  724. }
  725. params := &guestman.SStorageCloneDisk{
  726. ServerId: sid,
  727. SourceStorage: srcStorage,
  728. SourceDisk: srcDisk,
  729. TargetStorage: targetStorage,
  730. TargetDiskId: input.TargetDiskId,
  731. DiskFormat: input.DiskFormat,
  732. TargetDiskDesc: input.TargetDiskDesc,
  733. CompletedDiskCount: input.CompletedDiskCount,
  734. CloneDiskCount: input.CloneDiskCount,
  735. }
  736. return params, nil
  737. }
  738. func guestStorageCloneDisk(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  739. params, err := formatCloneDiskParams(sid, body)
  740. if err != nil {
  741. return nil, err
  742. }
  743. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().StorageCloneDisk, params)
  744. return nil, nil
  745. }
  746. func guestLiveChangeDisk(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  747. params, err := formatCloneDiskParams(sid, body)
  748. if err != nil {
  749. return nil, err
  750. }
  751. hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().LiveChangeDisk, params)
  752. return nil, nil
  753. }
  754. func guestCPUSet(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  755. input := new(computeapi.ServerCPUSetInput)
  756. if err := body.Unmarshal(input); err != nil {
  757. return nil, err
  758. }
  759. gm := guestman.GetGuestManager()
  760. return gm.CPUSet(ctx, sid, input)
  761. }
  762. func guestCPUSetRemove(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  763. gm := guestman.GetGuestManager()
  764. if err := gm.CPUSetRemove(ctx, sid); err != nil {
  765. return nil, err
  766. }
  767. return nil, nil
  768. }
  769. func guestMemorySnapshot(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  770. input := new(hostapi.GuestMemorySnapshotRequest)
  771. if err := body.Unmarshal(input); err != nil {
  772. return nil, err
  773. }
  774. gm := guestman.GetGuestManager()
  775. hostutils.DelayTaskWithoutReqctx(ctx, gm.DoMemorySnapshot, &guestman.SMemorySnapshot{
  776. GuestMemorySnapshotRequest: input,
  777. Sid: sid,
  778. })
  779. return nil, nil
  780. }
  781. func guestMemorySnapshotReset(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  782. input := new(hostapi.GuestMemorySnapshotResetRequest)
  783. if err := body.Unmarshal(input); err != nil {
  784. return nil, err
  785. }
  786. if input.InstanceSnapshotId == "" {
  787. return nil, httperrors.NewMissingParameterError("instance_snapshot_id")
  788. }
  789. if input.Path == "" {
  790. return nil, httperrors.NewMissingParameterError("path")
  791. }
  792. gm := guestman.GetGuestManager()
  793. hostutils.DelayTaskWithoutReqctx(ctx, gm.DoResetMemorySnapshot, &guestman.SMemorySnapshotReset{
  794. GuestMemorySnapshotResetRequest: input,
  795. Sid: sid,
  796. })
  797. return nil, nil
  798. }
  799. func guestMemorySnapshotDelete(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  800. _, _, body := appsrv.FetchEnv(ctx, w, r)
  801. input := new(hostapi.GuestMemorySnapshotDeleteRequest)
  802. if err := body.Unmarshal(input); err != nil {
  803. hostutils.Response(ctx, w, err)
  804. return
  805. }
  806. if input.InstanceSnapshotId == "" {
  807. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("instance_snapshot_id"))
  808. return
  809. }
  810. if input.Path == "" {
  811. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("path"))
  812. return
  813. }
  814. gm := guestman.GetGuestManager()
  815. hostutils.DelayTask(ctx, gm.DoDeleteMemorySnapshot, &guestman.SMemorySnapshotDelete{
  816. GuestMemorySnapshotDeleteRequest: input,
  817. })
  818. }
  819. func guestResetNicTrafficLimit(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  820. input := []computeapi.ServerNicTrafficLimit{}
  821. if err := body.Unmarshal(&input); err != nil {
  822. return nil, httperrors.NewInputParameterError("failed unmarshal input %s", err)
  823. }
  824. hostutils.DelayTask(ctx, func(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  825. return nil, guestman.GetGuestManager().ResetGuestNicTrafficLimit(sid, input)
  826. }, nil)
  827. return nil, nil
  828. }
  829. func guestSetNicTrafficLimit(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  830. input := []computeapi.ServerNicTrafficLimit{}
  831. if err := body.Unmarshal(&input); err != nil {
  832. return nil, httperrors.NewInputParameterError("failed unmarshal input %s", err)
  833. }
  834. hostutils.DelayTask(ctx, func(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  835. return nil, guestman.GetGuestManager().SetGuestNicTrafficLimit(sid, input)
  836. }, nil)
  837. return nil, nil
  838. }
  839. func qgaGuestSetPassword(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  840. input := new(hostapi.GuestSetPasswordRequest)
  841. if err := body.Unmarshal(input); err != nil {
  842. return nil, err
  843. }
  844. if input.Username == "" {
  845. return nil, httperrors.NewMissingParameterError("username")
  846. }
  847. if input.Password == "" {
  848. return nil, httperrors.NewMissingParameterError("password")
  849. }
  850. gm := guestman.GetGuestManager()
  851. hostutils.DelayTask(ctx, gm.QgaGuestSetPassword, &guestman.SQgaGuestSetPassword{
  852. GuestSetPasswordRequest: input,
  853. Sid: sid,
  854. })
  855. return nil, nil
  856. }
  857. func qgaGuestPing(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  858. gm := guestman.GetGuestManager()
  859. params := &guestman.SBaseParams{Sid: sid, Body: body}
  860. if jsonutils.QueryBoolean(body, "async", true) {
  861. hostutils.DelayTask(ctx, gm.QgaGuestPing, params)
  862. return nil, nil
  863. } else {
  864. return gm.QgaGuestPing(ctx, params)
  865. }
  866. }
  867. func qgaCommand(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  868. gm := guestman.GetGuestManager()
  869. input := computeapi.ServerQgaCommandInput{}
  870. err := body.Unmarshal(&input)
  871. if err != nil {
  872. return nil, httperrors.NewInputParameterError("unmarshal input to ServerQgaCommandInput: %s", err.Error())
  873. }
  874. cmdJson, err := jsonutils.ParseString(input.Command)
  875. if err != nil {
  876. return nil, httperrors.NewInputParameterError("failed parse qga command")
  877. }
  878. qgaCmd := &monitor.Command{}
  879. qgaCmd.Execute, err = cmdJson.GetString("execute")
  880. if err != nil {
  881. return nil, httperrors.NewInputParameterError("failed get qga command")
  882. }
  883. if cmdJson.Contains("arguments") {
  884. qgaCmd.Args, _ = cmdJson.Get("arguments")
  885. }
  886. return gm.QgaCommand(qgaCmd, sid, input.Timeout)
  887. }
  888. func qgaGuestInfoTask(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  889. gm := guestman.GetGuestManager()
  890. return gm.QgaGuestInfoTask(sid)
  891. }
  892. func qgaGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  893. gm := guestman.GetGuestManager()
  894. return gm.QgaGetNetwork(sid)
  895. }
  896. func qgaSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  897. input := computeapi.ServerQgaSetNetworkInput{}
  898. err := body.Unmarshal(&input)
  899. if err != nil {
  900. return nil, httperrors.NewInputParameterError("unmarshal input to ServerQgaSetNetworkInput: %s", err.Error())
  901. }
  902. if input.Device == "" {
  903. return nil, httperrors.NewMissingParameterError("device")
  904. }
  905. if input.Ipmask == "" && input.Ip6mask == "" {
  906. return nil, httperrors.NewMissingParameterError("ipmask")
  907. }
  908. if input.Gateway == "" && input.Gateway6 == "" {
  909. return nil, httperrors.NewMissingParameterError("gateway")
  910. }
  911. hostutils.DelayTask(ctx, guestman.GetGuestManager().QgaSetNetwork, &guestman.SQgaGuestSetNetwork{
  912. Sid: sid,
  913. Device: input.Device,
  914. Ipmask: input.Ipmask,
  915. Gateway: input.Gateway,
  916. Ip6mask: input.Ip6mask,
  917. Gateway6: input.Gateway6,
  918. })
  919. return nil, nil
  920. }
  921. func qgaGetOsInfo(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  922. gm := guestman.GetGuestManager()
  923. return gm.QgaGetOsInfo(sid)
  924. }
  925. func guestScreenDump(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  926. gm := guestman.GetGuestManager()
  927. return gm.RequestGuestScreenDump(sid)
  928. }
  929. // prepare rescue files
  930. func guestStartRescue(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  931. return guestman.GetGuestManager().GuestStartRescue(ctx, userCred, sid, body)
  932. }
  933. func uploadStatus(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  934. _, _, body := appsrv.FetchEnv(ctx, w, r)
  935. input := new(computeapi.HostUploadGuestsStatusRequest)
  936. if err := body.Unmarshal(input); err != nil {
  937. hostutils.Response(ctx, w, httperrors.NewInputParameterError("Unmarshal body to input: %v", err))
  938. return
  939. }
  940. hostutils.DelayTask(ctx, guestman.GetGuestManager().UploadGuestsStatus, input)
  941. hostutils.ResponseOk(ctx, w)
  942. }
  943. func guestUploadStatus(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
  944. hostutils.DelayTask(ctx, func(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  945. return guestman.GetGuestManager().UploadGuestStatus(ctx, sid)
  946. }, nil)
  947. return nil, nil
  948. }