guest.py 20 KB

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