Authored by 沈志敏

增加删除重启,及服务器状态

  1 +/**
  2 + * 分发部署
  3 + *
  4 + * @class Restart
  5 + * @author shenzm<zhimin.shen@yoho.cn>
  6 + * @date 2016/10/12
  7 + */
  8 +
  9 +import ssh from 'ssh2';
  10 +import path from 'path';
  11 +
  12 +import ws from '../../lib/ws';
  13 +import {
  14 + RestartInfo,
  15 + Server
  16 +} from '../models';
  17 +
  18 +class DeleteRestart {
  19 +
  20 + constructor(project) {
  21 + this.project = project;
  22 + }
  23 +
  24 + async deleteRestart(info) {
  25 + let server = await Server.findByHost(info.host);
  26 + this.server = server;
  27 + this.info = info;
  28 + this.sshDeleteRestart({
  29 + host: server.host,
  30 + username: server.username,
  31 + password: server.password,
  32 + port: server.port
  33 + });
  34 + }
  35 +
  36 + sshDeleteRestart(serverInfo) {
  37 + console.log('ssh connecting', serverInfo);
  38 + let conn = new ssh.Client();
  39 + let self = this;
  40 + conn.on('ready', async() => {
  41 + console.log(`connected ${serverInfo.host}`);
  42 + try {
  43 + await self._delete(conn);
  44 + await self._restart(conn);
  45 + conn.end();
  46 + } catch (e) {
  47 + self._state('fail');
  48 + self._log(e);
  49 + }
  50 + }).on('error', (err) => {
  51 + self._state('fail');
  52 + self._log(err);
  53 + }).connect(serverInfo);
  54 + }
  55 +
  56 + _delete(conn) {
  57 + let self = this;
  58 + let script = `pm2 delete ${self.project.name}`;
  59 + return new Promise((resolve, reject) => {
  60 + self._state('deleteing');
  61 + self._log(`>>>> ${script}`);
  62 + conn.exec(`cd ${self.remoteRunningDir} && ${script}`, (err, stream) => {
  63 + if (err) {
  64 + reject(err);
  65 + } else {
  66 + stream.stdout.on('data', (data) => {
  67 + self._log(data.toString());
  68 + });
  69 + stream.stderr.on('data', (data) => {
  70 + self._log(data.toString());
  71 + });
  72 + stream.on('exit', (code) => {
  73 + if (code === 0) {
  74 + self._state('deleted');
  75 + resolve();
  76 + } else {
  77 + reject('delete fail: ' + script);
  78 + }
  79 + });
  80 + }
  81 + });
  82 + });
  83 + }
  84 +
  85 + _restart(conn) {
  86 + let self = this;
  87 + let startup = this.project.scripts.start;
  88 + return new Promise((resolve, reject) => {
  89 + self._state('restarting');
  90 + self._log(`>>>> ${startup}`);
  91 + conn.exec(`cd ${self.remoteRunningDir} && ${startup}`, (err, stream) => {
  92 + if (err) {
  93 + reject(err);
  94 + } else {
  95 + stream.stdout.on('data', (data) => {
  96 + self._log(data.toString());
  97 + });
  98 + stream.stderr.on('data', (data) => {
  99 + self._log(data.toString());
  100 + });
  101 + stream.on('exit', (code) => {
  102 + if (code === 0) {
  103 + self._state('running');
  104 + resolve();
  105 + } else {
  106 + reject('restart fail');
  107 + }
  108 + });
  109 + }
  110 + });
  111 + });
  112 + }
  113 +
  114 + async _state(state) {
  115 + ws.broadcast(`/restart/${this.project._id}`, {
  116 + host: this.info.host,
  117 + state: state
  118 + });
  119 + await RestartInfo.updateState(this.info._id, state);
  120 + }
  121 +
  122 + _log(msg) {
  123 + console.log("log: ", msg);
  124 + ws.broadcast(`/restart/${this.project._id}/log`, {
  125 + host: this.info.host,
  126 + msg: msg
  127 + });
  128 + }
  129 +
  130 + get remoteRunningDir() {
  131 + return path.join(this.server.deployDir, this.project.name, 'current', this.project.name);
  132 + }
  133 +}
  134 +
  135 +export default DeleteRestart;
  1 +'use strict';
  2 +
  3 +import Model from './model';
  4 +
  5 +class DeleteRestart extends Model {
  6 +
  7 + constructor() {
  8 + super('delete_restart');
  9 + }
  10 +
  11 + async updateState(id, state) {
  12 + await this.update({
  13 + _id: id
  14 + }, {
  15 + $set: {
  16 + state: state
  17 + }
  18 + });
  19 + }
  20 +}
  21 +
  22 +export default DeleteRestart;
@@ -6,6 +6,7 @@ import BuildingModel from './building'; @@ -6,6 +6,7 @@ import BuildingModel from './building';
6 import ProjectModel from './project'; 6 import ProjectModel from './project';
7 import DeployModel from './deploy'; 7 import DeployModel from './deploy';
8 import RestartModel from './restart'; 8 import RestartModel from './restart';
  9 +import DeleteRestartModel from './delete_restart';
9 import UserModel from './user'; 10 import UserModel from './user';
10 import HotfixModel from './hotfix'; 11 import HotfixModel from './hotfix';
11 import OperationLoggerModel from './operation_logger'; 12 import OperationLoggerModel from './operation_logger';
@@ -19,6 +20,7 @@ const Building = new BuildingModel(); @@ -19,6 +20,7 @@ const Building = new BuildingModel();
19 const Project = new ProjectModel(); 20 const Project = new ProjectModel();
20 const DeployInfo = new DeployModel(); 21 const DeployInfo = new DeployModel();
21 const RestartInfo = new RestartModel(); 22 const RestartInfo = new RestartModel();
  23 +const DeleteRestartInfo = new DeleteRestartModel();
22 const User = new UserModel(); 24 const User = new UserModel();
23 const Hotfix = new HotfixModel(); 25 const Hotfix = new HotfixModel();
24 const OperationLogger = new OperationLoggerModel(); 26 const OperationLogger = new OperationLoggerModel();
@@ -38,5 +40,6 @@ export { @@ -38,5 +40,6 @@ export {
38 OperationLogger, 40 OperationLogger,
39 PageCache, 41 PageCache,
40 MemcachedHost, 42 MemcachedHost,
41 - RestartInfo 43 + RestartInfo,
  44 + DeleteRestartInfo
42 }; 45 };
@@ -6,6 +6,7 @@ import Rp from 'request-promise'; @@ -6,6 +6,7 @@ import Rp from 'request-promise';
6 import Build from '../../ci/build'; 6 import Build from '../../ci/build';
7 import Deploy from '../../ci/deploy'; 7 import Deploy from '../../ci/deploy';
8 import Restart from '../../ci/restart'; 8 import Restart from '../../ci/restart';
  9 +import DeleteRestart from '../../ci/delete_restart';
9 import Operation from '../../logger/operation'; 10 import Operation from '../../logger/operation';
10 import ws from '../../../lib/ws'; 11 import ws from '../../../lib/ws';
11 12
@@ -14,7 +15,8 @@ import { @@ -14,7 +15,8 @@ import {
14 Project, 15 Project,
15 Server, 16 Server,
16 DeployInfo, 17 DeployInfo,
17 - RestartInfo 18 + RestartInfo,
  19 + DeleteRestartInfo
18 } from '../../models'; 20 } from '../../models';
19 21
20 let r = new Router(); 22 let r = new Router();
@@ -319,6 +321,67 @@ const p = { @@ -319,6 +321,67 @@ const p = {
319 code: 200 321 code: 200
320 }; 322 };
321 }, 323 },
  324 + project_deleterestart: async(ctx) => {
  325 + const projectId = ctx.request.body.id;
  326 + const host = ctx.request.body.host;
  327 + const env = ctx.request.body.env;
  328 + const project = await Project.findById(projectId);
  329 +
  330 + if (!project) {
  331 + ctx.body = {
  332 + code: 201,
  333 + msg: '该项目不存在'
  334 + };
  335 + return;
  336 + }
  337 +
  338 + let hosts = [];
  339 + if (host === 'all') {
  340 + // 全部重启
  341 + hosts = project.deploy[env].target;
  342 + } else {
  343 + // 单台重启
  344 + hosts.push(host);
  345 + }
  346 +
  347 + hosts.forEach(async(host) => {
  348 + let doc = await DeployInfo.findOne({
  349 + projectId: projectId,
  350 + host: host,
  351 + env: env
  352 + });
  353 +
  354 + if (!doc) {
  355 + return;
  356 + }
  357 +
  358 + let info = {
  359 + projectId: projectId,
  360 + host: host,
  361 + env: env,
  362 + createdAt: new Date(),
  363 + updatedAt: new Date(),
  364 + state: 'waiting'
  365 + };
  366 +
  367 + let delrestartDoc = await DeleteRestartInfo.insert(info);
  368 + let deleteRestart = new DeleteRestart(project);
  369 +
  370 + info._id = delrestartDoc[0]._id;
  371 + deleteRestart.deleteRestart(info);
  372 +
  373 + await Operation.action(ctx.session.user, 'PROJECT_DELETERESTART', '项目删除重启', {
  374 + _id: info._id,
  375 + project: project.name,
  376 + branch: project.deploy[env].branchName,
  377 + env: env
  378 + });
  379 + });
  380 +
  381 + ctx.body = {
  382 + code: 200
  383 + };
  384 + },
322 project_monit: async(ctx) => { 385 project_monit: async(ctx) => {
323 const projectId = ctx.request.body.id; 386 const projectId = ctx.request.body.id;
324 const env = ctx.request.body.env; 387 const env = ctx.request.body.env;
@@ -335,7 +398,7 @@ const p = { @@ -335,7 +398,7 @@ const p = {
335 hosts.forEach((host) => { 398 hosts.forEach((host) => {
336 var obj = { 399 var obj = {
337 'total': 0, 400 'total': 0,
338 - 'status': {} 401 + 'status': {},
339 }; 402 };
340 403
341 Rp({ 404 Rp({
@@ -344,6 +407,11 @@ const p = { @@ -344,6 +407,11 @@ const p = {
344 }).then(function(data) { 407 }).then(function(data) {
345 var processes = data.processes || []; 408 var processes = data.processes || [];
346 processes.forEach(function(p) { 409 processes.forEach(function(p) {
  410 + if (p.name === 'pm2-server-monit') {
  411 + obj.cpuUsg = p.pm2_env.axm_monitor['CPU usage'].value;
  412 + obj.freeMen = p.pm2_env.axm_monitor['Free memory'].value;
  413 + }
  414 +
347 if (p.name === project.name) { 415 if (p.name === project.name) {
348 obj.total++; 416 obj.total++;
349 417
@@ -379,5 +447,6 @@ r.post('/save', p.save); @@ -379,5 +447,6 @@ r.post('/save', p.save);
379 r.post('/build/:pid', p.project_build); 447 r.post('/build/:pid', p.project_build);
380 r.post('/deploy/:building', p.project_deploy); 448 r.post('/deploy/:building', p.project_deploy);
381 r.post('/restart', p.project_restart); 449 r.post('/restart', p.project_restart);
  450 +r.post('/deleterestart', p.project_deleterestart);
382 r.post('/monit', p.project_monit); 451 r.post('/monit', p.project_monit);
383 export default r; 452 export default r;
@@ -49,6 +49,7 @@ @@ -49,6 +49,7 @@
49 <h4>服务器信息</h4> 49 <h4>服务器信息</h4>
50 <p>点击状态Label,可以查看实时日志</p> 50 <p>点击状态Label,可以查看实时日志</p>
51 <button class="btn btn-info btn-xs restart-btn" data-host='all'>全部重启</button> 51 <button class="btn btn-info btn-xs restart-btn" data-host='all'>全部重启</button>
  52 + <button class="btn btn-warning btn-xs deleterestart-btn" data-host='all'>全部删除重启</button>
52 </div> 53 </div>
53 <div class="panel-body"> 54 <div class="panel-body">
54 <div class="row"> 55 <div class="row">
@@ -65,17 +66,22 @@ @@ -65,17 +66,22 @@
65 未知部署{{/if}}</code></h5> 66 未知部署{{/if}}</code></h5>
66 </div><!-- media-body --> 67 </div><!-- media-body -->
67 <hr class="mt10 mb10"> 68 <hr class="mt10 mb10">
68 - <div class="clearfix mt5"> 69 + <div class="clearfix mt5" style="padding-left:10px;">
69 <button class="btn btn-info btn-xs restart-btn" data-host='{{host}}'>重启</button> 70 <button class="btn btn-info btn-xs restart-btn" data-host='{{host}}'>重启</button>
  71 + <button class="btn btn-warning btn-xs deleterestart-btn" data-host='{{host}}'>删除重启</button>
  72 + </div>
  73 + <div class="clearfix mt5" style="padding-left:10px;">
  74 + <h5 class="md-title mt10">服务器状态</h5>
  75 + <span class="label label-serstatus"></span>
70 </div> 76 </div>
71 <div class="clearfix mt5"> 77 <div class="clearfix mt5">
72 - <div class="col-xs-5 project-env" data-env="test"> 78 + <div class="col-xs-3 project-env" data-env="test">
73 <h5 class="md-title mt10">当前状态</h5> 79 <h5 class="md-title mt10">当前状态</h5>
74 <span class="label label-success deploy-log-btn" data-host="{{host}}"><i 80 <span class="label label-success deploy-log-btn" data-host="{{host}}"><i
75 class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}} 81 class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}}
76 未知部署{{/if}}</b></span> 82 未知部署{{/if}}</b></span>
77 </div> 83 </div>
78 - <div class="col-xs-7"> 84 + <div class="col-xs-3">
79 <h5 class="md-title mt10">进程状态</h5> 85 <h5 class="md-title mt10">进程状态</h5>
80 <span class="label label-status"></span> 86 <span class="label label-status"></span>
81 </div> 87 </div>
@@ -197,7 +203,29 @@ @@ -197,7 +203,29 @@
197 env: env 203 env: env
198 },function(ret) { 204 },function(ret) {
199 if (ret.code == 200) { 205 if (ret.code == 200) {
200 - layer.msg('正在重起中'); 206 + layer.msg('正在重启中');
  207 + } else {
  208 + layer.msg(ret.msg, {icon: 5});
  209 + }
  210 + });
  211 + });
  212 + });
  213 +
  214 + $('.deleterestart-btn').click(function(){
  215 + var id = $('#info').data('id');
  216 + var env = $('#info').data('env');
  217 + var host = $(this).data('host');
  218 +
  219 + layer.confirm('确定删除后重启吗?', {
  220 + btn: ['确定', '取消']
  221 + }, function() {
  222 + $.post('/projects/deleterestart', {
  223 + id: id,
  224 + host: host,
  225 + env: env
  226 + },function(ret) {
  227 + if (ret.code == 200) {
  228 + layer.msg('正在删除重启中');
201 } else { 229 } else {
202 layer.msg(ret.msg, {icon: 5}); 230 layer.msg(ret.msg, {icon: 5});
203 } 231 }
@@ -258,17 +286,24 @@ @@ -258,17 +286,24 @@
258 $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state); 286 $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state);
259 }); 287 });
260 288
  289 + ws.on('/deleterestart/{{project._id}}', function(data) {
  290 + console.log(data);
  291 + $('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state);
  292 + });
  293 +
261 ws.on('/monit/{{project._id}}', function(data) { 294 ws.on('/monit/{{project._id}}', function(data) {
262 console.log(data); 295 console.log(data);
263 var label = $('#d-' + data.host.replace(/\./g, '-')).find('.label-status'); 296 var label = $('#d-' + data.host.replace(/\./g, '-')).find('.label-status');
  297 + var servers = $('#d-' + data.host.replace(/\./g, '-')).find('.label-serstatus');
264 298
265 data = data.monitdata; 299 data = data.monitdata;
266 if (data.errmsg) { 300 if (data.errmsg) {
267 - label.text(data.errmsg).addClass('label-danger'); 301 + label.text(data.errmsg).removeClass('label-success').addClass('label-danger');
  302 + servers.text("获取失败").removeClass('label-success').addClass('label-danger');
268 } else { 303 } else {
269 - var msg = "线程:" + data.total 304 + var msg = "线程:" + data.total
270 for(var s in data.status) { 305 for(var s in data.status) {
271 - msg += '; [' + s + ']状态数:' + data.status[s]; 306 + msg += '; [' + s + ']:' + data.status[s];
272 } 307 }
273 label.text(msg); 308 label.text(msg);
274 309
@@ -277,6 +312,11 @@ @@ -277,6 +312,11 @@
277 } else { 312 } else {
278 label.removeClass('label-danger').addClass('label-success'); 313 label.removeClass('label-danger').addClass('label-success');
279 } 314 }
  315 +
  316 + msg = "";
  317 + if (data.cpuUsg) msg += "CPU:" + data.cpuUsg + ";";
  318 + if (data.freeMen) msg += "可用内存:" + data.freeMen;
  319 + servers.text(msg).removeClass('label-danger').addClass('label-success');
280 } 320 }
281 }); 321 });
282 322