#!/usr/bin/env python
# -*- coding: utf-8 -*-
import traceback
import signal
import time
from datetime import timedelta
import jimit as ji
import json
import os
import threading
from flask import g, request, redirect, url_for, Response, session, Flask
try:
from flask_session import Session
except ImportError as e:
# 兼容老版本
from flask.ext.session import Session
from flask.ext.themes2 import Themes, packaged_themes_loader
from werkzeug.debug import get_current_traceback
from models.initialize import logger, Init, app_config, dev_table
app = Flask(__name__)
app.jinja_env.add_extension('jinja2.ext.i18n')
app.jinja_env.add_extension('jinja2.ext.do')
app.config = dict(app.config, **app_config)
app.jinja_env.add_extension('jinja2.ext.loopcontrols')
from models import Utils
from models.event_processor import EventProcessor
import api_route_table
import views_route_table
from models import Database as db
from models import Config
from models import User
from api.user import blueprint as user_blueprint
from api.os_template_image import blueprint as os_template_image_blueprint
from api.os_template_image import blueprints as os_template_image_blueprints
from api.os_template_initialize_operate_set import blueprint as os_template_initialize_operate_set_blueprint
from api.os_template_initialize_operate_set import blueprints as os_template_initialize_operate_set_blueprints
from api.os_template_initialize_operate import blueprint as os_template_initialize_operate_blueprint
from api.os_template_initialize_operate import blueprints as os_template_initialize_operate_blueprints
from api.os_template_profile import blueprint as os_template_profile_blueprint
from api.os_template_profile import blueprints as os_template_profile_blueprints
from api.guest import blueprint as guest_blueprint
from api.guest import blueprints as guest_blueprints
from api.disk import blueprint as disk_blueprint
from api.disk import blueprints as disk_blueprints
from api.config import blueprint as config_blueprint
from api.log import blueprint as log_blueprint
from api.log import blueprints as log_blueprints
from api.host import blueprint as host_blueprint
from api.host import blueprints as host_blueprints
from api.ssh_key import blueprint as ssh_key_blueprint
from api.ssh_key import blueprints as ssh_key_blueprints
from api.snapshot import blueprint as snapshot_blueprint
from api.snapshot import blueprints as snapshot_blueprints
from api.guest_performance import blueprint as performance_blueprint
from api.guest_performance import blueprints as performance_blueprints
from api.host_performance import blueprint as host_performance_blueprint
from api.host_performance import blueprints as host_performance_blueprints
from api.dashboard import blueprint as dashboard_blueprint
from api.dashboard import blueprints as dashboard_blueprints
from views.error_pages import *
from views.config import blueprint as view_config_blueprint
from views.misc import blueprint as view_misc_blueprint
from views.dashboard import blueprint as view_dashboard_blueprint
from views.guest import blueprint as view_guest_blueprint
from views.guest import blueprints as view_guest_blueprints
from views.disk import blueprint as view_disk_blueprint
from views.disk import blueprints as view_disk_blueprints
from views.log import blueprint as view_log_blueprint
from views.log import blueprints as view_log_blueprints
from views.os_template_image import blueprint as view_os_template_image_blueprint
from views.os_template_image import blueprints as view_os_template_image_blueprints
from views.ssh_key import blueprint as view_ssh_key_blueprint
from views.ssh_key import blueprints as view_ssh_key_blueprints
from views.snapshot import blueprint as view_snapshot_blueprint
from views.snapshot import blueprints as view_snapshot_blueprints
from views.host import blueprint as view_host_blueprint
from views.host import blueprints as view_host_blueprints
from websockify.websocketproxy import WebSocketProxy
__author__ = 'James Iter'
__date__ = '2017/3/31'
__contact__ = 'james.iter.cn@gmail.com'
__copyright__ = '(c) 2017 by James Iter.'
# 替换为Flask-Session
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=app_config['PERMANENT_SESSION_LIFETIME'])
Session(app)
Themes(app, app_identifier="JimV-C")
l = packaged_themes_loader(app)
pass
@app.context_processor
def utility_processor():
def format_price(amount, currency=u'¥'):
return u'{0:.2f}{1}'.format(amount, currency)
def format_datetime_by_ts(ts, fmt='%Y-%m-%d %H:%M'):
return time.strftime(fmt, time.localtime(ts))
def format_datetime_by_tus(tus, fmt='%y-%m-%d %H:%M'):
return time.strftime(fmt, time.localtime(tus/1000/1000))
def format_guest_status(_status, progress):
from models.status import GuestState
color = 'FF645B'
icon = 'glyph-icon icon-bolt'
desc = '未知状态'
if _status == GuestState.booting.value:
color = '00BBBB'
icon = 'glyph-icon icon-circle'
desc = '启动中'
elif _status == GuestState.running.value:
color = '00BB00'
icon = 'glyph-icon icon-circle'
desc = '运行中'
elif _status in [GuestState.no_state.value, GuestState.creating.value]:
color = 'FFC543'
icon = 'glyph-icon icon-spinner'
desc = ' '.join(['创建中', str(progress)+'%'])
elif _status == GuestState.blocked.value:
color = '3D4245'
icon = 'glyph-icon icon-minus-square'
desc = '被阻塞'
elif _status == GuestState.paused.value:
color = 'B7B904'
icon = 'glyph-icon icon-pause'
desc = '暂停'
elif _status == GuestState.shutdown.value:
color = '4E5356'
icon = 'glyph-icon icon-terminal'
desc = '关闭'
elif _status == GuestState.shutoff.value:
color = 'FFC543'
icon = 'glyph-icon icon-plug'
desc = '断电'
elif _status == GuestState.crashed.value:
color = '9E2927'
icon = 'glyph-icon icon-question'
desc = '已崩溃'
elif _status == GuestState.pm_suspended.value:
color = 'FCFF07'
icon = 'glyph-icon icon-anchor'
desc = '悬挂'
elif _status == GuestState.migrating.value:
color = '1CF5E7'
icon = 'glyph-icon icon-space-shuttle'
desc = '迁移中'
elif _status == GuestState.dirty.value:
color = 'FF0707'
icon = 'glyph-icon icon-remove'
desc = '创建失败,待清理'
else:
pass
return ' {desc}'.format(
icon=icon, color=color, desc=desc)
def format_sequence_to_device_name(sequence):
# sequence 不能大于 25。dev_table 序数从 0 开始。
if sequence == -1:
return u'无'
if sequence >= dev_table.__len__():
return 'Unknown'
return dev_table[sequence]
def format_disk_state(state):
from models.status import DiskState
color = 'FF645B'
icon = 'glyph-icon icon-bolt'
desc = '未知状态'
if state == DiskState.pending.value:
color = 'FFC543'
icon = 'glyph-icon icon-spinner'
desc = '创建中'
elif state == DiskState.idle.value:
color = '0077BB'
icon = 'glyph-icon icon-unlink'
desc = '待挂载'
elif state == DiskState.mounted.value:
color = '00BB00'
icon = 'glyph-icon icon-link'
desc = '使用中'
elif state == DiskState.mounting.value:
color = '00BBBB'
icon = 'glyph-icon icon-elusive-upload'
desc = '挂载中'
elif state == DiskState.unloading.value:
color = '93969B'
icon = 'glyph-icon icon-elusive-download'
desc = '卸载中'
elif state == DiskState.dirty.value:
color = 'FF0707'
icon = 'glyph-icon icon-remove'
desc = '创建失败,待清理'
else:
pass
return ' {desc}'.format(
icon=icon, color=color, desc=desc)
return dict(format_price=format_price, format_datetime_by_tus=format_datetime_by_tus,
format_datetime_by_ts=format_datetime_by_ts, format_guest_status=format_guest_status,
format_sequence_to_device_name=format_sequence_to_device_name, format_disk_state=format_disk_state)
def instantiation_ws_vnc(listen_port, target_host, target_port):
# 用于 Web noVNC 代理
ws = WebSocketProxy(listen_host="0.0.0.0", listen_port=listen_port, target_host=target_host,
target_port=target_port, run_once=True, daemon=True, idle_timeout=10)
ws.start_server()
def ws_engine_for_vnc():
logger.info(msg='VNC ws engine is launched.')
while True:
payload = db.r.lpop(app_config['ipc_queue'])
if payload is None:
time.sleep(1)
continue
payload = json.loads(payload)
c_pid = os.fork()
if c_pid == 0:
instantiation_ws_vnc(payload['listen_port'], payload['target_host'], payload['target_port'])
# 因为 WebSocketProxy 使用了 daemon 参数,所以当执行到 ws.start_server() 时,会退出其所在的子进程,
# 故而这里设置wait来处理结束的子进程的环境,避免出现僵尸进程。
os.wait()
def is_not_need_to_auth(endpoint):
not_auth_table = [
'api_config.r_get',
'api_config.r_create',
'v_config.create',
'v_misc.login',
'v_misc.recover_password',
'v_misc.reset_password',
'api_user.r_sign_in',
'api_user.r_reset_password',
'api_user.r_send_reset_password_email'
]
if endpoint in not_auth_table:
return True
return False
@app.before_request
@Utils.dumps2response
def r_before_request():
try:
g.ts = ji.Common.ts()
if not is_not_need_to_auth(request.endpoint) and request.blueprint is not None and request.method != 'OPTIONS':
g.config = Config()
g.config.id = 1
g.config.get()
token = session.get('token', '')
g.token = Utils.verify_token(token)
user = User()
user.id = g.token['uid']
try:
user.get()
except ji.PreviewingError, e:
# 如果该用户获取失败,则清除该用户对应的session。因为该用户可能已经被删除。
for key in session.keys():
session.pop(key=key)
return json.loads(e.message)
except ji.JITError, e:
ret = json.loads(e.message)
if ret['state']['code'] == '404':
return redirect(location=url_for('v_config.create'), Response=Response)
if ret['state']['sub']['code'] in ['41208']:
return redirect(location=url_for('v_misc.login'), Response=Response)
return ret
@app.after_request
@Utils.dumps2response
def r_after_request(response):
try:
# https://developer.mozilla.org/en/HTTP_access_control
# (中文版) https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials
# http://www.w3.org/TR/cors/
# 由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。
if request.referrer is None:
# 跑测试脚本时,用该规则。
response.headers['Access-Control-Allow-Origin'] = '*'
else:
# 生产环境中,如果前后端分离。那么请指定具体的前端域名地址,不要用如下在开发环境中的便捷方式。
# -- Access-Control-Allow-Credentials为true,携带cookie时,不允许Access-Control-Allow-Origin为通配符,是浏览器对用户的一种安全保护。
# -- 至少能避免登录山寨网站,骗取用户相关信息。
response.headers['Access-Control-Allow-Origin'] = '/'.join(request.referrer.split('/')[:3])
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Methods'] = 'HEAD, GET, POST, DELETE, OPTIONS, PATCH, PUT'
response.headers['Access-Control-Allow-Headers'] = 'X-Request-With, Content-Type'
response.headers['Access-Control-Expose-Headers'] = 'Set-Cookie'
# 少于session生命周期一半时,自动对其续期
if not is_not_need_to_auth(request.endpoint) and hasattr(g, 'token') and \
g.token['exp'] < (ji.Common.ts() + (app_config['token_ttl'] / 2)):
token = Utils.generate_token(g.token['uid'])
# 清除原有session,由新session代替
for key in session.keys():
session.pop(key=key)
session['token'] = token
return response
except ji.JITError, e:
return json.loads(e.message)
@app.teardown_request
def teardown_request(exception):
if exception:
_traceback = get_current_traceback()
logger.error(_traceback.plaintext)
# noinspection PyBroadException
try:
db.init_conn_mysql()
db.init_conn_redis()
app.register_blueprint(user_blueprint)
app.register_blueprint(os_template_image_blueprint)
app.register_blueprint(os_template_image_blueprints)
app.register_blueprint(os_template_initialize_operate_set_blueprint)
app.register_blueprint(os_template_initialize_operate_set_blueprints)
app.register_blueprint(os_template_initialize_operate_blueprint)
app.register_blueprint(os_template_initialize_operate_blueprints)
app.register_blueprint(os_template_profile_blueprint)
app.register_blueprint(os_template_profile_blueprints)
app.register_blueprint(guest_blueprint)
app.register_blueprint(guest_blueprints)
app.register_blueprint(disk_blueprint)
app.register_blueprint(disk_blueprints)
app.register_blueprint(config_blueprint)
app.register_blueprint(log_blueprint)
app.register_blueprint(log_blueprints)
app.register_blueprint(host_blueprint)
app.register_blueprint(host_blueprints)
app.register_blueprint(ssh_key_blueprint)
app.register_blueprint(ssh_key_blueprints)
app.register_blueprint(snapshot_blueprint)
app.register_blueprint(snapshot_blueprints)
app.register_blueprint(performance_blueprint)
app.register_blueprint(performance_blueprints)
app.register_blueprint(host_performance_blueprint)
app.register_blueprint(host_performance_blueprints)
app.register_blueprint(dashboard_blueprint)
app.register_blueprint(dashboard_blueprints)
app.register_blueprint(view_config_blueprint)
app.register_blueprint(view_misc_blueprint)
app.register_blueprint(view_dashboard_blueprint)
app.register_blueprint(view_guest_blueprint)
app.register_blueprint(view_guest_blueprints)
app.register_blueprint(view_disk_blueprint)
app.register_blueprint(view_disk_blueprints)
app.register_blueprint(view_log_blueprint)
app.register_blueprint(view_log_blueprints)
app.register_blueprint(view_os_template_image_blueprint)
app.register_blueprint(view_os_template_image_blueprints)
app.register_blueprint(view_ssh_key_blueprint)
app.register_blueprint(view_ssh_key_blueprints)
app.register_blueprint(view_snapshot_blueprint)
app.register_blueprint(view_snapshot_blueprints)
app.register_blueprint(view_host_blueprint)
app.register_blueprint(view_host_blueprints)
except:
logger.error(traceback.format_exc())
threads = []
# noinspection PyBroadException
try:
signal.signal(signal.SIGTERM, Utils.signal_handle)
signal.signal(signal.SIGINT, Utils.signal_handle)
pid = os.fork()
if pid == 0:
ws_engine_for_vnc()
else:
# 父进程退出时,会清理所有提前退出的子进程的环境。所以这里无需对子进程做等待操作。
# 即:即使子进程提前退出,且因父进程没有做wait处理,使其变成了僵尸进程。但当父进程退出时,会对因其所产生的僵尸进程做统一清理操作。
t_ = threading.Thread(target=EventProcessor.launch, args=())
threads.append(t_)
t_ = threading.Thread(target=Init.pub_sub_ping_pong, args=())
threads.append(t_)
t_ = threading.Thread(target=Init.clear_expire_monitor_log, args=())
threads.append(t_)
for t in threads:
t.start()
except:
logger.error(traceback.format_exc())
exit(-1)
if __name__ == '__main__':
# noinspection PyBroadException
try:
app.run(host=app_config['listen'], port=app_config['port'], use_reloader=False, threaded=True)
while True:
if Utils.exit_flag:
# 主线程即将结束
break
time.sleep(1)
# 等待子线程结束
for t in threads:
t.join()
print 'Main say bye-bye!'
except:
logger.error(traceback.format_exc())
exit(-1)