disk.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from math import ceil
  4. from flask import Blueprint, request, url_for
  5. import json
  6. import requests
  7. from uuid import uuid4
  8. import jimit as ji
  9. from models import Guest, DiskState, Host
  10. from models.initialize import dev_table
  11. from models import Config
  12. from models import Disk
  13. from models import Rules
  14. from models import Utils
  15. from models import OSTemplateImage
  16. from models.status import StorageMode
  17. from base import Base
  18. __author__ = 'James Iter'
  19. __date__ = '2017/4/24'
  20. __contact__ = 'james.iter.cn@gmail.com'
  21. __copyright__ = '(c) 2017 by James Iter.'
  22. blueprint = Blueprint(
  23. 'api_disk',
  24. __name__,
  25. url_prefix='/api/disk'
  26. )
  27. blueprints = Blueprint(
  28. 'api_disks',
  29. __name__,
  30. url_prefix='/api/disks'
  31. )
  32. disk_base = Base(the_class=Disk, the_blueprint=blueprint, the_blueprints=blueprints)
  33. @Utils.dumps2response
  34. def r_create():
  35. args_rules = [
  36. Rules.DISK_SIZE.value,
  37. Rules.REMARK.value,
  38. Rules.QUANTITY.value
  39. ]
  40. config = Config()
  41. config.id = 1
  42. config.get()
  43. # 非共享模式,必须指定 node_id
  44. if config.storage_mode not in [StorageMode.shared_mount.value, StorageMode.ceph.value,
  45. StorageMode.glusterfs.value]:
  46. args_rules.append(
  47. Rules.NODE_ID.value
  48. )
  49. try:
  50. ji.Check.previewing(args_rules, request.json)
  51. size = request.json['size']
  52. quantity = request.json['quantity']
  53. ret = dict()
  54. ret['state'] = ji.Common.exchange_state(20000)
  55. # 如果是共享模式,则让负载最轻的计算节点去创建磁盘
  56. if config.storage_mode in [StorageMode.shared_mount.value, StorageMode.ceph.value,
  57. StorageMode.glusterfs.value]:
  58. available_hosts = Host.get_available_hosts()
  59. if available_hosts.__len__() == 0:
  60. ret['state'] = ji.Common.exchange_state(50351)
  61. return ret
  62. # 在可用计算节点中平均分配任务
  63. chosen_host = available_hosts[quantity % available_hosts.__len__()]
  64. request.json['node_id'] = chosen_host['node_id']
  65. node_id = request.json['node_id']
  66. if size < 1:
  67. ret['state'] = ji.Common.exchange_state(41255)
  68. return ret
  69. while quantity:
  70. quantity -= 1
  71. disk = Disk()
  72. disk.guest_uuid = ''
  73. disk.size = size
  74. disk.uuid = uuid4().__str__()
  75. disk.remark = request.json.get('remark', '')
  76. disk.node_id = int(node_id)
  77. disk.sequence = -1
  78. disk.format = 'qcow2'
  79. disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
  80. disk.quota(config=config)
  81. message = {
  82. '_object': 'disk',
  83. 'action': 'create',
  84. 'uuid': disk.uuid,
  85. 'storage_mode': config.storage_mode,
  86. 'dfs_volume': config.dfs_volume,
  87. 'node_id': disk.node_id,
  88. 'image_path': disk.path,
  89. 'size': disk.size
  90. }
  91. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  92. disk.create()
  93. return ret
  94. except ji.PreviewingError, e:
  95. return json.loads(e.message)
  96. @Utils.dumps2response
  97. def r_resize(uuid, size):
  98. args_rules = [
  99. Rules.UUID.value,
  100. Rules.DISK_SIZE_STR.value
  101. ]
  102. try:
  103. ji.Check.previewing(args_rules, {'uuid': uuid, 'size': size})
  104. disk = Disk()
  105. disk.uuid = uuid
  106. disk.get_by('uuid')
  107. ret = dict()
  108. ret['state'] = ji.Common.exchange_state(20000)
  109. if disk.size >= int(size):
  110. ret['state'] = ji.Common.exchange_state(41257)
  111. return ret
  112. config = Config()
  113. config.id = 1
  114. config.get()
  115. disk.size = int(size)
  116. disk.quota(config=config)
  117. # 将在事件返回层(models/event_processor.py:224 附近),更新数据库中 disk 对象
  118. message = {
  119. '_object': 'disk',
  120. 'action': 'resize',
  121. 'uuid': disk.uuid,
  122. 'guest_uuid': disk.guest_uuid,
  123. 'storage_mode': config.storage_mode,
  124. 'size': disk.size,
  125. 'dfs_volume': config.dfs_volume,
  126. 'node_id': disk.node_id,
  127. 'image_path': disk.path,
  128. 'disks': [disk.__dict__],
  129. 'passback_parameters': {'size': disk.size}
  130. }
  131. if config.storage_mode in [StorageMode.shared_mount.value, StorageMode.ceph.value,
  132. StorageMode.glusterfs.value]:
  133. message['node_id'] = Host.get_lightest_host()['node_id']
  134. if disk.guest_uuid.__len__() == 36:
  135. message['device_node'] = dev_table[disk.sequence]
  136. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  137. return ret
  138. except ji.PreviewingError, e:
  139. return json.loads(e.message)
  140. @Utils.dumps2response
  141. def r_delete(uuids):
  142. args_rules = [
  143. Rules.UUIDS.value
  144. ]
  145. try:
  146. ji.Check.previewing(args_rules, {'uuids': uuids})
  147. ret = dict()
  148. ret['state'] = ji.Common.exchange_state(20000)
  149. disk = Disk()
  150. # 检测所指定的 UUDIs 磁盘都存在
  151. for uuid in uuids.split(','):
  152. disk.uuid = uuid
  153. disk.get_by('uuid')
  154. # 判断磁盘是否与虚拟机处于离状态
  155. if disk.state not in [DiskState.idle.value, DiskState.dirty.value]:
  156. ret['state'] = ji.Common.exchange_state(41256)
  157. return ret
  158. config = Config()
  159. config.id = 1
  160. config.get()
  161. # 执行删除操作
  162. for uuid in uuids.split(','):
  163. disk.uuid = uuid
  164. disk.get_by('uuid')
  165. message = {
  166. '_object': 'disk',
  167. 'action': 'delete',
  168. 'uuid': disk.uuid,
  169. 'storage_mode': config.storage_mode,
  170. 'dfs_volume': config.dfs_volume,
  171. 'node_id': disk.node_id,
  172. 'image_path': disk.path
  173. }
  174. if config.storage_mode in [StorageMode.shared_mount.value, StorageMode.ceph.value,
  175. StorageMode.glusterfs.value]:
  176. message['node_id'] = Host.get_lightest_host()['node_id']
  177. Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
  178. return ret
  179. except ji.PreviewingError, e:
  180. return json.loads(e.message)
  181. def add_device(func):
  182. from functools import wraps
  183. @wraps(func)
  184. def _add_device(*args, **kwargs):
  185. ret = func(*args, **kwargs)
  186. if ret['data'].__len__() > 0:
  187. if isinstance(ret['data'], list):
  188. for i, item in enumerate(ret['data']):
  189. ret['data'][i][u'device'] = u'/dev/' + dev_table[item['sequence']]
  190. if item['sequence'] < 0:
  191. ret['data'][i][u'device'] = None
  192. elif isinstance(ret['data'], dict):
  193. ret['data'][u'device'] = u'/dev/' + dev_table[ret['data']['sequence']]
  194. if ret['data']['sequence'] < 0:
  195. ret['data'][u'device'] = None
  196. else:
  197. raise json.dumps(ret)
  198. return ret
  199. return _add_device
  200. @Utils.dumps2response
  201. @add_device
  202. def r_get(uuids):
  203. return disk_base.get(ids=uuids, ids_rule=Rules.UUIDS.value, by_field='uuid')
  204. @Utils.dumps2response
  205. @add_device
  206. def r_get_by_filter():
  207. return disk_base.get_by_filter()
  208. @Utils.dumps2response
  209. @add_device
  210. def r_content_search():
  211. return disk_base.content_search()
  212. @Utils.dumps2response
  213. def r_update(uuids):
  214. ret = dict()
  215. ret['state'] = ji.Common.exchange_state(20000)
  216. ret['data'] = list()
  217. args_rules = [
  218. Rules.UUIDS.value
  219. ]
  220. if 'remark' in request.json:
  221. args_rules.append(
  222. Rules.REMARK.value
  223. )
  224. if 'iops' in request.json:
  225. args_rules.append(
  226. Rules.IOPS.value
  227. )
  228. if 'iops_rd' in request.json:
  229. args_rules.append(
  230. Rules.IOPS_RD.value
  231. )
  232. if 'iops_wr' in request.json:
  233. args_rules.append(
  234. Rules.IOPS_WR.value
  235. )
  236. if 'iops_max' in request.json:
  237. args_rules.append(
  238. Rules.IOPS_MAX.value
  239. )
  240. if 'iops_max_length' in request.json:
  241. args_rules.append(
  242. Rules.IOPS_MAX_LENGTH.value
  243. )
  244. if 'bps' in request.json:
  245. args_rules.append(
  246. Rules.BPS.value
  247. )
  248. if 'bps_rd' in request.json:
  249. args_rules.append(
  250. Rules.BPS_RD.value
  251. )
  252. if 'bps_wr' in request.json:
  253. args_rules.append(
  254. Rules.BPS_WR.value
  255. )
  256. if 'bps_max' in request.json:
  257. args_rules.append(
  258. Rules.BPS_MAX.value
  259. )
  260. if 'bps_max_length' in request.json:
  261. args_rules.append(
  262. Rules.BPS_MAX_LENGTH.value
  263. )
  264. if args_rules.__len__() < 2:
  265. return ret
  266. request.json['uuids'] = uuids
  267. need_update_quota = False
  268. need_update_quota_parameters = ['iops', 'iops_rd', 'iops_wr', 'iops_max', 'iops_max_length',
  269. 'bps', 'bps_rd', 'bps_wr', 'bps_max', 'bps_max_length']
  270. if filter(lambda p: p in request.json, need_update_quota_parameters).__len__() > 0:
  271. need_update_quota = True
  272. try:
  273. ji.Check.previewing(args_rules, request.json)
  274. disk = Disk()
  275. # 检测所指定的 UUDIs 磁盘都存在
  276. for uuid in uuids.split(','):
  277. disk.uuid = uuid
  278. disk.get_by('uuid')
  279. for uuid in uuids.split(','):
  280. disk.uuid = uuid
  281. disk.get_by('uuid')
  282. disk.remark = request.json.get('remark', disk.remark)
  283. disk.iops = request.json.get('iops', disk.iops)
  284. disk.iops_rd = request.json.get('iops_rd', disk.iops_rd)
  285. disk.iops_wr = request.json.get('iops_wr', disk.iops_wr)
  286. disk.iops_max = request.json.get('iops_max', disk.iops_max)
  287. disk.iops_max_length = request.json.get('iops_max_length', disk.iops_max_length)
  288. disk.bps = request.json.get('bps', disk.bps)
  289. disk.bps_rd = request.json.get('bps_rd', disk.bps_rd)
  290. disk.bps_wr = request.json.get('bps_wr', disk.bps_wr)
  291. disk.bps_max = request.json.get('bps_max', disk.bps_max)
  292. disk.bps_max_length = request.json.get('bps_max_length', disk.bps_max_length)
  293. disk.update()
  294. disk.get()
  295. if disk.sequence >= 0 and need_update_quota:
  296. message = {
  297. '_object': 'disk',
  298. 'action': 'quota',
  299. 'uuid': disk.uuid,
  300. 'guest_uuid': disk.guest_uuid,
  301. 'node_id': disk.node_id,
  302. 'disks': [disk.__dict__]
  303. }
  304. Utils.emit_instruction(message=json.dumps(message))
  305. ret['data'].append(disk.__dict__)
  306. return ret
  307. except ji.PreviewingError, e:
  308. return json.loads(e.message)
  309. @Utils.dumps2response
  310. def r_distribute_count():
  311. from models import Disk
  312. rows, count = Disk.get_all()
  313. ret = dict()
  314. ret['state'] = ji.Common.exchange_state(20000)
  315. ret['data'] = {
  316. 'kind': {'system': 0, 'data_mounted': 0, 'data_idle': 0},
  317. 'total_size': 0,
  318. 'disks': rows.__len__()
  319. }
  320. for disk in rows:
  321. if disk['sequence'] == 0:
  322. ret['data']['kind']['system'] += 1
  323. elif disk['sequence'] < 0:
  324. ret['data']['kind']['data_idle'] += 1
  325. else:
  326. ret['data']['kind']['data_mounted'] += 1
  327. ret['data']['total_size'] += disk['size']
  328. return ret
  329. @Utils.dumps2response
  330. def r_show():
  331. args = list()
  332. page = request.args.get('page', 1)
  333. if page == '':
  334. page = 1
  335. page = int(page)
  336. page_size = int(request.args.get('page_size', 10))
  337. keyword = request.args.get('keyword', None)
  338. show_area = request.args.get('show_area', 'unmount')
  339. guest_uuid = request.args.get('guest_uuid', None)
  340. sequence = request.args.get('sequence', None)
  341. order_by = request.args.get('order_by', None)
  342. order = request.args.get('order', None)
  343. filters = list()
  344. if page is not None:
  345. args.append('page=' + page.__str__())
  346. if page_size is not None:
  347. args.append('page_size=' + page_size.__str__())
  348. if keyword is not None:
  349. args.append('keyword=' + keyword.__str__())
  350. if guest_uuid is not None:
  351. filters.append('guest_uuid:in:' + guest_uuid.__str__())
  352. show_area = 'all'
  353. if sequence is not None:
  354. filters.append('sequence:in:' + sequence.__str__())
  355. show_area = 'all'
  356. if show_area in ['unmount', 'data_disk', 'all']:
  357. if show_area == 'unmount':
  358. filters.append('sequence:eq:-1')
  359. elif show_area == 'data_disk':
  360. filters.append('sequence:gt:0')
  361. else:
  362. pass
  363. else:
  364. # 与前端页面相照应,首次打开时,默认只显示未挂载的磁盘
  365. filters.append('sequence:eq:-1')
  366. if order_by is not None:
  367. args.append('order_by=' + order_by)
  368. if order is not None:
  369. args.append('order=' + order)
  370. if filters.__len__() > 0:
  371. args.append('filter=' + ','.join(filters))
  372. hosts_url = url_for('api_hosts.r_get_by_filter', _external=True)
  373. disks_url = url_for('api_disks.r_get_by_filter', _external=True)
  374. if keyword is not None:
  375. disks_url = url_for('api_disks.r_content_search', _external=True)
  376. # 关键字检索,不支持显示域过滤
  377. show_area = 'all'
  378. hosts_ret = requests.get(url=hosts_url, cookies=request.cookies)
  379. hosts_ret = json.loads(hosts_ret.content)
  380. hosts_mapping_by_node_id = dict()
  381. for host in hosts_ret['data']:
  382. hosts_mapping_by_node_id[int(host['node_id'])] = host
  383. if args.__len__() > 0:
  384. disks_url = disks_url + '?' + '&'.join(args)
  385. disks_ret = requests.get(url=disks_url, cookies=request.cookies)
  386. disks_ret = json.loads(disks_ret.content)
  387. guests_uuid = list()
  388. disks_uuid = list()
  389. for disk in disks_ret['data']:
  390. disks_uuid.append(disk['uuid'])
  391. if disk['guest_uuid'].__len__() == 36:
  392. guests_uuid.append(disk['guest_uuid'])
  393. if guests_uuid.__len__() > 0:
  394. guests, _ = Guest.get_by_filter(filter_str='uuid:in:' + ','.join(guests_uuid))
  395. guests_uuid_mapping = dict()
  396. for guest in guests:
  397. guests_uuid_mapping[guest['uuid']] = guest
  398. for i, disk in enumerate(disks_ret['data']):
  399. if disk['guest_uuid'].__len__() == 36:
  400. disks_ret['data'][i]['guest'] = guests_uuid_mapping[disk['guest_uuid']]
  401. if disks_uuid.__len__() > 0:
  402. snapshots_id_mapping_by_disks_uuid_url = url_for('api_snapshots.r_get_snapshots_by_disks_uuid',
  403. disks_uuid=','.join(disks_uuid), _external=True)
  404. snapshots_id_mapping_by_disks_uuid_ret = requests.get(url=snapshots_id_mapping_by_disks_uuid_url,
  405. cookies=request.cookies)
  406. snapshots_id_mapping_by_disks_uuid_ret = json.loads(snapshots_id_mapping_by_disks_uuid_ret.content)
  407. snapshots_id_mapping_by_disk_uuid = dict()
  408. for snapshot_id_mapping_by_disk_uuid in snapshots_id_mapping_by_disks_uuid_ret['data']:
  409. disk_uuid = snapshot_id_mapping_by_disk_uuid['disk_uuid']
  410. snapshot_id = snapshot_id_mapping_by_disk_uuid['snapshot_id']
  411. if disk_uuid not in snapshots_id_mapping_by_disk_uuid:
  412. snapshots_id_mapping_by_disk_uuid[disk_uuid] = list()
  413. snapshots_id_mapping_by_disk_uuid[disk_uuid].append(snapshot_id)
  414. for i, disk in enumerate(disks_ret['data']):
  415. if disk['uuid'] in snapshots_id_mapping_by_disk_uuid:
  416. disks_ret['data'][i]['snapshot'] = snapshots_id_mapping_by_disk_uuid[disk['uuid']]
  417. config = Config()
  418. config.id = 1
  419. config.get()
  420. show_on_host = False
  421. if config.storage_mode == StorageMode.local.value:
  422. show_on_host = True
  423. last_page = int(ceil(disks_ret['paging']['total'] / float(page_size)))
  424. page_length = 5
  425. pages = list()
  426. if page < int(ceil(page_length / 2.0)):
  427. for i in range(1, page_length + 1):
  428. pages.append(i)
  429. if i == last_page or last_page == 0:
  430. break
  431. elif last_page - page < page_length / 2:
  432. for i in range(last_page - page_length + 1, last_page + 1):
  433. if i < 1:
  434. continue
  435. pages.append(i)
  436. else:
  437. for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
  438. pages.append(i)
  439. if i == last_page or last_page == 0:
  440. break
  441. ret = dict()
  442. ret['state'] = ji.Common.exchange_state(20000)
  443. ret['data'] = {
  444. 'disks': disks_ret['data'],
  445. 'hosts_mapping_by_node_id': hosts_mapping_by_node_id,
  446. 'order_by': order_by,
  447. 'order': order,
  448. 'show_area': show_area,
  449. 'config': config.__dict__,
  450. 'show_on_host': show_on_host,
  451. 'paging': disks_ret['paging'],
  452. 'page': page,
  453. 'page_size': page_size,
  454. 'keyword': keyword,
  455. 'pages': pages,
  456. 'last_page': last_page
  457. }
  458. return ret
  459. @Utils.dumps2response
  460. def r_detail(uuid):
  461. disk = Disk()
  462. disk.uuid = uuid
  463. disk.get_by(field='uuid')
  464. disk.wrap_device(dev_table=dev_table)
  465. guest = None
  466. os_template_image = None
  467. config = Config()
  468. config.id = 1
  469. config.get()
  470. if disk.sequence != -1:
  471. guest = Guest()
  472. guest.uuid = disk.guest_uuid
  473. guest.get_by('uuid')
  474. os_template_image = OSTemplateImage()
  475. os_template_image.id = guest.os_template_image_id
  476. os_template_image.get()
  477. guest = guest.__dict__
  478. os_template_image = os_template_image.__dict__
  479. ret = dict()
  480. ret['state'] = ji.Common.exchange_state(20000)
  481. ret['data'] = {
  482. 'guest': guest,
  483. 'os_template_image': os_template_image,
  484. 'disk': disk.__dict__,
  485. 'config': config.__dict__
  486. }
  487. return ret