guest.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import copy
  4. from flask import Blueprint
  5. from flask import request
  6. import json
  7. from uuid import uuid4
  8. import jimit as ji
  9. from models import OSInitWrite
  10. from models.initialize import app, dev_table
  11. from models import Database as db
  12. from models import Config
  13. from models import GuestDisk
  14. from models import Rules
  15. from models import Utils
  16. from models import Guest
  17. from models import OSTemplate
  18. from models import GuestXML
  19. from models import status
  20. __author__ = 'James Iter'
  21. __date__ = '2017/3/22'
  22. __contact__ = 'james.iter.cn@gmail.com'
  23. __copyright__ = '(c) 2017 by James Iter.'
  24. blueprint = Blueprint(
  25. 'guest',
  26. __name__,
  27. url_prefix='/api/guest'
  28. )
  29. blueprints = Blueprint(
  30. 'guests',
  31. __name__,
  32. url_prefix='/api/guests'
  33. )
  34. @Utils.dumps2response
  35. def r_create():
  36. args_rules = [
  37. Rules.CPU.value,
  38. Rules.MEMORY.value,
  39. Rules.OS_TEMPLATE_ID.value,
  40. Rules.QUANTITY.value,
  41. Rules.NAME.value,
  42. Rules.PASSWORD.value,
  43. Rules.LEASE_TERM.value
  44. ]
  45. try:
  46. ret = dict()
  47. ret['state'] = ji.Common.exchange_state(20000)
  48. ji.Check.previewing(args_rules, request.json)
  49. config = Config()
  50. config.id = 1
  51. config.get()
  52. os_template = OSTemplate()
  53. os_template.id = request.json.get('os_template_id')
  54. if not os_template.exist():
  55. ret['state'] = ji.Common.exchange_state(40450)
  56. ret['state']['sub']['zh-cn'] = ''.join([ret['state']['sub']['zh-cn'], ': ', os_template.id.__str__()])
  57. return ret
  58. os_template.get()
  59. os_init_writes, os_init_writes_count = OSInitWrite.get_by_filter(
  60. filter_str='os_init_id:in:' + os_template.os_init_id.__str__())
  61. if db.r.scard(app.config['ip_available_set']) < 1:
  62. ret['state'] = ji.Common.exchange_state(50350)
  63. return ret
  64. quantity = request.json.get('quantity')
  65. while quantity:
  66. quantity -= 1
  67. guest = Guest()
  68. guest.uuid = uuid4().__str__()
  69. guest.cpu = request.json.get('cpu')
  70. # 虚拟机内存单位,模板生成方法中已置其为GiB
  71. guest.memory = request.json.get('memory')
  72. guest.os_template_id = request.json.get('os_template_id')
  73. guest.name = request.json.get('name')
  74. guest.password = request.json.get('password')
  75. if guest.password is None or guest.password.__len__() < 1:
  76. guest.password = ji.Common.generate_random_code(length=16)
  77. while guest.name.__len__() < 1 or guest.exist_by('name'):
  78. guest.name = ji.Common.generate_random_code(length=8)
  79. guest.ip = db.r.spop(app.config['ip_available_set'])
  80. db.r.sadd(app.config['ip_used_set'], guest.ip)
  81. guest.network = config.vm_network
  82. guest.manage_network = config.vm_manage_network
  83. guest.vnc_port = db.r.spop(app.config['vnc_port_available_set'])
  84. db.r.sadd(app.config['vnc_port_used_set'], guest.vnc_port)
  85. guest.vnc_password = ji.Common.generate_random_code(length=16)
  86. guest_disk = {'uuid': uuid4().__str__(), 'size': -1, 'format': 'qcow2', 'sequence': 0}
  87. guest_xml = GuestXML(guest=guest, disk=guest_disk, config=config)
  88. guest.xml = guest_xml.get_domain()
  89. guest.create()
  90. # 替换占位符为有效内容
  91. _os_init_writes = copy.deepcopy(os_init_writes)
  92. for k, v in enumerate(_os_init_writes):
  93. _os_init_writes[k]['content'] = v['content'].replace('{IP}', guest.ip).\
  94. replace('{HOSTNAME}', guest.name).\
  95. replace('{NETMASK}', config.netmask).\
  96. replace('{GATEWAY}', config.gateway).\
  97. replace('{DNS1}', config.dns1).\
  98. replace('{DNS2}', config.dns2)
  99. create_vm_msg = {
  100. 'action': 'create_vm',
  101. 'uuid': guest.uuid,
  102. 'name': guest.name,
  103. 'glusterfs_volume': config.glusterfs_volume,
  104. 'template_path': 'template_pool/' + os_template.name,
  105. 'guest_disk': guest_disk,
  106. 'writes': _os_init_writes,
  107. 'password': guest.password,
  108. 'xml': guest_xml.get_domain()
  109. }
  110. db.r.rpush(app.config['downstream_queue'], json.dumps(create_vm_msg, ensure_ascii=False))
  111. return ret
  112. except ji.PreviewingError, e:
  113. return json.loads(e.message)
  114. @Utils.dumps2response
  115. def r_reboot(uuids):
  116. args_rules = [
  117. Rules.UUIDS.value
  118. ]
  119. try:
  120. ji.Check.previewing(args_rules, {'uuids': uuids})
  121. guest = Guest()
  122. for uuid in uuids.split(','):
  123. guest.uuid = uuid
  124. guest.get_by('uuid')
  125. for uuid in uuids.split(','):
  126. message = {'action': 'reboot', 'uuid': uuid}
  127. Guest.emit_instruction(message=json.dumps(message))
  128. ret = dict()
  129. ret['state'] = ji.Common.exchange_state(20000)
  130. return ret
  131. except ji.PreviewingError, e:
  132. return json.loads(e.message)
  133. @Utils.dumps2response
  134. def r_force_reboot(uuids):
  135. args_rules = [
  136. Rules.UUIDS.value
  137. ]
  138. try:
  139. ji.Check.previewing(args_rules, {'uuids': uuids})
  140. guest = Guest()
  141. for uuid in uuids.split(','):
  142. guest.uuid = uuid
  143. guest.get_by('uuid')
  144. for uuid in uuids.split(','):
  145. message = {'action': 'force_reboot', 'uuid': uuid}
  146. Guest.emit_instruction(message=json.dumps(message))
  147. ret = dict()
  148. ret['state'] = ji.Common.exchange_state(20000)
  149. return ret
  150. except ji.PreviewingError, e:
  151. return json.loads(e.message)
  152. @Utils.dumps2response
  153. def r_shutdown(uuids):
  154. args_rules = [
  155. Rules.UUIDS.value
  156. ]
  157. try:
  158. ji.Check.previewing(args_rules, {'uuids': uuids})
  159. guest = Guest()
  160. for uuid in uuids.split(','):
  161. guest.uuid = uuid
  162. guest.get_by('uuid')
  163. for uuid in uuids.split(','):
  164. message = {'action': 'shutdown', 'uuid': uuid}
  165. Guest.emit_instruction(message=json.dumps(message))
  166. ret = dict()
  167. ret['state'] = ji.Common.exchange_state(20000)
  168. return ret
  169. except ji.PreviewingError, e:
  170. return json.loads(e.message)
  171. @Utils.dumps2response
  172. def r_force_shutdown(uuids):
  173. args_rules = [
  174. Rules.UUIDS.value
  175. ]
  176. try:
  177. ji.Check.previewing(args_rules, {'uuids': uuids})
  178. guest = Guest()
  179. for uuid in uuids.split(','):
  180. guest.uuid = uuid
  181. guest.get_by('uuid')
  182. for uuid in uuids.split(','):
  183. message = {'action': 'force_shutdown', 'uuid': uuid}
  184. Guest.emit_instruction(message=json.dumps(message))
  185. ret = dict()
  186. ret['state'] = ji.Common.exchange_state(20000)
  187. return ret
  188. except ji.PreviewingError, e:
  189. return json.loads(e.message)
  190. @Utils.dumps2response
  191. def r_boot(uuids):
  192. args_rules = [
  193. Rules.UUIDS.value
  194. ]
  195. try:
  196. ji.Check.previewing(args_rules, {'uuids': uuids})
  197. guest = Guest()
  198. for uuid in uuids.split(','):
  199. guest.uuid = uuid
  200. guest.get_by('uuid')
  201. for uuid in uuids.split(','):
  202. message = {'action': 'boot', 'uuid': uuid}
  203. Guest.emit_instruction(message=json.dumps(message))
  204. ret = dict()
  205. ret['state'] = ji.Common.exchange_state(20000)
  206. return ret
  207. except ji.PreviewingError, e:
  208. return json.loads(e.message)
  209. @Utils.dumps2response
  210. def r_suspend(uuids):
  211. args_rules = [
  212. Rules.UUIDS.value
  213. ]
  214. try:
  215. ji.Check.previewing(args_rules, {'uuids': uuids})
  216. guest = Guest()
  217. for uuid in uuids.split(','):
  218. guest.uuid = uuid
  219. guest.get_by('uuid')
  220. for uuid in uuids.split(','):
  221. message = {'action': 'suspend', 'uuid': uuid}
  222. Guest.emit_instruction(message=json.dumps(message))
  223. ret = dict()
  224. ret['state'] = ji.Common.exchange_state(20000)
  225. return ret
  226. except ji.PreviewingError, e:
  227. return json.loads(e.message)
  228. @Utils.dumps2response
  229. def r_resume(uuids):
  230. args_rules = [
  231. Rules.UUIDS.value
  232. ]
  233. try:
  234. ji.Check.previewing(args_rules, {'uuids': uuids})
  235. guest = Guest()
  236. for uuid in uuids.split(','):
  237. guest.uuid = uuid
  238. guest.get_by('uuid')
  239. for uuid in uuids.split(','):
  240. message = {'action': 'resume', 'uuid': uuid}
  241. Guest.emit_instruction(message=json.dumps(message))
  242. ret = dict()
  243. ret['state'] = ji.Common.exchange_state(20000)
  244. return ret
  245. except ji.PreviewingError, e:
  246. return json.loads(e.message)
  247. @Utils.dumps2response
  248. def r_delete(uuids):
  249. args_rules = [
  250. Rules.UUIDS.value
  251. ]
  252. # TODO: 加入是否删除使用的数据磁盘开关
  253. try:
  254. ji.Check.previewing(args_rules, {'uuids': uuids})
  255. guest = Guest()
  256. # 检测所指定的 UUDIs 实例都存在
  257. for uuid in uuids.split(','):
  258. guest.uuid = uuid
  259. guest.get_by('uuid')
  260. # 执行删除操作
  261. for uuid in uuids.split(','):
  262. guest.uuid = uuid
  263. guest.get_by('uuid')
  264. # TODO: 删除数据库记录,考虑由 JimV-N 通知执行成功之后再删除
  265. guest.delete()
  266. message = {'action': 'delete', 'uuid': uuid}
  267. Guest.emit_instruction(message=json.dumps(message))
  268. ret = dict()
  269. ret['state'] = ji.Common.exchange_state(20000)
  270. return ret
  271. except ji.PreviewingError, e:
  272. return json.loads(e.message)
  273. @Utils.dumps2response
  274. def r_attach_disk(uuid, disk_uuid):
  275. args_rules = [
  276. Rules.UUID.value,
  277. Rules.DISK_UUID.value
  278. ]
  279. try:
  280. ji.Check.previewing(args_rules, {'uuid': uuid, 'disk_uuid': disk_uuid})
  281. guest = Guest()
  282. guest.uuid = uuid
  283. guest.get_by('uuid')
  284. guest_disk = GuestDisk()
  285. guest_disk.uuid = disk_uuid
  286. guest_disk.get_by('uuid')
  287. ret = dict()
  288. ret['state'] = ji.Common.exchange_state(20000)
  289. # 判断欲挂载的磁盘是否空闲
  290. if guest_disk.guest_uuid.__len__() > 0:
  291. ret['state'] = ji.Common.exchange_state(41258)
  292. return ret
  293. # 判断 Guest 是否处于可用状态
  294. if guest.status == status.GuestState.no_state.value:
  295. ret['state'] = ji.Common.exchange_state(41259)
  296. return ret
  297. # 取出该 guest 已挂载的磁盘,来做出决定,确定该磁盘的序列
  298. guest_disk.guest_uuid = guest.uuid
  299. disks, count = guest_disk.get_by_filter(filter_str='guest_uuid:in:' + guest.uuid)
  300. guest_disk.sequence = count + 1
  301. config = Config()
  302. config.id = 1
  303. config.get()
  304. guest_disk.update()
  305. xml = """
  306. <disk type='network' device='disk'>
  307. <driver name='qemu' type='qcow2' cache='none'/>
  308. <source protocol='gluster' name='{0}/DiskPool/{1}.{2}'>
  309. <host name='127.0.0.1' port='24007'/>
  310. </source>
  311. <target dev='{3}' bus='virtio'/>
  312. </disk>
  313. """.format(config.glusterfs_volume, guest_disk.uuid, guest_disk.format,
  314. dev_table[guest_disk.sequence])
  315. message = {'action': 'attach_disk', 'uuid': uuid, 'xml': xml}
  316. Guest.emit_instruction(message=json.dumps(message))
  317. return ret
  318. except ji.PreviewingError, e:
  319. return json.loads(e.message)
  320. @Utils.dumps2response
  321. def r_detach_disk(disk_uuid):
  322. args_rules = [
  323. Rules.DISK_UUID.value
  324. ]
  325. try:
  326. ji.Check.previewing(args_rules, {'disk_uuid': disk_uuid})
  327. guest_disk = GuestDisk()
  328. guest_disk.uuid = disk_uuid
  329. guest_disk.get_by('uuid')
  330. ret = dict()
  331. ret['state'] = ji.Common.exchange_state(20000)
  332. if guest_disk.guest_uuid.__len__() != 36 and guest_disk.sequence < 1:
  333. # 表示未被任何实例使用,已被分离
  334. # 序列为 0 的表示实例系统盘,系统盘不可以被分离
  335. return ret
  336. guest_disk.guest_uuid = ''
  337. guest_disk.sequence = -1
  338. guest = Guest()
  339. guest.uuid = guest_disk.guest_uuid
  340. guest.get_by('uuid')
  341. # 判断 Guest 是否处于可用状态
  342. if guest.status == status.GuestState.no_state.value:
  343. ret['state'] = ji.Common.exchange_state(41259)
  344. return ret
  345. config = Config()
  346. config.id = 1
  347. config.get()
  348. guest_disk.update()
  349. xml = """
  350. <disk type='network' device='disk'>
  351. <driver name='qemu' type='qcow2' cache='none'/>
  352. <source protocol='gluster' name='{0}/DiskPool/{1}.{2}'>
  353. <host name='127.0.0.1' port='24007'/>
  354. </source>
  355. <target dev='{3}' bus='virtio'/>
  356. </disk>
  357. """.format(config.glusterfs_volume, guest_disk.uuid, guest_disk.format,
  358. dev_table[guest_disk.sequence])
  359. message = {'action': 'detach_disk', 'uuid': guest_disk.guest_uuid, 'xml': xml}
  360. Guest.emit_instruction(message=json.dumps(message))
  361. return ret
  362. except ji.PreviewingError, e:
  363. return json.loads(e.message)
  364. @Utils.dumps2response
  365. def r_migrate(uuids, destination_host):
  366. args_rules = [
  367. Rules.UUIDS.value,
  368. Rules.DESTINATION_HOST.value
  369. ]
  370. try:
  371. ji.Check.previewing(args_rules, {'uuids': uuids, 'destination_host': destination_host})
  372. guest = Guest()
  373. for uuid in uuids.split(','):
  374. guest.uuid = uuid
  375. guest.get_by('uuid')
  376. for uuid in uuids.split(','):
  377. message = {'action': 'migrate', 'uuid': uuid, 'duri': 'qemu+ssh://' + destination_host + '/system'}
  378. Guest.emit_instruction(message=json.dumps(message))
  379. ret = dict()
  380. ret['state'] = ji.Common.exchange_state(20000)
  381. return ret
  382. except ji.PreviewingError, e:
  383. return json.loads(e.message)
  384. @Utils.dumps2response
  385. def r_get_by_filter():
  386. page = str(request.args.get('page', 1))
  387. page_size = str(request.args.get('page_size', 50))
  388. args_rules = [
  389. Rules.PAGE.value,
  390. Rules.PAGE_SIZE.value
  391. ]
  392. try:
  393. ji.Check.previewing(args_rules, {'page': page, 'page_size': page_size})
  394. except ji.PreviewingError, e:
  395. return json.loads(e.message)
  396. page = int(page)
  397. page_size = int(page_size)
  398. # 把page和page_size换算成offset和limit
  399. offset = (page - 1) * page_size
  400. # offset, limit将覆盖page及page_size的影响
  401. offset = str(request.args.get('offset', offset))
  402. limit = str(request.args.get('limit', page_size))
  403. order_by = request.args.get('order_by', 'id')
  404. order = request.args.get('order', 'asc')
  405. filter_str = request.args.get('filter', '')
  406. args_rules = [
  407. Rules.OFFSET.value,
  408. Rules.LIMIT.value,
  409. Rules.ORDER_BY.value,
  410. Rules.ORDER.value
  411. ]
  412. try:
  413. ji.Check.previewing(args_rules, {'offset': offset, 'limit': limit, 'order_by': order_by, 'order': order})
  414. offset = int(offset)
  415. limit = int(limit)
  416. ret = dict()
  417. ret['state'] = ji.Common.exchange_state(20000)
  418. ret['data'] = list()
  419. ret['paging'] = {'total': 0, 'offset': offset, 'limit': limit, 'page': page, 'page_size': page_size,
  420. 'next': '', 'prev': '', 'first': '', 'last': ''}
  421. ret['data'], ret['paging']['total'] = Guest.get_by_filter(offset=offset, limit=limit, order_by=order_by,
  422. order=order, filter_str=filter_str)
  423. host_url = request.host_url.rstrip('/')
  424. other_str = '&filter=' + filter_str + '&order=' + order + '&order_by=' + order_by
  425. last_pagination = (ret['paging']['total'] + page_size - 1) / page_size
  426. if page <= 1:
  427. ret['paging']['prev'] = host_url + blueprints.url_prefix + '?page=1&page_size=' + page_size.__str__() + \
  428. other_str
  429. else:
  430. ret['paging']['prev'] = host_url + blueprints.url_prefix + '?page=' + str(page-1) + '&page_size=' + \
  431. page_size.__str__() + other_str
  432. if page >= last_pagination:
  433. ret['paging']['next'] = host_url + blueprints.url_prefix + '?page=' + last_pagination.__str__() + \
  434. '&page_size=' + page_size.__str__() + other_str
  435. else:
  436. ret['paging']['next'] = host_url + blueprints.url_prefix + '?page=' + str(page+1) + '&page_size=' + \
  437. page_size.__str__() + other_str
  438. ret['paging']['first'] = host_url + blueprints.url_prefix + '?page=1&page_size=' + \
  439. page_size.__str__() + other_str
  440. ret['paging']['last'] = \
  441. host_url + blueprints.url_prefix + '?page=' + last_pagination.__str__() + '&page_size=' + \
  442. page_size.__str__() + other_str
  443. return ret
  444. except ji.PreviewingError, e:
  445. return json.loads(e.message)
  446. @Utils.dumps2response
  447. def r_content_search():
  448. page = str(request.args.get('page', 1))
  449. page_size = str(request.args.get('page_size', 50))
  450. args_rules = [
  451. Rules.PAGE.value,
  452. Rules.PAGE_SIZE.value
  453. ]
  454. try:
  455. ji.Check.previewing(args_rules, {'page': page, 'page_size': page_size})
  456. except ji.PreviewingError, e:
  457. return json.loads(e.message)
  458. page = int(page)
  459. page_size = int(page_size)
  460. # 把page和page_size换算成offset和limit
  461. offset = (page - 1) * page_size
  462. # offset, limit将覆盖page及page_size的影响
  463. offset = str(request.args.get('offset', offset))
  464. limit = str(request.args.get('limit', page_size))
  465. order_by = request.args.get('order_by', 'id')
  466. order = request.args.get('order', 'asc')
  467. keyword = request.args.get('keyword', '')
  468. args_rules = [
  469. Rules.OFFSET.value,
  470. Rules.LIMIT.value,
  471. Rules.ORDER_BY.value,
  472. Rules.ORDER.value,
  473. Rules.KEYWORD.value
  474. ]
  475. try:
  476. ji.Check.previewing(args_rules, {'offset': offset, 'limit': limit, 'order_by': order_by, 'order': order,
  477. 'keyword': keyword})
  478. offset = int(offset)
  479. limit = int(limit)
  480. ret = dict()
  481. ret['state'] = ji.Common.exchange_state(20000)
  482. ret['data'] = list()
  483. ret['paging'] = {'total': 0, 'offset': offset, 'limit': limit, 'page': page, 'page_size': page_size}
  484. ret['data'], ret['paging']['total'] = Guest.content_search(offset=offset, limit=limit, order_by=order_by,
  485. order=order, keyword=keyword)
  486. host_url = request.host_url.rstrip('/')
  487. other_str = '&keyword=' + keyword + '&order=' + order + '&order_by=' + order_by
  488. last_pagination = (ret['paging']['total'] + page_size - 1) / page_size
  489. if page <= 1:
  490. ret['paging']['prev'] = host_url + blueprints.url_prefix + '/_search?page=1&page_size=' + \
  491. page_size.__str__() + other_str
  492. else:
  493. ret['paging']['prev'] = host_url + blueprints.url_prefix + '/_search?page=' + str(page-1) + \
  494. '&page_size=' + page_size.__str__() + other_str
  495. if page >= last_pagination:
  496. ret['paging']['next'] = host_url + blueprints.url_prefix + '/_search?page=' + last_pagination.__str__() + \
  497. '&page_size=' + page_size.__str__() + other_str
  498. else:
  499. ret['paging']['next'] = host_url + blueprints.url_prefix + '/_search?page=' + str(page+1) + \
  500. '&page_size=' + page_size.__str__() + other_str
  501. ret['paging']['first'] = host_url + blueprints.url_prefix + '/_search?page=1&page_size=' + \
  502. page_size.__str__() + other_str
  503. ret['paging']['last'] = \
  504. host_url + blueprints.url_prefix + '/_search?page=' + last_pagination.__str__() + '&page_size=' + \
  505. page_size.__str__() + other_str
  506. return ret
  507. except ji.PreviewingError, e:
  508. return json.loads(e.message)
  509. @Utils.dumps2response
  510. def r_update(uuid):
  511. args_rules = [
  512. Rules.UUID.value
  513. ]
  514. if 'remark' in request.json:
  515. args_rules.append(
  516. Rules.REMARK.value,
  517. )
  518. if args_rules.__len__() < 2:
  519. ret = dict()
  520. ret['state'] = ji.Common.exchange_state(20000)
  521. return ret
  522. request.json['uuid'] = uuid
  523. try:
  524. ji.Check.previewing(args_rules, request.json)
  525. guest = Guest()
  526. guest.uuid = uuid
  527. guest.get_by('uuid')
  528. guest.remark = request.json.get('remark', guest.name)
  529. guest.update()
  530. guest.get()
  531. ret = dict()
  532. ret['state'] = ji.Common.exchange_state(20000)
  533. ret['data'] = guest.__dict__
  534. return ret
  535. except ji.PreviewingError, e:
  536. return json.loads(e.message)