storagehandler.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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 storagehandler
  15. import (
  16. "context"
  17. "fmt"
  18. "net/http"
  19. "strings"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/utils"
  24. "yunion.io/x/onecloud/pkg/apis"
  25. "yunion.io/x/onecloud/pkg/apis/compute"
  26. "yunion.io/x/onecloud/pkg/appsrv"
  27. "yunion.io/x/onecloud/pkg/hostman/hostutils"
  28. "yunion.io/x/onecloud/pkg/hostman/storageman"
  29. "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
  30. "yunion.io/x/onecloud/pkg/hostman/storageman/lvmutils"
  31. "yunion.io/x/onecloud/pkg/httperrors"
  32. "yunion.io/x/onecloud/pkg/mcclient/auth"
  33. modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  34. "yunion.io/x/onecloud/pkg/util/procutils"
  35. )
  36. var (
  37. storageKeyWords = []string{"storages"}
  38. storageActionFuncs = map[string]storageActionFunc{
  39. "attach": storageAttach,
  40. "detach": storageDetach,
  41. "update": storageUpdate,
  42. }
  43. )
  44. type storageActionFunc func(context.Context, jsonutils.JSONObject) (interface{}, error)
  45. func AddStorageHandler(prefix string, app *appsrv.Application) {
  46. for _, keyWords := range storageKeyWords {
  47. app.AddHandler("POST",
  48. fmt.Sprintf("%s/%s/<action>", prefix, keyWords),
  49. auth.Authenticate(storageActions))
  50. app.AddHandler("POST",
  51. fmt.Sprintf("%s/%s/<storageId>/delete-snapshots", prefix, keyWords),
  52. auth.Authenticate(storageDeleteSnapshots))
  53. app.AddHandler("POST",
  54. fmt.Sprintf("%s/%s/<storageId>/delete-snapshot", prefix, keyWords),
  55. auth.Authenticate(storageDeleteSnapshot))
  56. app.AddHandler("GET",
  57. fmt.Sprintf("%s/%s/is-mount-point", prefix, keyWords),
  58. auth.Authenticate(storageVerifyMountPoint))
  59. app.AddHandler("GET",
  60. fmt.Sprintf("%s/%s/is-local-mount-point", prefix, keyWords),
  61. auth.Authenticate(storageIsLocalMountPoint))
  62. app.AddHandler("GET",
  63. fmt.Sprintf("%s/%s/is-vg-exist", prefix, keyWords),
  64. auth.Authenticate(storageIsVgExist))
  65. app.AddHandler("POST",
  66. fmt.Sprintf("%s/%s/delete-backup", prefix, keyWords),
  67. auth.Authenticate(storageDeleteBackup))
  68. app.AddHandler("POST",
  69. fmt.Sprintf("%s/%s/sync-backup", prefix, keyWords),
  70. auth.Authenticate(storageSyncBackup))
  71. app.AddHandler("POST",
  72. fmt.Sprintf("%s/%s/pack-instance-backup", prefix, keyWords),
  73. auth.Authenticate(storagePackInstanceBackup))
  74. app.AddHandler("POST",
  75. fmt.Sprintf("%s/%s/unpack-instance-backup", prefix, keyWords),
  76. auth.Authenticate(storageUnpackInstanceBackup))
  77. app.AddHandler("POST",
  78. fmt.Sprintf("%s/%s/sync-backup-storage", prefix, keyWords),
  79. auth.Authenticate(storageSyncBackupStorage))
  80. app.AddHandler("POST",
  81. fmt.Sprintf("%s/%s/<storageId>/clean-recycle-diskfiles", prefix, keyWords),
  82. auth.Authenticate(storageCleanRecycleDiskfiles))
  83. }
  84. }
  85. func storageIsLocalMountPoint(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  86. _, query, _ := appsrv.FetchEnv(ctx, w, r)
  87. mountPoint, err := query.GetString("mount_point")
  88. if err != nil {
  89. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("mount_point"))
  90. return
  91. }
  92. fs, err := procutils.NewRemoteCommandAsFarAsPossible(
  93. "findmnt", "-n", "-o", "FSTYPE", "--target", mountPoint,
  94. ).Output()
  95. if err != nil {
  96. log.Errorf("failed get source of mountpoint %s: %s", mountPoint, err)
  97. hostutils.Response(ctx, w, httperrors.NewInternalServerError("failed get source of mountpoint %s: %s", mountPoint, err))
  98. return
  99. }
  100. fsStr := strings.TrimSpace(string(fs))
  101. log.Infof("check %s file system is %s", mountPoint, fsStr)
  102. if utils.IsInStringArray(fsStr, []string{"ext", "ext2", "ext3", "ext4", "xfs", "btrfs", "jfs", "reiserfs", "ntfs", "fat32", "exfat", "zfs"}) {
  103. // local file system
  104. appsrv.SendStruct(w, map[string]interface{}{"is_local_mount_point": true})
  105. } else {
  106. appsrv.SendStruct(w, map[string]interface{}{"is_local_mount_point": false})
  107. }
  108. }
  109. func storageIsVgExist(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  110. _, query, _ := appsrv.FetchEnv(ctx, w, r)
  111. vgName, err := query.GetString("vg_name")
  112. if err != nil {
  113. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("vg_name"))
  114. return
  115. }
  116. if err := lvmutils.VgDisplay(vgName); err != nil {
  117. log.Errorf("vg %s display failed %s", vgName, err)
  118. hostutils.Response(ctx, w, httperrors.NewInternalServerError("%s", err.Error()))
  119. return
  120. }
  121. hostutils.ResponseOk(ctx, w)
  122. }
  123. func storageVerifyMountPoint(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  124. _, query, _ := appsrv.FetchEnv(ctx, w, r)
  125. mountPoint, err := query.GetString("mount_point")
  126. if err != nil {
  127. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("mount_point"))
  128. return
  129. }
  130. output, err := procutils.NewRemoteCommandAsFarAsPossible("mountpoint", mountPoint).Output()
  131. if err == nil {
  132. appsrv.SendStruct(w, map[string]interface{}{"is_mount_point": true})
  133. } else {
  134. appsrv.SendStruct(w, map[string]interface{}{
  135. "is_mount_point": false,
  136. "error": string(output),
  137. })
  138. }
  139. }
  140. func storageActions(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  141. params, _, body := appsrv.FetchEnv(ctx, w, r)
  142. var action = params["<action>"]
  143. if f, ok := storageActionFuncs[action]; !ok {
  144. hostutils.Response(ctx, w, httperrors.NewNotFoundError("Not found"))
  145. } else {
  146. res, err := f(ctx, body)
  147. if err != nil {
  148. hostutils.Response(ctx, w, err)
  149. } else if res != nil {
  150. hostutils.Response(ctx, w, res)
  151. } else {
  152. hostutils.ResponseOk(ctx, w)
  153. }
  154. }
  155. }
  156. func storageAttach(ctx context.Context, body jsonutils.JSONObject) (interface{}, error) {
  157. mountPoint, err := body.GetString("mount_point")
  158. if err != nil {
  159. return nil, httperrors.NewMissingParameterError("mount_point")
  160. }
  161. storageType, _ := body.GetString("storage_type")
  162. storage := storageman.GetManager().NewSharedStorageInstance(mountPoint, storageType)
  163. if storage == nil {
  164. return nil, httperrors.NewBadRequestError("'Not Support Storage[%s] mount_point: %s", storageType, mountPoint)
  165. }
  166. storagecacheId, _ := body.GetString("storagecache_id")
  167. imagecachePath, _ := body.GetString("imagecache_path")
  168. storageId, _ := body.GetString("storage_id")
  169. storageName, _ := body.GetString("name")
  170. storageConf, _ := body.Get("storage_conf")
  171. storage.SetStoragecacheId(storagecacheId)
  172. if err := storage.SetStorageInfo(storageId, storageName, storageConf); err != nil {
  173. return nil, err
  174. }
  175. /*err = storage.SyncStorageSize()
  176. if err != nil {
  177. return nil, errors.Wrapf(err, "SyncStorageSize")
  178. }*/
  179. resp, err := storage.SyncStorageInfo()
  180. if err != nil {
  181. return nil, err
  182. }
  183. storageman.GetManager().InitSharedStorageImageCache(storageType, storagecacheId, imagecachePath, storage)
  184. storageman.GetManager().Storages = append(storageman.GetManager().Storages, storage)
  185. return resp, nil
  186. }
  187. func storageDetach(ctx context.Context, body jsonutils.JSONObject) (interface{}, error) {
  188. info := struct {
  189. MountPoint string
  190. StorageId string
  191. Name string
  192. }{}
  193. err := body.Unmarshal(&info)
  194. if err != nil {
  195. return nil, errors.Wrapf(err, "body.Unmarshal")
  196. }
  197. if len(info.StorageId) == 0 {
  198. return nil, httperrors.NewMissingParameterError("storage_id")
  199. }
  200. storage := storageman.GetManager().GetStorage(info.StorageId)
  201. if storage != nil {
  202. if err := storage.Detach(); err != nil {
  203. log.Errorf("detach storage %s failed: %s", storage.GetPath(), err)
  204. }
  205. storageman.GetManager().Remove(storage)
  206. }
  207. return nil, nil
  208. }
  209. func storageUpdate(ctx context.Context, body jsonutils.JSONObject) (interface{}, error) {
  210. storageId, err := body.GetString("storage_id")
  211. if err != nil {
  212. return nil, httperrors.NewMissingParameterError("storage_id")
  213. }
  214. storageConf, err := body.Get("storage_conf")
  215. if err != nil {
  216. return nil, httperrors.NewMissingParameterError("storage_conf")
  217. }
  218. storage := storageman.GetManager().GetStorage(storageId)
  219. params := jsonutils.NewDict()
  220. params.Set("details", jsonutils.JSONTrue)
  221. ret, err := modules.Hoststorages.Get(hostutils.GetComputeSession(context.Background()),
  222. storageman.GetManager().GetHostId(), storageId, params)
  223. if err != nil {
  224. log.Errorln(err)
  225. return nil, err
  226. }
  227. if ret == nil || storage == nil {
  228. return nil, httperrors.NewNotFoundError("Storage %s not found", storageId)
  229. }
  230. storageName, _ := ret.GetString("storage")
  231. if err := storage.SetStorageInfo(storageId, storageName, storageConf); err != nil {
  232. return nil, err
  233. }
  234. mountPoint, _ := ret.GetString("mount_point")
  235. storage.SetPath(mountPoint)
  236. return nil, nil
  237. }
  238. func storageSyncBackup(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  239. _, _, body := appsrv.FetchEnv(ctx, w, r)
  240. backupId, err := body.GetString("backup_id")
  241. if err != nil {
  242. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_id"))
  243. return
  244. }
  245. backupStorageId, err := body.GetString("backup_storage_id")
  246. if err != nil {
  247. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_id"))
  248. return
  249. }
  250. backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
  251. if err != nil {
  252. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_access_info"))
  253. return
  254. }
  255. backupStorage, err := backupstorage.GetBackupStorage(backupStorageId, backupStorageAccessInfo.(*jsonutils.JSONDict))
  256. if err != nil {
  257. hostutils.Response(ctx, w, err)
  258. return
  259. }
  260. exist, reason, err := backupStorage.IsBackupExists(backupId)
  261. if err != nil {
  262. hostutils.Response(ctx, w, err)
  263. return
  264. }
  265. var (
  266. ret = jsonutils.NewDict()
  267. status string
  268. )
  269. if exist {
  270. status = compute.BACKUP_EXIST
  271. } else {
  272. if len(reason) == 0 {
  273. status = compute.BACKUP_NOT_EXIST
  274. } else {
  275. log.Errorf("fetch snapshot exist failed reason:%s", reason)
  276. status = compute.BACKUP_STATUS_UNKNOWN
  277. }
  278. }
  279. ret.Set("status", jsonutils.NewString(status))
  280. hostutils.Response(ctx, w, ret)
  281. }
  282. func storageSyncBackupStorage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  283. _, _, body := appsrv.FetchEnv(ctx, w, r)
  284. backupStorageId, err := body.GetString("backup_storage_id")
  285. if err != nil {
  286. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_id"))
  287. return
  288. }
  289. backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
  290. if err != nil {
  291. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_access_info"))
  292. return
  293. }
  294. backupStorage, err := backupstorage.GetBackupStorage(backupStorageId, backupStorageAccessInfo.(*jsonutils.JSONDict))
  295. if err != nil {
  296. hostutils.Response(ctx, w, err)
  297. return
  298. }
  299. exist, reason, err := backupStorage.IsOnline()
  300. if err != nil {
  301. hostutils.Response(ctx, w, err)
  302. return
  303. }
  304. var (
  305. ret = jsonutils.NewDict()
  306. status string
  307. )
  308. if exist {
  309. status = compute.BACKUPSTORAGE_STATUS_ONLINE
  310. } else {
  311. status = compute.BACKUPSTORAGE_STATUS_OFFLINE
  312. }
  313. ret.Set("status", jsonutils.NewString(status))
  314. ret.Set("reason", jsonutils.NewString(reason))
  315. hostutils.Response(ctx, w, ret)
  316. }
  317. func storagePackInstanceBackup(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  318. _, _, body := appsrv.FetchEnv(ctx, w, r)
  319. if !checkOptions(ctx, w, body, "package_name", "backup_ids", "backup_storage_id", "backup_storage_access_info", "metadata") {
  320. return
  321. }
  322. pb := storageman.SStoragePackInstanceBackup{}
  323. err := body.Unmarshal(&pb)
  324. if err != nil {
  325. hostutils.Response(ctx, w, httperrors.NewInputParameterError("%s", err.Error()))
  326. return
  327. }
  328. hostutils.DelayTask(ctx, packInstanceBackup, &pb)
  329. hostutils.ResponseOk(ctx, w)
  330. }
  331. func storageUnpackInstanceBackup(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  332. _, _, body := appsrv.FetchEnv(ctx, w, r)
  333. if !checkOptions(ctx, w, body, "package_name", "backup_storage_id", "backup_storage_access_info") {
  334. return
  335. }
  336. pb := storageman.SStorageUnpackInstanceBackup{}
  337. err := body.Unmarshal(&pb)
  338. if err != nil {
  339. hostutils.Response(ctx, w, httperrors.NewInputParameterError("%s", err.Error()))
  340. return
  341. }
  342. hostutils.DelayTask(ctx, unpackInstanceBackup, &pb)
  343. hostutils.ResponseOk(ctx, w)
  344. }
  345. func packInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  346. sbParams := params.(*storageman.SStoragePackInstanceBackup)
  347. packFileName, err := storageman.DoInstancePackBackup(ctx, *sbParams)
  348. if err != nil {
  349. return nil, errors.Wrap(err, "DoInstancePackBackup")
  350. }
  351. ret := jsonutils.NewDict()
  352. ret.Set("pack_file_name", jsonutils.NewString(packFileName))
  353. return ret, nil
  354. }
  355. func unpackInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  356. sbParams := params.(*storageman.SStorageUnpackInstanceBackup)
  357. diskBackupIds, metadata, err := storageman.DoInstanceUnpackBackup(ctx, *sbParams)
  358. if err != nil {
  359. return nil, errors.Wrap(err, "DoInstanceUnpackBackup")
  360. }
  361. ret := jsonutils.NewDict()
  362. if diskBackupIds != nil {
  363. ret.Set("disk_backup_ids", jsonutils.Marshal(diskBackupIds))
  364. }
  365. ret.Set("metadata", jsonutils.Marshal(metadata))
  366. return ret, nil
  367. }
  368. func storageDeleteBackup(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  369. _, _, body := appsrv.FetchEnv(ctx, w, r)
  370. backupId, err := body.GetString("backup_id")
  371. if err != nil {
  372. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_id"))
  373. return
  374. }
  375. backupStorageId, err := body.GetString("backup_storage_id")
  376. if err != nil {
  377. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_id"))
  378. return
  379. }
  380. backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
  381. if err != nil {
  382. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_access_info"))
  383. return
  384. }
  385. hostutils.DelayTask(ctx, deleteBackup, &storageman.SStorageBackup{
  386. BackupId: backupId,
  387. BackupStorageId: backupStorageId,
  388. BackupStorageAccessInfo: backupStorageAccessInfo.(*jsonutils.JSONDict),
  389. })
  390. hostutils.ResponseOk(ctx, w)
  391. }
  392. func checkOptions(ctx context.Context, w http.ResponseWriter, body jsonutils.JSONObject, options ...string) bool {
  393. for _, option := range options {
  394. if body.Contains(option) {
  395. continue
  396. }
  397. hostutils.Response(ctx, w, httperrors.NewMissingParameterError(option))
  398. return false
  399. }
  400. return true
  401. }
  402. func deleteBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
  403. sbParams := params.(*storageman.SStorageBackup)
  404. backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
  405. if err != nil {
  406. return nil, err
  407. }
  408. err = backupStorage.RemoveBackup(ctx, sbParams.BackupId)
  409. if err != nil {
  410. return nil, err
  411. }
  412. return nil, nil
  413. }
  414. func storageDeleteSnapshot(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  415. params, _, body := appsrv.FetchEnv(ctx, w, r)
  416. var storageId = params["<storageId>"]
  417. storage := storageman.GetManager().GetStorage(storageId)
  418. if storage == nil {
  419. hostutils.Response(ctx, w, httperrors.NewNotFoundError("Stroage Not found"))
  420. return
  421. }
  422. diskId, err := body.GetString("disk_id")
  423. if err != nil {
  424. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("disk_id"))
  425. return
  426. }
  427. snapshotId, err := body.GetString("delete_snapshot")
  428. if err != nil {
  429. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("snapshot_id"))
  430. return
  431. }
  432. // blockStream indicate snapshot<-disk
  433. blockStream := jsonutils.QueryBoolean(body, "block_stream", false)
  434. autoDeleted := jsonutils.QueryBoolean(body, "auto_deleted", false)
  435. input := &storageman.SStorageDeleteSnapshot{
  436. DiskId: diskId,
  437. BlockStream: blockStream,
  438. SnapshotId: snapshotId,
  439. }
  440. if body.Contains("encrypt_info") {
  441. encryptInfo := apis.SEncryptInfo{}
  442. if err = body.Unmarshal(&encryptInfo, "encrypt_info"); err != nil {
  443. hostutils.Response(ctx, w, httperrors.NewInputParameterError("unmarshal encrypt_info failed %s", err))
  444. return
  445. }
  446. input.EncryptInfo = encryptInfo
  447. }
  448. if !blockStream && !autoDeleted {
  449. convertSnapshot, err := body.GetString("convert_snapshot")
  450. if err != nil {
  451. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("convert_snapshot"))
  452. return
  453. }
  454. input.ConvertSnapshot = convertSnapshot
  455. }
  456. hostutils.DelayTask(ctx, storage.DeleteSnapshot, input)
  457. hostutils.ResponseOk(ctx, w)
  458. }
  459. func storageDeleteSnapshots(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  460. params, _, body := appsrv.FetchEnv(ctx, w, r)
  461. var storageId = params["<storageId>"]
  462. storage := storageman.GetManager().GetStorage(storageId)
  463. if storage == nil {
  464. hostutils.Response(ctx, w, httperrors.NewNotFoundError("Storage Not found"))
  465. return
  466. }
  467. diskId, err := body.GetString("disk_id")
  468. if err != nil {
  469. hostutils.Response(ctx, w, httperrors.NewImageNotFoundError("disk_id"))
  470. return
  471. }
  472. snapshotIds := []string{}
  473. err = body.Unmarshal(&snapshotIds, "snapshot_ids")
  474. if err != nil {
  475. hostutils.Response(ctx, w, httperrors.NewMissingParameterError("snapshot_ids"))
  476. return
  477. }
  478. input := &storageman.SStorageDeleteSnapshots{
  479. DiskId: diskId,
  480. SnapshotIds: snapshotIds,
  481. }
  482. hostutils.DelayTask(ctx, storage.DeleteSnapshots, input)
  483. hostutils.ResponseOk(ctx, w)
  484. }
  485. func storageCleanRecycleDiskfiles(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  486. params, _, _ := appsrv.FetchEnv(ctx, w, r)
  487. var storageId = params["<storageId>"]
  488. storage := storageman.GetManager().GetStorage(storageId)
  489. if storage == nil {
  490. hostutils.Response(ctx, w, httperrors.NewNotFoundError("Storage Not found"))
  491. return
  492. }
  493. go storage.CleanRecycleDiskfiles(ctx)
  494. hostutils.ResponseOk(ctx, w)
  495. }