guest.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import copy
  4. from math import ceil
  5. import requests
  6. import json
  7. from uuid import uuid4
  8. import jimit as ji
  9. from flask import Blueprint, url_for, request
  10. from api.base import Base
  11. from models import DiskState, Host
  12. from models.initialize import app, dev_table
  13. from models import Database as db
  14. from models import Config
  15. from models import Disk
  16. from models import Rules
  17. from models import Utils
  18. from models import Guest
  19. from models import OSTemplateImage
  20. from models import OSTemplateProfile
  21. from models import OSTemplateInitializeOperate
  22. from models import GuestXML
  23. from models import SSHKeyGuestMapping
  24. from models import SSHKey
  25. from models import Snapshot
  26. from models import status
  27. __author__ = 'James Iter'
  28. __date__ = '2017/3/22'
  29. __contact__ = 'james.iter.cn@gmail.com'
  30. __copyright__ = '(c) 2017 by James Iter.'
  31. blueprint = Blueprint(
  32. 'api_guest',
  33. __name__,
  34. url_prefix='/api/guest'
  35. )
  36. blueprints = Blueprint(
  37. 'api_guests',
  38. __name__,
  39. url_prefix='/api/guests'
  40. )
  41. guest_base = Base(the_class=Guest, the_blueprint=blueprint, the_blueprints=blueprints)
  42. @Utils.dumps2response
  43. def r_create():
  44. args_rules = [
  45. Rules.CPU.value,
  46. Rules.MEMORY.value,
  47. Rules.BANDWIDTH.value,
  48. Rules.BANDWIDTH_UNIT.value,
  49. Rules.OS_TEMPLATE_IMAGE_ID.value,
  50. Rules.QUANTITY.value,
  51. Rules.REMARK.value,
  52. Rules.PASSWORD.value,
  53. Rules.LEASE_TERM.value
  54. ]
  55. if 'node_id' in request.json:
  56. args_rules.append(
  57. Rules.NODE_ID.value
  58. )
  59. if 'ssh_keys_id' in request.json:
  60. args_rules.append(
  61. Rules.SSH_KEYS_ID.value
  62. )
  63. try:
  64. ret = dict()
  65. ret['state'] = ji.Common.exchange_state(20000)
  66. ji.Check.previewing(args_rules, request.json)
  67. config = Config()
  68. config.id = 1
  69. config.get()
  70. os_template_image = OSTemplateImage()
  71. os_template_profile = OSTemplateProfile()
  72. os_template_image.id = request.json.get('os_template_image_id')
  73. if not os_template_image.exist():
  74. ret['state'] = ji.Common.exchange_state(40450)
  75. ret['state']['sub']['zh-cn'] = ''.join([ret['state']['sub']['zh-cn'], ': ', os_template_image.id.__str__()])
  76. return ret
  77. os_template_image.get()
  78. os_template_profile.id = os_template_image.os_template_profile_id
  79. os_template_profile.get()
  80. os_template_initialize_operates, os_template_initialize_operates_count = \
  81. OSTemplateInitializeOperate.get_by_filter(
  82. filter_str='os_template_initialize_operate_set_id:eq:' +
  83. os_template_profile.os_template_initialize_operate_set_id.__str__())
  84. if db.r.scard(app.config['ip_available_set']) < 1:
  85. ret['state'] = ji.Common.exchange_state(50350)
  86. return ret
  87. node_id = request.json.get('node_id', None)
  88. # 默认只取可随机分配虚拟机的 hosts
  89. available_hosts = Host.get_available_hosts(nonrandom=False)
  90. # 当指定了 host 时,取全部活着的 hosts
  91. if node_id is not None:
  92. available_hosts = Host.get_available_hosts(nonrandom=None)
  93. if available_hosts.__len__() == 0:
  94. ret['state'] = ji.Common.exchange_state(50351)
  95. return ret
  96. available_hosts_mapping_by_node_id = dict()
  97. for host in available_hosts:
  98. if host['node_id'] not in available_hosts_mapping_by_node_id:
  99. available_hosts_mapping_by_node_id[host['node_id']] = host
  100. if node_id is not None and node_id not in available_hosts_mapping_by_node_id:
  101. ret['state'] = ji.Common.exchange_state(50351)
  102. return ret
  103. ssh_keys_id = request.json.get('ssh_keys_id', list())
  104. ssh_keys = list()
  105. ssh_key_guest_mapping = SSHKeyGuestMapping()
  106. if ssh_keys_id.__len__() > 0:
  107. rows, _ = SSHKey.get_by_filter(
  108. filter_str=':'.join(['id', 'in', ','.join(_id.__str__() for _id in ssh_keys_id)]))
  109. for row in rows:
  110. ssh_keys.append(row['public_key'])
  111. bandwidth = request.json.get('bandwidth')
  112. bandwidth_unit = request.json.get('bandwidth_unit')
  113. if bandwidth_unit == 'k':
  114. bandwidth = bandwidth * 1000
  115. elif bandwidth_unit == 'm':
  116. bandwidth = bandwidth * 1000 ** 2
  117. elif bandwidth_unit == 'g':
  118. bandwidth = bandwidth * 1000 ** 3
  119. else:
  120. ret = dict()
  121. ret['state'] = ji.Common.exchange_state(41203)
  122. raise ji.PreviewingError(json.dumps(ret, ensure_ascii=False))
  123. # http://man7.org/linux/man-pages/man8/tc.8.html
  124. # 如果带宽大于 tc 所控最大速率,则置其为无限带宽
  125. # 34359738360 等于 tc 最大可控字节速率,换算出的比特位
  126. if bandwidth > 34359738360:
  127. bandwidth = 0
  128. quantity = request.json.get('quantity')
  129. while quantity:
  130. quantity -= 1
  131. guest = Guest()
  132. guest.uuid = uuid4().__str__()
  133. guest.cpu = request.json.get('cpu')
  134. # 虚拟机内存单位,模板生成方法中已置其为GiB
  135. guest.memory = request.json.get('memory')
  136. guest.bandwidth = bandwidth
  137. guest.os_template_image_id = request.json.get('os_template_image_id')
  138. guest.label = ji.Common.generate_random_code(length=8)
  139. guest.remark = request.json.get('remark', '')
  140. guest.password = request.json.get('password')
  141. if guest.password is None or guest.password.__len__() < 1:
  142. guest.password = ji.Common.generate_random_code(length=16)
  143. guest.ip = db.r.spop(app.config['ip_available_set'])
  144. db.r.sadd(app.config['ip_used_set'], guest.ip)
  145. guest.network = config.vm_network
  146. guest.manage_network = config.vm_manage_network
  147. guest.vnc_port = db.r.spop(app.config['vnc_port_available_set'])
  148. db.r.sadd(app.config['vnc_port_used_set'], guest.vnc_port)
  149. guest.vnc_password = ji.Common.generate_random_code(length=16)
  150. disk = Disk()
  151. disk.uuid = guest.uuid
  152. disk.remark = guest.label.__str__() + '_SystemImage'
  153. disk.format = 'qcow2'
  154. disk.sequence = 0
  155. disk.size = 0
  156. disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
  157. disk.guest_uuid = ''
  158. # disk.node_id 由 guest 事件处理机更新。涉及迁移时,其所属 node_id 会变更。参见 @models/event_processory.py:111 附近。
  159. disk.node_id = 0
  160. disk.quota(config=config)
  161. disk.create()
  162. if node_id is None:
  163. # 在可用计算节点中平均分配任务
  164. chosen_host = available_hosts[quantity % available_hosts.__len__()]
  165. else:
  166. chosen_host = available_hosts_mapping_by_node_id[node_id]
  167. guest.node_id = chosen_host['node_id']
  168. guest_xml = GuestXML(host=chosen_host, guest=guest, disk=disk, config=config,
  169. os_type=os_template_profile.os_type)
  170. guest.xml = guest_xml.get_domain()
  171. guest.node_id = int(guest.node_id)
  172. guest.create()
  173. ssh_key_guest_mapping.guest_uuid = guest.uuid
  174. if ssh_keys_id.__len__() > 0:
  175. for ssh_key_id in ssh_keys_id:
  176. ssh_key_guest_mapping.ssh_key_id = ssh_key_id
  177. ssh_key_guest_mapping.create()
  178. # 替换占位符为有效内容
  179. _os_template_initialize_operates = copy.deepcopy(os_template_initialize_operates)
  180. for k, v in enumerate(_os_template_initialize_operates):
  181. _os_template_initialize_operates[k]['content'] = v['content'].replace('{IP}', guest.ip).\
  182. replace('{HOSTNAME}', guest.label). \
  183. replace('{PASSWORD}', guest.password). \
  184. replace('{NETMASK}', config.netmask).\
  185. replace('{GATEWAY}', config.gateway).\
  186. replace('{DNS1}', config.dns1).\
  187. replace('{DNS2}', config.dns2). \
  188. replace('{SSH-KEY}', '\n'.join(ssh_keys))
  189. _os_template_initialize_operates[k]['command'] = v['command'].replace('{IP}', guest.ip). \
  190. replace('{HOSTNAME}', guest.label). \
  191. replace('{PASSWORD}', guest.password). \
  192. replace('{NETMASK}', config.netmask). \
  193. replace('{GATEWAY}', config.gateway). \
  194. replace('{DNS1}', config.dns1). \
  195. replace('{DNS2}', config.dns2). \
  196. replace('{SSH-KEY}', '\n'.join(ssh_keys))
  197. message = {
  198. '_object': 'guest',
  199. 'action': 'create',
  200. 'uuid': guest.uuid,
  201. 'storage_mode': config.storage_mode,
  202. 'dfs_volume': config.dfs_volume,
  203. 'node_id': guest.node_id,
  204. 'name': guest.label,
  205. 'template_path': os_template_image.path,
  206. 'os_type': os_template_profile.os_type,
  207. 'disks': [disk.__dict__],
  208. 'xml': guest_xml.get_domain(),
  209. 'os_template_initialize_operates': _os_template_initialize_operates,
  210. 'passback_parameters': {}
  211. }
  212. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  213. return ret
  214. except ji.PreviewingError, e:
  215. return json.loads(e.message)
  216. @Utils.dumps2response
  217. def r_reboot(uuids):
  218. args_rules = [
  219. Rules.UUIDS.value
  220. ]
  221. try:
  222. ji.Check.previewing(args_rules, {'uuids': uuids})
  223. guest = Guest()
  224. for uuid in uuids.split(','):
  225. guest.uuid = uuid
  226. guest.get_by('uuid')
  227. for uuid in uuids.split(','):
  228. guest.uuid = uuid
  229. guest.get_by('uuid')
  230. message = {
  231. '_object': 'guest',
  232. 'action': 'reboot',
  233. 'uuid': uuid,
  234. 'node_id': guest.node_id
  235. }
  236. Utils.emit_instruction(message=json.dumps(message))
  237. ret = dict()
  238. ret['state'] = ji.Common.exchange_state(20000)
  239. return ret
  240. except ji.PreviewingError, e:
  241. return json.loads(e.message)
  242. @Utils.dumps2response
  243. def r_force_reboot(uuids):
  244. args_rules = [
  245. Rules.UUIDS.value
  246. ]
  247. try:
  248. ji.Check.previewing(args_rules, {'uuids': uuids})
  249. guest = Guest()
  250. for uuid in uuids.split(','):
  251. guest.uuid = uuid
  252. guest.get_by('uuid')
  253. for uuid in uuids.split(','):
  254. guest.uuid = uuid
  255. guest.get_by('uuid')
  256. disks, _ = Disk.get_by_filter(filter_str=':'.join(['guest_uuid', 'eq', guest.uuid]))
  257. message = {
  258. '_object': 'guest',
  259. 'action': 'force_reboot',
  260. 'uuid': uuid,
  261. 'node_id': guest.node_id,
  262. 'disks': disks
  263. }
  264. Utils.emit_instruction(message=json.dumps(message))
  265. ret = dict()
  266. ret['state'] = ji.Common.exchange_state(20000)
  267. return ret
  268. except ji.PreviewingError, e:
  269. return json.loads(e.message)
  270. @Utils.dumps2response
  271. def r_shutdown(uuids):
  272. args_rules = [
  273. Rules.UUIDS.value
  274. ]
  275. try:
  276. ji.Check.previewing(args_rules, {'uuids': uuids})
  277. guest = Guest()
  278. for uuid in uuids.split(','):
  279. guest.uuid = uuid
  280. guest.get_by('uuid')
  281. for uuid in uuids.split(','):
  282. guest.uuid = uuid
  283. guest.get_by('uuid')
  284. message = {
  285. '_object': 'guest',
  286. 'action': 'shutdown',
  287. 'uuid': uuid,
  288. 'node_id': guest.node_id
  289. }
  290. Utils.emit_instruction(message=json.dumps(message))
  291. ret = dict()
  292. ret['state'] = ji.Common.exchange_state(20000)
  293. return ret
  294. except ji.PreviewingError, e:
  295. return json.loads(e.message)
  296. @Utils.dumps2response
  297. def r_force_shutdown(uuids):
  298. args_rules = [
  299. Rules.UUIDS.value
  300. ]
  301. try:
  302. ji.Check.previewing(args_rules, {'uuids': uuids})
  303. guest = Guest()
  304. for uuid in uuids.split(','):
  305. guest.uuid = uuid
  306. guest.get_by('uuid')
  307. for uuid in uuids.split(','):
  308. guest.uuid = uuid
  309. guest.get_by('uuid')
  310. message = {
  311. '_object': 'guest',
  312. 'action': 'force_shutdown',
  313. 'uuid': uuid,
  314. 'node_id': guest.node_id
  315. }
  316. Utils.emit_instruction(message=json.dumps(message))
  317. ret = dict()
  318. ret['state'] = ji.Common.exchange_state(20000)
  319. return ret
  320. except ji.PreviewingError, e:
  321. return json.loads(e.message)
  322. @Utils.dumps2response
  323. def r_boot(uuids):
  324. # TODO: 做好关系依赖判断,比如boot不可以对suspend的实例操作。
  325. args_rules = [
  326. Rules.UUIDS.value
  327. ]
  328. try:
  329. ji.Check.previewing(args_rules, {'uuids': uuids})
  330. guest = Guest()
  331. for uuid in uuids.split(','):
  332. guest.uuid = uuid
  333. guest.get_by('uuid')
  334. config = Config()
  335. config.id = 1
  336. config.get()
  337. for uuid in uuids.split(','):
  338. guest.uuid = uuid
  339. guest.get_by('uuid')
  340. disks, _ = Disk.get_by_filter(filter_str=':'.join(['guest_uuid', 'eq', guest.uuid]))
  341. message = {
  342. '_object': 'guest',
  343. 'action': 'boot',
  344. 'uuid': uuid,
  345. 'node_id': guest.node_id,
  346. 'passback_parameters': {},
  347. 'disks': disks
  348. }
  349. Utils.emit_instruction(message=json.dumps(message))
  350. ret = dict()
  351. ret['state'] = ji.Common.exchange_state(20000)
  352. return ret
  353. except ji.PreviewingError, e:
  354. return json.loads(e.message)
  355. @Utils.dumps2response
  356. def r_suspend(uuids):
  357. args_rules = [
  358. Rules.UUIDS.value
  359. ]
  360. try:
  361. ji.Check.previewing(args_rules, {'uuids': uuids})
  362. guest = Guest()
  363. for uuid in uuids.split(','):
  364. guest.uuid = uuid
  365. guest.get_by('uuid')
  366. for uuid in uuids.split(','):
  367. guest.uuid = uuid
  368. guest.get_by('uuid')
  369. message = {
  370. '_object': 'guest',
  371. 'action': 'suspend',
  372. 'uuid': uuid,
  373. 'node_id': guest.node_id
  374. }
  375. Utils.emit_instruction(message=json.dumps(message))
  376. ret = dict()
  377. ret['state'] = ji.Common.exchange_state(20000)
  378. return ret
  379. except ji.PreviewingError, e:
  380. return json.loads(e.message)
  381. @Utils.dumps2response
  382. def r_resume(uuids):
  383. args_rules = [
  384. Rules.UUIDS.value
  385. ]
  386. try:
  387. ji.Check.previewing(args_rules, {'uuids': uuids})
  388. guest = Guest()
  389. for uuid in uuids.split(','):
  390. guest.uuid = uuid
  391. guest.get_by('uuid')
  392. for uuid in uuids.split(','):
  393. guest.uuid = uuid
  394. guest.get_by('uuid')
  395. message = {
  396. '_object': 'guest',
  397. 'action': 'resume',
  398. 'uuid': uuid,
  399. 'node_id': guest.node_id
  400. }
  401. Utils.emit_instruction(message=json.dumps(message))
  402. ret = dict()
  403. ret['state'] = ji.Common.exchange_state(20000)
  404. return ret
  405. except ji.PreviewingError, e:
  406. return json.loads(e.message)
  407. @Utils.dumps2response
  408. def r_delete(uuids):
  409. args_rules = [
  410. Rules.UUIDS.value
  411. ]
  412. # TODO: 加入是否删除使用的数据磁盘开关,如果为True,则顺便删除使用的磁盘。否则解除该磁盘被使用的状态。
  413. try:
  414. ji.Check.previewing(args_rules, {'uuids': uuids})
  415. guest = Guest()
  416. # 检测所指定的 UUDIs 实例都存在
  417. for uuid in uuids.split(','):
  418. guest.uuid = uuid
  419. guest.get_by('uuid')
  420. config = Config()
  421. config.id = 1
  422. config.get()
  423. # 执行删除操作
  424. for uuid in uuids.split(','):
  425. guest.uuid = uuid
  426. guest.get_by('uuid')
  427. message = {
  428. '_object': 'guest',
  429. 'action': 'delete',
  430. 'uuid': uuid,
  431. 'storage_mode': config.storage_mode,
  432. 'dfs_volume': config.dfs_volume,
  433. 'node_id': guest.node_id
  434. }
  435. Utils.emit_instruction(message=json.dumps(message))
  436. # 删除创建失败的 Guest
  437. if guest.status == status.GuestState.dirty.value:
  438. disk = Disk()
  439. disk.uuid = guest.uuid
  440. disk.get_by('uuid')
  441. if disk.state == status.DiskState.pending.value:
  442. disk.delete()
  443. guest.delete()
  444. SSHKeyGuestMapping.delete_by_filter(filter_str=':'.join(['guest_uuid', 'eq', guest.uuid]))
  445. ret = dict()
  446. ret['state'] = ji.Common.exchange_state(20000)
  447. return ret
  448. except ji.PreviewingError, e:
  449. return json.loads(e.message)
  450. @Utils.dumps2response
  451. def r_attach_disk(uuid, disk_uuid):
  452. args_rules = [
  453. Rules.UUID.value,
  454. Rules.DISK_UUID.value
  455. ]
  456. try:
  457. ji.Check.previewing(args_rules, {'uuid': uuid, 'disk_uuid': disk_uuid})
  458. guest = Guest()
  459. guest.uuid = uuid
  460. guest.get_by('uuid')
  461. disk = Disk()
  462. disk.uuid = disk_uuid
  463. disk.get_by('uuid')
  464. config = Config()
  465. config.id = 1
  466. config.get()
  467. ret = dict()
  468. ret['state'] = ji.Common.exchange_state(20000)
  469. # 判断欲挂载的磁盘是否空闲
  470. if disk.guest_uuid.__len__() > 0 or disk.state != DiskState.idle.value:
  471. ret['state'] = ji.Common.exchange_state(41258)
  472. return ret
  473. # 判断 Guest 是否处于可用状态
  474. if guest.status in (status.GuestState.no_state.value, status.GuestState.dirty.value):
  475. ret['state'] = ji.Common.exchange_state(41259)
  476. return ret
  477. # 判断 Guest 与 磁盘是否在同一宿主机上
  478. if config.storage_mode in [status.StorageMode.local.value, status.StorageMode.shared_mount.value]:
  479. if guest.node_id != disk.node_id:
  480. ret['state'] = ji.Common.exchange_state(41260)
  481. return ret
  482. # 通过检测未被使用的序列,来确定当前磁盘在目标 Guest 身上的序列
  483. disk.guest_uuid = guest.uuid
  484. disks, count = disk.get_by_filter(filter_str='guest_uuid:in:' + guest.uuid)
  485. already_used_sequence = list()
  486. for _disk in disks:
  487. already_used_sequence.append(_disk['sequence'])
  488. for sequence in range(0, dev_table.__len__()):
  489. if sequence not in already_used_sequence:
  490. disk.sequence = sequence
  491. break
  492. disk.state = DiskState.mounting.value
  493. guest_xml = GuestXML(guest=guest, disk=disk, config=config)
  494. message = {
  495. '_object': 'guest',
  496. 'action': 'attach_disk',
  497. 'uuid': uuid,
  498. 'node_id': guest.node_id,
  499. 'xml': guest_xml.get_disk(),
  500. 'passback_parameters': {'disk_uuid': disk.uuid, 'sequence': disk.sequence},
  501. 'disks': [disk.__dict__]
  502. }
  503. Utils.emit_instruction(message=json.dumps(message))
  504. disk.update()
  505. return ret
  506. except ji.PreviewingError, e:
  507. return json.loads(e.message)
  508. @Utils.dumps2response
  509. def r_detach_disk(disk_uuid):
  510. args_rules = [
  511. Rules.DISK_UUID.value
  512. ]
  513. try:
  514. ji.Check.previewing(args_rules, {'disk_uuid': disk_uuid})
  515. disk = Disk()
  516. disk.uuid = disk_uuid
  517. disk.get_by('uuid')
  518. ret = dict()
  519. ret['state'] = ji.Common.exchange_state(20000)
  520. if disk.state != DiskState.mounted.value or disk.sequence == 0:
  521. # 表示未被任何实例使用,已被分离
  522. # 序列为 0 的表示实例系统盘,系统盘不可以被分离
  523. # TODO: 系统盘单独范围其它状态
  524. return ret
  525. guest = Guest()
  526. guest.uuid = disk.guest_uuid
  527. guest.get_by('uuid')
  528. # 判断 Guest 是否处于可用状态
  529. if guest.status in (status.GuestState.no_state.value, status.GuestState.dirty.value):
  530. ret['state'] = ji.Common.exchange_state(41259)
  531. return ret
  532. config = Config()
  533. config.id = 1
  534. config.get()
  535. guest_xml = GuestXML(guest=guest, disk=disk, config=config)
  536. message = {
  537. '_object': 'guest',
  538. 'action': 'detach_disk',
  539. 'uuid': disk.guest_uuid,
  540. 'node_id': guest.node_id,
  541. 'xml': guest_xml.get_disk(),
  542. 'passback_parameters': {'disk_uuid': disk.uuid}
  543. }
  544. Utils.emit_instruction(message=json.dumps(message))
  545. disk.state = DiskState.unloading.value
  546. disk.update()
  547. return ret
  548. except ji.PreviewingError, e:
  549. return json.loads(e.message)
  550. @Utils.dumps2response
  551. def r_migrate(uuids, destination_host):
  552. args_rules = [
  553. Rules.UUIDS.value,
  554. Rules.DESTINATION_HOST.value
  555. ]
  556. try:
  557. ji.Check.previewing(args_rules, {'uuids': uuids, 'destination_host': destination_host})
  558. ret = dict()
  559. ret['state'] = ji.Common.exchange_state(20000)
  560. # 取全部活着的 hosts
  561. available_hosts = Host.get_available_hosts(nonrandom=None)
  562. if available_hosts.__len__() == 0:
  563. ret['state'] = ji.Common.exchange_state(50351)
  564. return ret
  565. available_hosts_mapping_by_node_id = dict()
  566. for host in available_hosts:
  567. if host['node_id'] not in available_hosts_mapping_by_node_id:
  568. available_hosts_mapping_by_node_id[host['node_id']] = host
  569. guest = Guest()
  570. for uuid in uuids.split(','):
  571. guest.uuid = uuid
  572. guest.get_by('uuid')
  573. config = Config()
  574. config.id = 1
  575. config.get()
  576. for uuid in uuids.split(','):
  577. guest.uuid = uuid
  578. guest.get_by('uuid')
  579. # 忽略宕机计算节点 上面的 虚拟机 迁移请求
  580. # 忽略目标计算节点 等于 当前所在 计算节点 的虚拟机 迁移请求
  581. if guest.node_id not in available_hosts_mapping_by_node_id or \
  582. available_hosts_mapping_by_node_id[guest.node_id]['hostname'] == destination_host:
  583. continue
  584. message = {
  585. '_object': 'guest',
  586. 'action': 'migrate',
  587. 'uuid': uuid,
  588. 'node_id': guest.node_id,
  589. 'storage_mode': config.storage_mode,
  590. 'duri': 'qemu+ssh://' + destination_host + '/system'
  591. }
  592. Utils.emit_instruction(message=json.dumps(message))
  593. return ret
  594. except ji.PreviewingError, e:
  595. return json.loads(e.message)
  596. @Utils.dumps2response
  597. def r_get(uuids):
  598. ret = guest_base.get(ids=uuids, ids_rule=Rules.UUIDS.value, by_field='uuid')
  599. if '200' != ret['state']['code']:
  600. return ret
  601. rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['guest_uuid', 'in', uuids]))
  602. guest_uuid_ssh_key_id_mapping = dict()
  603. ssh_keys_id = list()
  604. for row in rows:
  605. if row['ssh_key_id'] not in ssh_keys_id:
  606. ssh_keys_id.append(row['ssh_key_id'].__str__())
  607. if row['guest_uuid'] not in guest_uuid_ssh_key_id_mapping:
  608. guest_uuid_ssh_key_id_mapping[row['guest_uuid']] = list()
  609. guest_uuid_ssh_key_id_mapping[row['guest_uuid']].append(row['ssh_key_id'])
  610. rows, _ = SSHKey.get_by_filter(filter_str=':'.join(['id', 'in', ','.join(ssh_keys_id)]))
  611. ssh_key_id_mapping = dict()
  612. for row in rows:
  613. row['url'] = url_for('v_ssh_keys.show')
  614. ssh_key_id_mapping[row['id']] = row
  615. if -1 == uuids.find(','):
  616. if 'ssh_keys' not in ret['data']:
  617. ret['data']['ssh_keys'] = list()
  618. if ret['data']['uuid'] in guest_uuid_ssh_key_id_mapping:
  619. for ssh_key_id in guest_uuid_ssh_key_id_mapping[ret['data']['uuid']]:
  620. if ssh_key_id not in ssh_key_id_mapping:
  621. continue
  622. ret['data']['ssh_keys'].append(ssh_key_id_mapping[ssh_key_id])
  623. else:
  624. for i, guest in enumerate(ret['data']):
  625. if 'ssh_keys' not in ret['data'][i]:
  626. ret['data'][i]['ssh_keys'] = list()
  627. if ret['data'][i]['uuid'] in guest_uuid_ssh_key_id_mapping:
  628. for ssh_key_id in guest_uuid_ssh_key_id_mapping[ret['data'][i]['uuid']]:
  629. if ssh_key_id not in ssh_key_id_mapping:
  630. continue
  631. ret['data'][i]['ssh_keys'].append(ssh_key_id_mapping[ssh_key_id])
  632. return ret
  633. @Utils.dumps2response
  634. def r_get_by_filter():
  635. ret = guest_base.get_by_filter()
  636. uuids = list()
  637. for guest in ret['data']:
  638. uuids.append(guest['uuid'])
  639. rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['guest_uuid', 'in', ','.join(uuids)]))
  640. guest_uuid_ssh_key_id_mapping = dict()
  641. ssh_keys_id = list()
  642. for row in rows:
  643. if row['ssh_key_id'] not in ssh_keys_id:
  644. ssh_keys_id.append(row['ssh_key_id'].__str__())
  645. if row['guest_uuid'] not in guest_uuid_ssh_key_id_mapping:
  646. guest_uuid_ssh_key_id_mapping[row['guest_uuid']] = list()
  647. guest_uuid_ssh_key_id_mapping[row['guest_uuid']].append(row['ssh_key_id'])
  648. rows, _ = SSHKey.get_by_filter(filter_str=':'.join(['id', 'in', ','.join(ssh_keys_id)]))
  649. ssh_key_id_mapping = dict()
  650. for row in rows:
  651. row['url'] = url_for('v_ssh_keys.show')
  652. ssh_key_id_mapping[row['id']] = row
  653. rows, _ = Snapshot.get_by_filter(filter_str=':'.join(['guest_uuid', 'in', ','.join(uuids)]))
  654. snapshots_guest_uuid_mapping = dict()
  655. for row in rows:
  656. guest_uuid = row['guest_uuid']
  657. if guest_uuid not in snapshots_guest_uuid_mapping:
  658. snapshots_guest_uuid_mapping[guest_uuid] = list()
  659. snapshots_guest_uuid_mapping[guest_uuid].append(row)
  660. for i, guest in enumerate(ret['data']):
  661. guest_uuid = ret['data'][i]['uuid']
  662. if 'ssh_keys' not in ret['data'][i]:
  663. ret['data'][i]['ssh_keys'] = list()
  664. if guest_uuid in guest_uuid_ssh_key_id_mapping:
  665. for ssh_key_id in guest_uuid_ssh_key_id_mapping[guest_uuid]:
  666. if ssh_key_id not in ssh_key_id_mapping:
  667. continue
  668. ret['data'][i]['ssh_keys'].append(ssh_key_id_mapping[ssh_key_id])
  669. if 'snapshot' not in ret['data'][i]:
  670. ret['data'][i]['snapshot'] = {
  671. 'creatable': True,
  672. 'mapping': list()
  673. }
  674. if guest_uuid in snapshots_guest_uuid_mapping:
  675. ret['data'][i]['snapshot']['mapping'] = snapshots_guest_uuid_mapping[guest_uuid]
  676. for snapshot in snapshots_guest_uuid_mapping[guest_uuid]:
  677. if snapshot['progress'] == 100:
  678. continue
  679. else:
  680. ret['data'][i]['snapshot']['creatable'] = False
  681. return ret
  682. @Utils.dumps2response
  683. def r_content_search():
  684. ret = guest_base.content_search()
  685. uuids = list()
  686. for guest in ret['data']:
  687. uuids.append(guest['uuid'])
  688. rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['guest_uuid', 'in', ','.join(uuids)]))
  689. guest_uuid_ssh_key_id_mapping = dict()
  690. ssh_keys_id = list()
  691. for row in rows:
  692. if row['ssh_key_id'] not in ssh_keys_id:
  693. ssh_keys_id.append(row['ssh_key_id'].__str__())
  694. if row['guest_uuid'] not in guest_uuid_ssh_key_id_mapping:
  695. guest_uuid_ssh_key_id_mapping[row['guest_uuid']] = list()
  696. guest_uuid_ssh_key_id_mapping[row['guest_uuid']].append(row['ssh_key_id'])
  697. rows, _ = SSHKey.get_by_filter(filter_str=':'.join(['id', 'in', ','.join(ssh_keys_id)]))
  698. ssh_key_id_mapping = dict()
  699. for row in rows:
  700. row['url'] = url_for('v_ssh_keys.show')
  701. ssh_key_id_mapping[row['id']] = row
  702. rows, _ = Snapshot.get_by_filter(filter_str=':'.join(['guest_uuid', 'in', ','.join(uuids)]))
  703. snapshots_guest_uuid_mapping = dict()
  704. for row in rows:
  705. guest_uuid = row['guest_uuid']
  706. if guest_uuid not in snapshots_guest_uuid_mapping:
  707. snapshots_guest_uuid_mapping[guest_uuid] = list()
  708. snapshots_guest_uuid_mapping[guest_uuid].append(row)
  709. for i, guest in enumerate(ret['data']):
  710. guest_uuid = ret['data'][i]['uuid']
  711. if 'ssh_keys' not in ret['data'][i]:
  712. ret['data'][i]['ssh_keys'] = list()
  713. if guest_uuid in guest_uuid_ssh_key_id_mapping:
  714. for ssh_key_id in guest_uuid_ssh_key_id_mapping[guest_uuid]:
  715. if ssh_key_id not in ssh_key_id_mapping:
  716. continue
  717. ret['data'][i]['ssh_keys'].append(ssh_key_id_mapping[ssh_key_id])
  718. if 'snapshot' not in ret['data'][i]:
  719. ret['data'][i]['snapshot'] = {
  720. 'creatable': True,
  721. 'mapping': list()
  722. }
  723. if guest_uuid in snapshots_guest_uuid_mapping:
  724. ret['data'][i]['snapshot']['mapping'] = snapshots_guest_uuid_mapping[guest_uuid]
  725. for snapshot in snapshots_guest_uuid_mapping[guest_uuid]:
  726. if snapshot['progress'] == 100:
  727. continue
  728. else:
  729. ret['data'][i]['snapshot']['creatable'] = False
  730. return ret
  731. @Utils.dumps2response
  732. def r_distribute_count():
  733. from models import Guest
  734. rows, count = Guest.get_all()
  735. ret = dict()
  736. ret['state'] = ji.Common.exchange_state(20000)
  737. ret['data'] = {
  738. 'os_template_image_id': dict(),
  739. 'status': dict(),
  740. 'node_id': dict(),
  741. 'cpu_memory': dict(),
  742. 'cpu': 0,
  743. 'memory': 0,
  744. 'guests': rows.__len__()
  745. }
  746. for guest in rows:
  747. if guest['os_template_image_id'] not in ret['data']['os_template_image_id']:
  748. ret['data']['os_template_image_id'][guest['os_template_image_id']] = 0
  749. if guest['status'] not in ret['data']['status']:
  750. ret['data']['status'][guest['status']] = 0
  751. if guest['node_id'] not in ret['data']['node_id']:
  752. ret['data']['node_id'][guest['node_id']] = 0
  753. cpu_memory = '_'.join([str(guest['cpu']), str(guest['memory'])])
  754. if cpu_memory not in ret['data']['cpu_memory']:
  755. ret['data']['cpu_memory'][cpu_memory] = 0
  756. ret['data']['os_template_image_id'][guest['os_template_image_id']] += 1
  757. ret['data']['status'][guest['status']] += 1
  758. ret['data']['node_id'][guest['node_id']] += 1
  759. ret['data']['cpu_memory'][cpu_memory] += 1
  760. ret['data']['cpu'] += guest['cpu']
  761. ret['data']['memory'] += guest['memory']
  762. return ret
  763. @Utils.dumps2response
  764. def r_update(uuid):
  765. args_rules = [
  766. Rules.UUID.value
  767. ]
  768. if 'remark' in request.json:
  769. args_rules.append(
  770. Rules.REMARK.value,
  771. )
  772. if args_rules.__len__() < 2:
  773. ret = dict()
  774. ret['state'] = ji.Common.exchange_state(20000)
  775. return ret
  776. request.json['uuid'] = uuid
  777. try:
  778. ji.Check.previewing(args_rules, request.json)
  779. guest = Guest()
  780. guest.uuid = uuid
  781. guest.get_by('uuid')
  782. guest.remark = request.json.get('remark', guest.label)
  783. guest.update()
  784. guest.get()
  785. ret = dict()
  786. ret['state'] = ji.Common.exchange_state(20000)
  787. ret['data'] = guest.__dict__
  788. return ret
  789. except ji.PreviewingError, e:
  790. return json.loads(e.message)
  791. @Utils.dumps2response
  792. def r_reset_password(uuids, password):
  793. args_rules = [
  794. Rules.UUIDS.value,
  795. Rules.PASSWORD.value
  796. ]
  797. try:
  798. ji.Check.previewing(args_rules, {'uuids': uuids, 'password': password})
  799. guest = Guest()
  800. os_template_image = OSTemplateImage()
  801. os_template_profile = OSTemplateProfile()
  802. # 检测所指定的 UUDIs 实例都存在
  803. for uuid in uuids.split(','):
  804. guest.uuid = uuid
  805. guest.get_by('uuid')
  806. for uuid in uuids.split(','):
  807. guest.uuid = uuid
  808. guest.get_by('uuid')
  809. os_template_image.id = guest.os_template_image_id
  810. os_template_image.get()
  811. os_template_profile.id = os_template_image.os_template_profile_id
  812. os_template_profile.get()
  813. user = 'root'
  814. if os_template_profile.os_type == 'windows':
  815. user = 'administrator'
  816. # guest.password 由 guest 事件处理机更新。参见 @models/event_processory.py:189 附近。
  817. message = {
  818. '_object': 'guest',
  819. 'action': 'reset_password',
  820. 'uuid': guest.uuid,
  821. 'node_id': guest.node_id,
  822. 'os_type': os_template_profile.os_type,
  823. 'user': user,
  824. 'password': password,
  825. 'passback_parameters': {'password': password}
  826. }
  827. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  828. ret = dict()
  829. ret['state'] = ji.Common.exchange_state(20000)
  830. return ret
  831. except ji.PreviewingError, e:
  832. return json.loads(e.message)
  833. @Utils.dumps2response
  834. def r_allocate_bandwidth(uuids, bandwidth, bandwidth_unit):
  835. args_rules = [
  836. Rules.UUIDS.value,
  837. Rules.BANDWIDTH_IN_URL.value,
  838. Rules.BANDWIDTH_UNIT.value,
  839. ]
  840. try:
  841. ji.Check.previewing(args_rules, {'uuids': uuids, 'bandwidth': bandwidth, 'bandwidth_unit': bandwidth_unit})
  842. ret = dict()
  843. ret['state'] = ji.Common.exchange_state(20000)
  844. bandwidth = int(bandwidth)
  845. if bandwidth_unit == 'k':
  846. bandwidth = bandwidth * 1000
  847. elif bandwidth_unit == 'm':
  848. bandwidth = bandwidth * 1000 ** 2
  849. elif bandwidth_unit == 'g':
  850. bandwidth = bandwidth * 1000 ** 3
  851. else:
  852. ret['state'] = ji.Common.exchange_state(41203)
  853. return ret
  854. # http://man7.org/linux/man-pages/man8/tc.8.html
  855. # 如果带宽大于 tc 所控最大速率,则置其为无限带宽
  856. # 34359738360 等于 tc 最大可控字节速率,换算出的比特位
  857. if bandwidth > 34359738360:
  858. bandwidth = 0
  859. guest = Guest()
  860. # 检测所指定的 UUDIs 实例都存在
  861. for uuid in uuids.split(','):
  862. guest.uuid = uuid
  863. guest.get_by('uuid')
  864. for uuid in uuids.split(','):
  865. guest.uuid = uuid
  866. guest.get_by('uuid')
  867. guest.bandwidth = bandwidth
  868. message = {
  869. '_object': 'guest',
  870. 'action': 'allocate_bandwidth',
  871. 'uuid': guest.uuid,
  872. 'node_id': guest.node_id,
  873. 'bandwidth': guest.bandwidth,
  874. 'passback_parameters': {'bandwidth': guest.bandwidth}
  875. }
  876. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  877. return ret
  878. except ji.PreviewingError, e:
  879. return json.loads(e.message)
  880. @Utils.dumps2response
  881. def r_adjust_ability(uuids, cpu, memory):
  882. args_rules = [
  883. Rules.UUIDS.value,
  884. Rules.CPU.value,
  885. Rules.MEMORY.value,
  886. ]
  887. try:
  888. ret = dict()
  889. ret['state'] = ji.Common.exchange_state(20000)
  890. cpu = int(cpu)
  891. memory = int(memory)
  892. ji.Check.previewing(args_rules, {'uuids': uuids, 'cpu': cpu, 'memory': memory})
  893. not_ready_yet_of_guests = list()
  894. guest = Guest()
  895. # 检测所指定的 UUDIs 实例都存在。且状态都为可以操作状态(即关闭状态)。
  896. for uuid in uuids.split(','):
  897. guest.uuid = uuid
  898. guest.get_by('uuid')
  899. if guest.status != status.GuestState.shutoff.value:
  900. not_ready_yet_of_guests.append(guest.__dict__)
  901. if not_ready_yet_of_guests.__len__() > 0:
  902. ret['state'] = ji.Common.exchange_state(41261)
  903. ret['data'] = not_ready_yet_of_guests
  904. return ret
  905. for uuid in uuids.split(','):
  906. guest.uuid = uuid
  907. guest.get_by('uuid')
  908. guest.cpu = cpu
  909. guest.memory = memory
  910. message = {
  911. '_object': 'guest',
  912. 'action': 'adjust_ability',
  913. 'uuid': guest.uuid,
  914. 'node_id': guest.node_id,
  915. 'cpu': guest.cpu,
  916. 'memory': guest.memory,
  917. 'passback_parameters': {'cpu': guest.cpu, 'memory': guest.memory}
  918. }
  919. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  920. return ret
  921. except ji.PreviewingError, e:
  922. return json.loads(e.message)
  923. @Utils.dumps2response
  924. def r_refresh_guest_state():
  925. try:
  926. ret = dict()
  927. ret['state'] = ji.Common.exchange_state(20000)
  928. # 取全部活着的 hosts
  929. available_hosts = Host.get_available_hosts(nonrandom=None)
  930. if available_hosts.__len__() == 0:
  931. ret['state'] = ji.Common.exchange_state(50351)
  932. return ret
  933. for host in available_hosts:
  934. message = {
  935. '_object': 'global',
  936. 'action': 'refresh_guest_state',
  937. 'node_id': host['node_id']
  938. }
  939. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  940. except ji.PreviewingError, e:
  941. return json.loads(e.message)
  942. @Utils.dumps2response
  943. def r_show():
  944. args = list()
  945. page = int(request.args.get('page', 1))
  946. page_size = int(request.args.get('page_size', 10))
  947. keyword = request.args.get('keyword', None)
  948. if page is not None:
  949. args.append('page=' + page.__str__())
  950. if page_size is not None:
  951. args.append('page_size=' + page_size.__str__())
  952. if keyword is not None:
  953. args.append('keyword=' + keyword.__str__())
  954. hosts_url = url_for('api_hosts.r_get_by_filter', _external=True)
  955. guests_url = url_for('api_guests.r_get_by_filter', _external=True)
  956. if keyword is not None:
  957. guests_url = url_for('api_guests.r_content_search', _external=True)
  958. os_templates_image_url = url_for('api_os_templates_image.r_get_by_filter', _external=True)
  959. os_templates_profile_url = url_for('api_os_templates_profile.r_get_by_filter', _external=True)
  960. if args.__len__() > 0:
  961. guests_url = guests_url + '?' + '&'.join(args)
  962. hosts_ret = requests.get(url=hosts_url, cookies=request.cookies)
  963. hosts_ret = json.loads(hosts_ret.content)
  964. hosts_mapping_by_node_id = dict()
  965. for host in hosts_ret['data']:
  966. hosts_mapping_by_node_id[int(host['node_id'])] = host
  967. guests_ret = requests.get(url=guests_url, cookies=request.cookies)
  968. guests_ret = json.loads(guests_ret.content)
  969. os_templates_image_ret = requests.get(url=os_templates_image_url, cookies=request.cookies)
  970. os_templates_image_ret = json.loads(os_templates_image_ret.content)
  971. os_templates_image_mapping_by_id = dict()
  972. for os_template_image in os_templates_image_ret['data']:
  973. os_templates_image_mapping_by_id[os_template_image['id']] = os_template_image
  974. os_templates_profile_ret = requests.get(url=os_templates_profile_url, cookies=request.cookies)
  975. os_templates_profile_ret = json.loads(os_templates_profile_ret.content)
  976. os_templates_profile_mapping_by_id = dict()
  977. for os_template_profile in os_templates_profile_ret['data']:
  978. os_templates_profile_mapping_by_id[os_template_profile['id']] = os_template_profile
  979. last_page = int(ceil(guests_ret['paging']['total'] / float(page_size)))
  980. page_length = 5
  981. pages = list()
  982. if page < int(ceil(page_length / 2.0)):
  983. for i in range(1, page_length + 1):
  984. pages.append(i)
  985. if i == last_page or last_page == 0:
  986. break
  987. elif last_page - page < page_length / 2:
  988. for i in range(last_page - page_length + 1, last_page + 1):
  989. if i < 1:
  990. continue
  991. pages.append(i)
  992. else:
  993. for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
  994. pages.append(i)
  995. if i == last_page or last_page == 0:
  996. break
  997. ret = dict()
  998. ret['state'] = ji.Common.exchange_state(20000)
  999. ret['data'] = {
  1000. 'guests_ret': guests_ret,
  1001. 'os_templates_image_mapping_by_id': os_templates_image_mapping_by_id,
  1002. 'os_templates_profile_mapping_by_id': os_templates_profile_mapping_by_id,
  1003. 'hosts_mapping_by_node_id': hosts_mapping_by_node_id,
  1004. 'page': page,
  1005. 'page_size': page_size,
  1006. 'keyword': keyword,
  1007. 'pages': pages,
  1008. 'last_page': last_page
  1009. }
  1010. return ret