main.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import traceback
  4. import signal
  5. import time
  6. from datetime import timedelta
  7. import jimit as ji
  8. import json
  9. import os
  10. import threading
  11. from flask import g, request, redirect, url_for, Response, session, Flask
  12. try:
  13. from flask_session import Session
  14. except ImportError as e:
  15. # 兼容老版本
  16. from flask.ext.session import Session
  17. from flask.ext.themes2 import Themes, packaged_themes_loader
  18. from werkzeug.debug import get_current_traceback
  19. from models.initialize import logger, Init, app_config, dev_table
  20. app = Flask(__name__)
  21. app.jinja_env.add_extension('jinja2.ext.i18n')
  22. app.jinja_env.add_extension('jinja2.ext.do')
  23. app.config = dict(app.config, **app_config)
  24. app.jinja_env.add_extension('jinja2.ext.loopcontrols')
  25. from models import Utils
  26. from models.event_processor import EventProcessor
  27. import api_route_table
  28. import views_route_table
  29. from models import Database as db
  30. from models import Config
  31. from models import User
  32. from api.user import blueprint as user_blueprint
  33. from api.os_template_image import blueprint as os_template_image_blueprint
  34. from api.os_template_image import blueprints as os_template_image_blueprints
  35. from api.os_template_initialize_operate_set import blueprint as os_template_initialize_operate_set_blueprint
  36. from api.os_template_initialize_operate_set import blueprints as os_template_initialize_operate_set_blueprints
  37. from api.os_template_initialize_operate import blueprint as os_template_initialize_operate_blueprint
  38. from api.os_template_initialize_operate import blueprints as os_template_initialize_operate_blueprints
  39. from api.os_template_profile import blueprint as os_template_profile_blueprint
  40. from api.os_template_profile import blueprints as os_template_profile_blueprints
  41. from api.guest import blueprint as guest_blueprint
  42. from api.guest import blueprints as guest_blueprints
  43. from api.disk import blueprint as disk_blueprint
  44. from api.disk import blueprints as disk_blueprints
  45. from api.config import blueprint as config_blueprint
  46. from api.log import blueprint as log_blueprint
  47. from api.log import blueprints as log_blueprints
  48. from api.host import blueprint as host_blueprint
  49. from api.host import blueprints as host_blueprints
  50. from api.ssh_key import blueprint as ssh_key_blueprint
  51. from api.ssh_key import blueprints as ssh_key_blueprints
  52. from api.snapshot import blueprint as snapshot_blueprint
  53. from api.snapshot import blueprints as snapshot_blueprints
  54. from api.guest_performance import blueprint as performance_blueprint
  55. from api.guest_performance import blueprints as performance_blueprints
  56. from api.host_performance import blueprint as host_performance_blueprint
  57. from api.host_performance import blueprints as host_performance_blueprints
  58. from api.dashboard import blueprint as dashboard_blueprint
  59. from api.dashboard import blueprints as dashboard_blueprints
  60. from views.error_pages import *
  61. from views.config import blueprint as view_config_blueprint
  62. from views.misc import blueprint as view_misc_blueprint
  63. from views.dashboard import blueprint as view_dashboard_blueprint
  64. from views.guest import blueprint as view_guest_blueprint
  65. from views.guest import blueprints as view_guest_blueprints
  66. from views.disk import blueprint as view_disk_blueprint
  67. from views.disk import blueprints as view_disk_blueprints
  68. from views.log import blueprint as view_log_blueprint
  69. from views.log import blueprints as view_log_blueprints
  70. from views.os_template_image import blueprint as view_os_template_image_blueprint
  71. from views.os_template_image import blueprints as view_os_template_image_blueprints
  72. from views.ssh_key import blueprint as view_ssh_key_blueprint
  73. from views.ssh_key import blueprints as view_ssh_key_blueprints
  74. from views.snapshot import blueprint as view_snapshot_blueprint
  75. from views.snapshot import blueprints as view_snapshot_blueprints
  76. from views.host import blueprint as view_host_blueprint
  77. from views.host import blueprints as view_host_blueprints
  78. from websockify.websocketproxy import WebSocketProxy
  79. __author__ = 'James Iter'
  80. __date__ = '2017/3/31'
  81. __contact__ = 'james.iter.cn@gmail.com'
  82. __copyright__ = '(c) 2017 by James Iter.'
  83. # 替换为Flask-Session
  84. app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=app_config['PERMANENT_SESSION_LIFETIME'])
  85. Session(app)
  86. Themes(app, app_identifier="JimV-C")
  87. l = packaged_themes_loader(app)
  88. pass
  89. @app.context_processor
  90. def utility_processor():
  91. def format_price(amount, currency=u'¥'):
  92. return u'{0:.2f}{1}'.format(amount, currency)
  93. def format_datetime_by_ts(ts, fmt='%Y-%m-%d %H:%M'):
  94. return time.strftime(fmt, time.localtime(ts))
  95. def format_datetime_by_tus(tus, fmt='%y-%m-%d %H:%M'):
  96. return time.strftime(fmt, time.localtime(tus/1000/1000))
  97. def format_guest_status(_status, progress):
  98. from models.status import GuestState
  99. color = 'FF645B'
  100. icon = 'glyph-icon icon-bolt'
  101. desc = '未知状态'
  102. if _status == GuestState.booting.value:
  103. color = '00BBBB'
  104. icon = 'glyph-icon icon-circle'
  105. desc = '启动中'
  106. elif _status == GuestState.running.value:
  107. color = '00BB00'
  108. icon = 'glyph-icon icon-circle'
  109. desc = '运行中'
  110. elif _status in [GuestState.no_state.value, GuestState.creating.value]:
  111. color = 'FFC543'
  112. icon = 'glyph-icon icon-spinner'
  113. desc = ' '.join(['创建中', str(progress)+'%'])
  114. elif _status == GuestState.blocked.value:
  115. color = '3D4245'
  116. icon = 'glyph-icon icon-minus-square'
  117. desc = '被阻塞'
  118. elif _status == GuestState.paused.value:
  119. color = 'B7B904'
  120. icon = 'glyph-icon icon-pause'
  121. desc = '暂停'
  122. elif _status == GuestState.shutdown.value:
  123. color = '4E5356'
  124. icon = 'glyph-icon icon-terminal'
  125. desc = '关闭'
  126. elif _status == GuestState.shutoff.value:
  127. color = 'FFC543'
  128. icon = 'glyph-icon icon-plug'
  129. desc = '断电'
  130. elif _status == GuestState.crashed.value:
  131. color = '9E2927'
  132. icon = 'glyph-icon icon-question'
  133. desc = '已崩溃'
  134. elif _status == GuestState.pm_suspended.value:
  135. color = 'FCFF07'
  136. icon = 'glyph-icon icon-anchor'
  137. desc = '悬挂'
  138. elif _status == GuestState.migrating.value:
  139. color = '1CF5E7'
  140. icon = 'glyph-icon icon-space-shuttle'
  141. desc = '迁移中'
  142. elif _status == GuestState.dirty.value:
  143. color = 'FF0707'
  144. icon = 'glyph-icon icon-remove'
  145. desc = '创建失败,待清理'
  146. else:
  147. pass
  148. return '<span class="{icon}" style="color: #{color};">&nbsp;&nbsp;{desc}</span>'.format(
  149. icon=icon, color=color, desc=desc)
  150. def format_sequence_to_device_name(sequence):
  151. # sequence 不能大于 25。dev_table 序数从 0 开始。
  152. if sequence == -1:
  153. return u'无'
  154. if sequence >= dev_table.__len__():
  155. return 'Unknown'
  156. return dev_table[sequence]
  157. def format_disk_state(state):
  158. from models.status import DiskState
  159. color = 'FF645B'
  160. icon = 'glyph-icon icon-bolt'
  161. desc = '未知状态'
  162. if state == DiskState.pending.value:
  163. color = 'FFC543'
  164. icon = 'glyph-icon icon-spinner'
  165. desc = '创建中'
  166. elif state == DiskState.idle.value:
  167. color = '0077BB'
  168. icon = 'glyph-icon icon-unlink'
  169. desc = '待挂载'
  170. elif state == DiskState.mounted.value:
  171. color = '00BB00'
  172. icon = 'glyph-icon icon-link'
  173. desc = '使用中'
  174. elif state == DiskState.mounting.value:
  175. color = '00BBBB'
  176. icon = 'glyph-icon icon-elusive-upload'
  177. desc = '挂载中'
  178. elif state == DiskState.unloading.value:
  179. color = '93969B'
  180. icon = 'glyph-icon icon-elusive-download'
  181. desc = '卸载中'
  182. elif state == DiskState.dirty.value:
  183. color = 'FF0707'
  184. icon = 'glyph-icon icon-remove'
  185. desc = '创建失败,待清理'
  186. else:
  187. pass
  188. return '<span class="{icon}" style="color: #{color};">&nbsp;&nbsp;{desc}</span>'.format(
  189. icon=icon, color=color, desc=desc)
  190. return dict(format_price=format_price, format_datetime_by_tus=format_datetime_by_tus,
  191. format_datetime_by_ts=format_datetime_by_ts, format_guest_status=format_guest_status,
  192. format_sequence_to_device_name=format_sequence_to_device_name, format_disk_state=format_disk_state)
  193. def instantiation_ws_vnc(listen_port, target_host, target_port):
  194. # 用于 Web noVNC 代理
  195. ws = WebSocketProxy(listen_host="0.0.0.0", listen_port=listen_port, target_host=target_host,
  196. target_port=target_port, run_once=True, daemon=True, idle_timeout=10)
  197. ws.start_server()
  198. def ws_engine_for_vnc():
  199. logger.info(msg='VNC ws engine is launched.')
  200. while True:
  201. payload = db.r.lpop(app_config['ipc_queue'])
  202. if payload is None:
  203. time.sleep(1)
  204. continue
  205. payload = json.loads(payload)
  206. c_pid = os.fork()
  207. if c_pid == 0:
  208. instantiation_ws_vnc(payload['listen_port'], payload['target_host'], payload['target_port'])
  209. # 因为 WebSocketProxy 使用了 daemon 参数,所以当执行到 ws.start_server() 时,会退出其所在的子进程,
  210. # 故而这里设置wait来处理结束的子进程的环境,避免出现僵尸进程。
  211. os.wait()
  212. def is_not_need_to_auth(endpoint):
  213. not_auth_table = [
  214. 'api_config.r_get',
  215. 'api_config.r_create',
  216. 'v_config.create',
  217. 'v_misc.login',
  218. 'v_misc.recover_password',
  219. 'v_misc.reset_password',
  220. 'api_user.r_sign_in',
  221. 'api_user.r_reset_password',
  222. 'api_user.r_send_reset_password_email'
  223. ]
  224. if endpoint in not_auth_table:
  225. return True
  226. return False
  227. @app.before_request
  228. @Utils.dumps2response
  229. def r_before_request():
  230. try:
  231. g.ts = ji.Common.ts()
  232. if not is_not_need_to_auth(request.endpoint) and request.blueprint is not None and request.method != 'OPTIONS':
  233. g.config = Config()
  234. g.config.id = 1
  235. g.config.get()
  236. token = session.get('token', '')
  237. g.token = Utils.verify_token(token)
  238. user = User()
  239. user.id = g.token['uid']
  240. try:
  241. user.get()
  242. except ji.PreviewingError, e:
  243. # 如果该用户获取失败,则清除该用户对应的session。因为该用户可能已经被删除。
  244. for key in session.keys():
  245. session.pop(key=key)
  246. return json.loads(e.message)
  247. except ji.JITError, e:
  248. ret = json.loads(e.message)
  249. if ret['state']['code'] == '404':
  250. return redirect(location=url_for('v_config.create'), Response=Response)
  251. if ret['state']['sub']['code'] in ['41208']:
  252. return redirect(location=url_for('v_misc.login'), Response=Response)
  253. return ret
  254. @app.after_request
  255. @Utils.dumps2response
  256. def r_after_request(response):
  257. try:
  258. # https://developer.mozilla.org/en/HTTP_access_control
  259. # (中文版) https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials
  260. # http://www.w3.org/TR/cors/
  261. # 由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。
  262. if request.referrer is None:
  263. # 跑测试脚本时,用该规则。
  264. response.headers['Access-Control-Allow-Origin'] = '*'
  265. else:
  266. # 生产环境中,如果前后端分离。那么请指定具体的前端域名地址,不要用如下在开发环境中的便捷方式。
  267. # -- Access-Control-Allow-Credentials为true,携带cookie时,不允许Access-Control-Allow-Origin为通配符,是浏览器对用户的一种安全保护。
  268. # -- 至少能避免登录山寨网站,骗取用户相关信息。
  269. response.headers['Access-Control-Allow-Origin'] = '/'.join(request.referrer.split('/')[:3])
  270. response.headers['Access-Control-Allow-Credentials'] = 'true'
  271. response.headers['Access-Control-Allow-Methods'] = 'HEAD, GET, POST, DELETE, OPTIONS, PATCH, PUT'
  272. response.headers['Access-Control-Allow-Headers'] = 'X-Request-With, Content-Type'
  273. response.headers['Access-Control-Expose-Headers'] = 'Set-Cookie'
  274. # 少于session生命周期一半时,自动对其续期
  275. if not is_not_need_to_auth(request.endpoint) and hasattr(g, 'token') and \
  276. g.token['exp'] < (ji.Common.ts() + (app_config['token_ttl'] / 2)):
  277. token = Utils.generate_token(g.token['uid'])
  278. # 清除原有session,由新session代替
  279. for key in session.keys():
  280. session.pop(key=key)
  281. session['token'] = token
  282. return response
  283. except ji.JITError, e:
  284. return json.loads(e.message)
  285. @app.teardown_request
  286. def teardown_request(exception):
  287. if exception:
  288. _traceback = get_current_traceback()
  289. logger.error(_traceback.plaintext)
  290. # noinspection PyBroadException
  291. try:
  292. db.init_conn_mysql()
  293. db.init_conn_redis()
  294. app.register_blueprint(user_blueprint)
  295. app.register_blueprint(os_template_image_blueprint)
  296. app.register_blueprint(os_template_image_blueprints)
  297. app.register_blueprint(os_template_initialize_operate_set_blueprint)
  298. app.register_blueprint(os_template_initialize_operate_set_blueprints)
  299. app.register_blueprint(os_template_initialize_operate_blueprint)
  300. app.register_blueprint(os_template_initialize_operate_blueprints)
  301. app.register_blueprint(os_template_profile_blueprint)
  302. app.register_blueprint(os_template_profile_blueprints)
  303. app.register_blueprint(guest_blueprint)
  304. app.register_blueprint(guest_blueprints)
  305. app.register_blueprint(disk_blueprint)
  306. app.register_blueprint(disk_blueprints)
  307. app.register_blueprint(config_blueprint)
  308. app.register_blueprint(log_blueprint)
  309. app.register_blueprint(log_blueprints)
  310. app.register_blueprint(host_blueprint)
  311. app.register_blueprint(host_blueprints)
  312. app.register_blueprint(ssh_key_blueprint)
  313. app.register_blueprint(ssh_key_blueprints)
  314. app.register_blueprint(snapshot_blueprint)
  315. app.register_blueprint(snapshot_blueprints)
  316. app.register_blueprint(performance_blueprint)
  317. app.register_blueprint(performance_blueprints)
  318. app.register_blueprint(host_performance_blueprint)
  319. app.register_blueprint(host_performance_blueprints)
  320. app.register_blueprint(dashboard_blueprint)
  321. app.register_blueprint(dashboard_blueprints)
  322. app.register_blueprint(view_config_blueprint)
  323. app.register_blueprint(view_misc_blueprint)
  324. app.register_blueprint(view_dashboard_blueprint)
  325. app.register_blueprint(view_guest_blueprint)
  326. app.register_blueprint(view_guest_blueprints)
  327. app.register_blueprint(view_disk_blueprint)
  328. app.register_blueprint(view_disk_blueprints)
  329. app.register_blueprint(view_log_blueprint)
  330. app.register_blueprint(view_log_blueprints)
  331. app.register_blueprint(view_os_template_image_blueprint)
  332. app.register_blueprint(view_os_template_image_blueprints)
  333. app.register_blueprint(view_ssh_key_blueprint)
  334. app.register_blueprint(view_ssh_key_blueprints)
  335. app.register_blueprint(view_snapshot_blueprint)
  336. app.register_blueprint(view_snapshot_blueprints)
  337. app.register_blueprint(view_host_blueprint)
  338. app.register_blueprint(view_host_blueprints)
  339. except:
  340. logger.error(traceback.format_exc())
  341. threads = []
  342. # noinspection PyBroadException
  343. try:
  344. signal.signal(signal.SIGTERM, Utils.signal_handle)
  345. signal.signal(signal.SIGINT, Utils.signal_handle)
  346. pid = os.fork()
  347. if pid == 0:
  348. ws_engine_for_vnc()
  349. else:
  350. # 父进程退出时,会清理所有提前退出的子进程的环境。所以这里无需对子进程做等待操作。
  351. # 即:即使子进程提前退出,且因父进程没有做wait处理,使其变成了僵尸进程。但当父进程退出时,会对因其所产生的僵尸进程做统一清理操作。
  352. t_ = threading.Thread(target=EventProcessor.launch, args=())
  353. threads.append(t_)
  354. t_ = threading.Thread(target=Init.pub_sub_ping_pong, args=())
  355. threads.append(t_)
  356. t_ = threading.Thread(target=Init.clear_expire_monitor_log, args=())
  357. threads.append(t_)
  358. for t in threads:
  359. t.start()
  360. except:
  361. logger.error(traceback.format_exc())
  362. exit(-1)
  363. if __name__ == '__main__':
  364. # noinspection PyBroadException
  365. try:
  366. app.run(host=app_config['listen'], port=app_config['port'], use_reloader=False, threaded=True)
  367. while True:
  368. if Utils.exit_flag:
  369. # 主线程即将结束
  370. break
  371. time.sleep(1)
  372. # 等待子线程结束
  373. for t in threads:
  374. t.join()
  375. print 'Main say bye-bye!'
  376. except:
  377. logger.error(traceback.format_exc())
  378. exit(-1)