deployer.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Author: wushuiyong
  4. # @Created Time : 日 1/ 1 23:43:12 2017
  5. # @Description:
  6. import time
  7. from datetime import datetime
  8. import os
  9. import pwd
  10. import re
  11. from flask import current_app
  12. from flask_socketio import emit
  13. from walle.model.project import ProjectModel
  14. from walle.model.record import RecordModel
  15. from walle.model.task import TaskModel
  16. from walle.service.code import Code
  17. from walle.service.error import WalleError
  18. from walle.service.utils import color_clean
  19. from walle.service.utils import excludes_format, includes_format
  20. from walle.service.notice import Notice
  21. from walle.service.waller import Waller
  22. from flask_login import current_user
  23. class Deployer:
  24. '''
  25. 序列号
  26. '''
  27. stage = 'init'
  28. sequence = 0
  29. stage_prev_deploy = 'prev_deploy'
  30. stage_deploy = 'deploy'
  31. stage_post_deploy = 'post_deploy'
  32. stage_prev_release = 'prev_release'
  33. stage_release = 'release'
  34. stage_post_release = 'post_release'
  35. task_id = '0'
  36. user_id = '0'
  37. taskMdl = None
  38. TaskRecord = None
  39. console = False
  40. custom_global_env = {}
  41. version = datetime.now().strftime('%Y%m%d%H%M%S')
  42. local_codebase, dir_codebase_project, project_name = None, None, None
  43. dir_release, dir_webroot = None, None
  44. connections, success, errors = {}, {}, {}
  45. release_version_tar, previous_release_version, release_version = None, None, None
  46. local = None
  47. def __init__(self, task_id=None, project_id=None, console=False):
  48. self.local_codebase = current_app.config.get('CODE_BASE').rstrip('/') + '/'
  49. self.localhost = Waller(host='127.0.0.1')
  50. self.TaskRecord = RecordModel()
  51. if task_id:
  52. self.task_id = task_id
  53. # task start
  54. current_app.logger.info(self.task_id)
  55. self.taskMdl = TaskModel().item(self.task_id)
  56. self.user_id = self.taskMdl.get('user_id')
  57. self.servers = self.taskMdl.get('servers_info')
  58. self.project_info = self.taskMdl.get('project_info')
  59. # copy to a local version
  60. self.release_version = '{project_id}_{task_id}_{timestamp}'.format(
  61. project_id=self.project_info['id'],
  62. task_id=self.task_id,
  63. timestamp=time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())),
  64. )
  65. current_app.logger.info(self.taskMdl)
  66. self.custom_global_env = {
  67. 'WEBROOT': str(self.project_info['target_root']),
  68. 'CURRENT_RELEASE': str(self.release_version),
  69. 'BRANCH': str(self.taskMdl.get('branch')),
  70. 'TAG': str(self.taskMdl.get('tag')),
  71. 'COMMIT_ID': str(self.taskMdl.get('commit_id')),
  72. 'PROJECT_NAME': '"{}"'.format(self.project_info['name']),
  73. 'PROJECT_ID': str(self.project_info['id']),
  74. 'TASK_NAME': '"{}"'.format(self.taskMdl.get('name')),
  75. 'TASK_ID': str(self.task_id),
  76. 'DEPLOY_USER': str(self.taskMdl.get('user_name')),
  77. 'DEPLOY_TIME': str(time.strftime('%Y%m%d-%H:%M:%S', time.localtime(time.time()))),
  78. }
  79. if self.project_info['task_vars']:
  80. task_vars = [i.strip() for i in self.project_info['task_vars'].split('\n') if i.strip() and not i.strip().startswith('#')]
  81. for var in task_vars:
  82. var_list = var.split('=', 1)
  83. if len(var_list) != 2:
  84. continue
  85. self.custom_global_env[var_list[0]] = var_list[1]
  86. self.localhost.init_env(env=self.custom_global_env)
  87. if project_id:
  88. self.project_id = project_id
  89. self.project_info = ProjectModel(id=project_id).item()
  90. self.servers = self.project_info['servers_info']
  91. self.project_name = self.project_info['id']
  92. self.dir_codebase_project = self.local_codebase + str(self.project_name)
  93. # self.init_repo()
  94. # start to deploy
  95. self.console = console
  96. def config(self, console=None):
  97. return {
  98. 'task_id': self.task_id,
  99. 'user_id': self.user_id,
  100. 'stage': self.stage,
  101. 'sequence': self.sequence,
  102. 'console': console if console is not None else self.console
  103. }
  104. def start(self):
  105. RecordModel().query.filter_by(task_id=self.task_id).delete()
  106. TaskModel().get_by_id(self.task_id).update({'status': TaskModel.status_doing})
  107. self.taskMdl = TaskModel().item(self.task_id)
  108. # ===================== fabric ================
  109. # SocketHandler
  110. def prev_deploy(self):
  111. '''
  112. # TODO
  113. socketio.sleep(0.001)
  114. 1.代码检出前要做的基础工作
  115. - 检查 当前用户
  116. - 检查 python 版本
  117. - 检查 git 版本
  118. - 检查 目录是否存在
  119. - 用户自定义命令
  120. :return:
  121. '''
  122. self.stage = self.stage_prev_deploy
  123. self.sequence = 1
  124. # 检查 目录是否存在
  125. self.init_repo()
  126. # 用户自定义命令
  127. commands = self.project_info['prev_deploy']
  128. if commands:
  129. for command in commands.split('\n'):
  130. if command.strip().startswith('#') or not command.strip():
  131. continue
  132. with self.localhost.cd(self.dir_codebase_project):
  133. result = self.localhost.local(command, wenv=self.config())
  134. def deploy(self):
  135. '''
  136. 2.检出代码
  137. :param project_name:
  138. :return:
  139. '''
  140. self.stage = self.stage_deploy
  141. self.sequence = 2
  142. #
  143. # # copy to a local version
  144. # self.release_version = '%s_%s_%s' % (
  145. # self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))
  146. with self.localhost.cd(self.local_codebase):
  147. command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
  148. current_app.logger.info('cd %s command: %s ', self.dir_codebase_project, command)
  149. result = self.localhost.local(command, wenv=self.config())
  150. # 更新到指定 commit_id
  151. with self.localhost.cd(self.local_codebase + self.release_version):
  152. command = 'git reset -q --hard %s' % (self.taskMdl.get('commit_id'))
  153. result = self.localhost.local(command, wenv=self.config())
  154. if result.exited != Code.Ok:
  155. raise WalleError(Code.shell_git_fail, message=result.stdout)
  156. def post_deploy(self):
  157. '''
  158. 3.检出代码后要做的任务
  159. - 用户自定义操作命令
  160. - 代码编译
  161. - 清除日志文件及无用文件
  162. -
  163. - 压缩打包
  164. - 传送到版本库 release
  165. :return:
  166. '''
  167. self.stage = self.stage_post_deploy
  168. self.sequence = 3
  169. # 用户自定义命令
  170. commands = self.project_info['post_deploy']
  171. if commands:
  172. for command in commands.split('\n'):
  173. if command.strip().startswith('#') or not command.strip():
  174. continue
  175. with self.localhost.cd(self.local_codebase + self.release_version):
  176. result = self.localhost.local(command, wenv=self.config())
  177. # 压缩打包
  178. # 排除文件发布
  179. self.release_version_tar = '%s.tgz' % (self.release_version)
  180. with self.localhost.cd(self.local_codebase):
  181. if self.project_info['is_include']:
  182. files = includes_format(self.release_version, self.project_info['excludes'])
  183. else:
  184. files = excludes_format(self.release_version, self.project_info['excludes'])
  185. command = 'tar zcf %s/%s %s' % (self.local_codebase.rstrip('/'), self.release_version_tar, files)
  186. result = self.localhost.local(command, wenv=self.config())
  187. def prev_release(self, waller):
  188. '''
  189. 4.部署代码到目标机器前做的任务
  190. - 检查 webroot 父目录是否存在
  191. :return:
  192. '''
  193. self.stage = self.stage_prev_release
  194. self.sequence = 4
  195. # 检查 target_releases 父目录是否存在
  196. command = 'mkdir -p %s' % (self.project_info['target_releases'])
  197. result = waller.run(command, wenv=self.config())
  198. # TODO md5
  199. # 传送到版本库 release
  200. result = waller.put(self.local_codebase + self.release_version_tar,
  201. remote=self.project_info['target_releases'], wenv=self.config())
  202. current_app.logger.info('command: %s', dir(result))
  203. # 解压
  204. self.release_untar(waller)
  205. # 用户自定义命令
  206. self.prev_release_custom(waller)
  207. def prev_release_custom(self, waller):
  208. # 用户自定义命令
  209. commands = self.project_info['prev_release']
  210. if commands:
  211. for command in commands.split('\n'):
  212. if command.strip().startswith('#') or not command.strip():
  213. continue
  214. # TODO
  215. target_release_version = "%s/%s" % (self.project_info['target_releases'], self.release_version)
  216. with waller.cd(target_release_version):
  217. result = waller.run(command, wenv=self.config())
  218. def release(self, waller):
  219. '''
  220. 5.部署代码到目标机器做的任务
  221. - 打包代码 local
  222. - scp local => remote
  223. - 解压 remote
  224. :return:
  225. '''
  226. self.stage = self.stage_release
  227. self.sequence = 5
  228. with waller.cd(self.project_info['target_releases']):
  229. # 0. get previous link
  230. command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
  231. result = waller.run(command, wenv=self.config(console=False))
  232. self.previous_release_version = os.path.basename(result.stdout).strip()
  233. # 1. create a tmp link dir
  234. current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
  235. command = 'ln -sfn %s/%s %s' % (
  236. self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
  237. result = waller.run(command, wenv=self.config())
  238. # 2. make a soft link from release to tmp link
  239. # 3. move tmp link to webroot
  240. current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
  241. command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
  242. result = waller.run(command, wenv=self.config())
  243. def rollback(self, waller):
  244. '''
  245. 5.部署代码到目标机器做的任务
  246. - 恢复旧版本
  247. :return:
  248. '''
  249. self.stage = self.stage_release
  250. self.sequence = 5
  251. with waller.cd(self.project_info['target_releases']):
  252. # 0. get previous link
  253. command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
  254. result = waller.run(command, wenv=self.config(console=False))
  255. self.previous_release_version = os.path.basename(result.stdout)
  256. # 1. create a tmp link dir
  257. current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
  258. command = 'ln -sfn %s/%s %s' % (
  259. self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
  260. result = waller.run(command, wenv=self.config())
  261. # 2. make a soft link from release to tmp link
  262. # 3. move tmp link to webroot
  263. current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
  264. command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
  265. result = waller.run(command, wenv=self.config())
  266. def release_untar(self, waller):
  267. '''
  268. 解压版本包
  269. :return:
  270. '''
  271. with waller.cd(self.project_info['target_releases']):
  272. command = 'tar zxf %s' % (self.release_version_tar)
  273. result = waller.run(command, wenv=self.config())
  274. def post_release(self, waller):
  275. '''
  276. 6.部署代码到目标机器后要做的任务
  277. - 切换软链
  278. - 重启 nginx
  279. :return:
  280. '''
  281. self.stage = self.stage_post_release
  282. self.sequence = 6
  283. # 用户自定义命令
  284. commands = self.project_info['post_release']
  285. if commands:
  286. for command in commands.split('\n'):
  287. if command.strip().startswith('#') or not command.strip():
  288. continue
  289. # TODO
  290. with waller.cd(self.project_info['target_root']):
  291. pty = False if command.find('nohup') >= 0 else True
  292. result = waller.run(command, wenv=self.config(), pty=pty)
  293. # 个性化,用户重启的不一定是NGINX,可能是tomcat, apache, php-fpm等
  294. # self.post_release_service(waller)
  295. # 清理现场
  296. self.cleanup_remote(waller)
  297. def post_release_service(self, waller):
  298. '''
  299. 代码部署完成后,服务启动工作,如: nginx重启
  300. :param connection:
  301. :return:
  302. '''
  303. with waller.cd(self.project_info['target_root']):
  304. command = 'sudo service nginx restart'
  305. result = waller.run(command, wenv=self.config())
  306. def project_detection(self):
  307. errors = []
  308. # LOCAL_SERVER_USER => git
  309. # LOCAL_SERVER_USER => target_servers
  310. for server_info in self.servers:
  311. waller = Waller(host=server_info['host'], user=server_info['user'], port=server_info['port'])
  312. result = waller.run('id', exception=False, wenv=self.config())
  313. if result.failed:
  314. errors.append({
  315. 'title': '远程目标机器免密码登录失败',
  316. 'why': '远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
  317. 'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
  318. pwd.getpwuid(os.getuid())[0], server_info['host']),
  319. })
  320. # maybe this is no webroot's parent dir
  321. command = '[ -d {webroot} ] || mkdir -p {webroot}'.format(webroot=os.path.basename(self.project_info['target_root']))
  322. result = waller.run(command, exception=False, wenv=self.config(console=False))
  323. # 检查 webroot 父目录是否存在,是否为软链
  324. command = '[ -L "%s" ] && echo "true" || echo "false"' % (self.project_info['target_root'])
  325. result = waller.run(command, exception=False, wenv=self.config())
  326. if result.stdout == 'false':
  327. errors.append({
  328. 'title': '远程目标机器webroot不能是已建好的目录',
  329. 'why': '远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' % (server_info['host']),
  330. 'how': '手工删除远程目标机器:%s webroot目录:%s' % (server_info['host'], self.project_info['target_root']),
  331. })
  332. # remote release directory
  333. return errors
  334. def list_tag(self):
  335. self.init_repo()
  336. with self.localhost.cd(self.dir_codebase_project):
  337. command = 'git tag -l'
  338. result = self.localhost.local(command, pty=False, wenv=self.config())
  339. tags = result.stdout.strip()
  340. tags = tags.split('\n')
  341. return [color_clean(tag.strip()) for tag in tags]
  342. return []
  343. def list_branch(self):
  344. self.init_repo()
  345. with self.localhost.cd(self.dir_codebase_project):
  346. command = 'git pull'
  347. result = self.localhost.local(command, wenv=self.config())
  348. if result.exited != Code.Ok:
  349. raise WalleError(Code.shell_git_pull_fail, message=result.stdout)
  350. current_app.logger.info(self.dir_codebase_project)
  351. command = 'git branch -r'
  352. result = self.localhost.local(command, pty=False, wenv=self.config())
  353. # if result.exited != Code.Ok:
  354. # raise WalleError(Code.shell_run_fail)
  355. # TODO 三种可能: false, error, success
  356. branches = result.stdout.strip()
  357. branches = branches.split('\n')
  358. # 去除 origin/HEAD -> 当前指向
  359. # 去除远端前缀
  360. branches = [branch.strip().lstrip('origin/') for branch in branches if
  361. not branch.strip().startswith('origin/HEAD')]
  362. return branches
  363. return None
  364. def list_commit(self, branch):
  365. self.init_repo()
  366. with self.localhost.cd(self.dir_codebase_project):
  367. command = 'git checkout %s && git pull' % (branch)
  368. self.localhost.local(command, wenv=self.config())
  369. command = 'git log -50 --pretty="%h #@_@# %an #@_@# %s"'
  370. result = self.localhost.local(command, pty=False, wenv=self.config())
  371. current_app.logger.info(result.stdout)
  372. commit_log = result.stdout.strip()
  373. current_app.logger.info(commit_log)
  374. commit_list = commit_log.split('\n')
  375. commits = []
  376. for commit in commit_list:
  377. if not re.search('^.+ #@_@# .+ #@_@# .*$', commit):
  378. continue
  379. commit_dict = commit.split(' #@_@# ')
  380. current_app.logger.info(commit_dict)
  381. commits.append({
  382. 'id': commit_dict[0],
  383. 'name': commit_dict[1],
  384. 'message': commit_dict[2],
  385. })
  386. return commits
  387. # TODO
  388. return None
  389. def init_repo(self):
  390. if not os.path.exists(self.dir_codebase_project):
  391. # 检查 目录是否存在
  392. command = 'mkdir -p %s' % (self.dir_codebase_project)
  393. self.localhost.local(command, wenv=self.config())
  394. with self.localhost.cd(self.dir_codebase_project):
  395. is_git_dir = self.localhost.local('[ -d ".git" ] && git status', exception=False, wenv=self.config())
  396. if is_git_dir.exited != Code.Ok:
  397. # 否则当作新项目检出完整代码
  398. # 检查 目录是否存在
  399. command = 'rm -rf %s' % (self.dir_codebase_project)
  400. self.localhost.local(command, wenv=self.config())
  401. command = 'git clone %s %s' % (self.project_info['repo_url'], self.dir_codebase_project)
  402. current_app.logger.info('cd %s command: %s ' % (self.dir_codebase_project, command))
  403. result = self.localhost.local(command, wenv=self.config())
  404. if result.exited != Code.Ok:
  405. raise WalleError(Code.shell_git_init_fail, message=result.stdout)
  406. def cleanup_remote(self, waller):
  407. command = 'rm -rf `ls -t | tail -n +{keep_version_num}`'.format(
  408. keep_version_num=int(self.project_info['keep_version_num']) + 1)
  409. with waller.cd(self.project_info['target_releases']):
  410. result = waller.run(command, exception=False, wenv=self.config())
  411. def logs(self):
  412. return RecordModel().fetch(task_id=self.task_id)
  413. def end(self, success=True, update_status=True):
  414. if update_status:
  415. status = TaskModel.status_success if success else TaskModel.status_fail
  416. current_app.logger.info('success:%s, status:%s' % (success, status))
  417. TaskModel().get_by_id(self.task_id).update({
  418. 'status': status,
  419. 'link_id': self.release_version,
  420. 'ex_link_id': self.previous_release_version,
  421. })
  422. notice_info = {
  423. 'title': '',
  424. 'username': current_user.username,
  425. 'project_name': self.project_info['name'],
  426. 'task_name': '%s ([%s](%s))' % (self.taskMdl.get('name'), self.task_id, Notice.task_url(project_name=self.project_info['name'], task_id=self.task_id)),
  427. 'branch': self.taskMdl.get('branch'),
  428. 'commit': self.taskMdl.get('commit_id'),
  429. 'is_branch': self.project_info['repo_mode'],
  430. }
  431. notice = Notice.create(self.project_info['notice_type'])
  432. if success:
  433. notice_info['title'] = '上线部署成功'
  434. notice.deploy_task(project_info=self.project_info, notice_info=notice_info)
  435. else:
  436. notice_info['title'] = '上线部署失败'
  437. notice.deploy_task(project_info=self.project_info, notice_info=notice_info)
  438. if success:
  439. emit('success', {'event': 'finish', 'data': {'message': '部署完成,辛苦了,为你的努力喝彩!'}}, room=self.task_id)
  440. else:
  441. emit('fail', {'event': 'finish', 'data': {'message': Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
  442. def walle_deploy(self):
  443. self.start()
  444. try:
  445. self.prev_deploy()
  446. self.deploy()
  447. self.post_deploy()
  448. is_all_servers_success = True
  449. for server_info in self.servers:
  450. host = server_info['host']
  451. try:
  452. waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
  453. waller.init_env(env=self.custom_global_env)
  454. self.connections[host] = waller
  455. self.prev_release(self.connections[host])
  456. self.release(self.connections[host])
  457. self.post_release(self.connections[host])
  458. RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
  459. task_id=self.task_id, status=RecordModel.status_success, host=host,
  460. user=server_info['user'], command='')
  461. emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
  462. except Exception as e:
  463. is_all_servers_success = False
  464. current_app.logger.error(e)
  465. self.errors[host] = e.message
  466. RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
  467. task_id=self.task_id, status=RecordModel.status_fail, host=host,
  468. user=server_info['user'], command='')
  469. emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
  470. self.end(is_all_servers_success)
  471. except Exception as e:
  472. self.end(False)
  473. return {'success': self.success, 'errors': self.errors}
  474. def walle_rollback(self):
  475. self.start()
  476. try:
  477. is_all_servers_success = True
  478. self.release_version = self.taskMdl.get('link_id')
  479. for server_info in self.servers:
  480. host = server_info['host']
  481. try:
  482. waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
  483. waller.init_env(env=self.custom_global_env)
  484. self.connections[host] = waller
  485. self.prev_release_custom(self.connections[host])
  486. self.release(self.connections[host])
  487. self.post_release(self.connections[host])
  488. RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
  489. task_id=self.task_id, status=RecordModel.status_success, host=host,
  490. user=server_info['user'], command='')
  491. emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
  492. except Exception as e:
  493. is_all_servers_success = False
  494. current_app.logger.error(e)
  495. self.errors[host] = e.message
  496. RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
  497. task_id=self.task_id, status=RecordModel.status_fail, host=host,
  498. user=server_info['user'], command='')
  499. emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
  500. self.end(is_all_servers_success)
  501. except Exception as e:
  502. self.end(False)
  503. return {'success': self.success, 'errors': self.errors}