Authored by 沈志敏

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

/**
* 分发部署
*
* @class Restart
* @author shenzm<zhimin.shen@yoho.cn>
* @date 2016/10/12
*/
import ssh from 'ssh2';
import path from 'path';
import ws from '../../lib/ws';
import {
RestartInfo,
Server
} from '../models';
class DeleteRestart {
constructor(project) {
this.project = project;
}
async deleteRestart(info) {
let server = await Server.findByHost(info.host);
this.server = server;
this.info = info;
this.sshDeleteRestart({
host: server.host,
username: server.username,
password: server.password,
port: server.port
});
}
sshDeleteRestart(serverInfo) {
console.log('ssh connecting', serverInfo);
let conn = new ssh.Client();
let self = this;
conn.on('ready', async() => {
console.log(`connected ${serverInfo.host}`);
try {
await self._delete(conn);
await self._restart(conn);
conn.end();
} catch (e) {
self._state('fail');
self._log(e);
}
}).on('error', (err) => {
self._state('fail');
self._log(err);
}).connect(serverInfo);
}
_delete(conn) {
let self = this;
let script = `pm2 delete ${self.project.name}`;
return new Promise((resolve, reject) => {
self._state('deleteing');
self._log(`>>>> ${script}`);
conn.exec(`cd ${self.remoteRunningDir} && ${script}`, (err, stream) => {
if (err) {
reject(err);
} else {
stream.stdout.on('data', (data) => {
self._log(data.toString());
});
stream.stderr.on('data', (data) => {
self._log(data.toString());
});
stream.on('exit', (code) => {
if (code === 0) {
self._state('deleted');
resolve();
} else {
reject('delete fail: ' + script);
}
});
}
});
});
}
_restart(conn) {
let self = this;
let startup = this.project.scripts.start;
return new Promise((resolve, reject) => {
self._state('restarting');
self._log(`>>>> ${startup}`);
conn.exec(`cd ${self.remoteRunningDir} && ${startup}`, (err, stream) => {
if (err) {
reject(err);
} else {
stream.stdout.on('data', (data) => {
self._log(data.toString());
});
stream.stderr.on('data', (data) => {
self._log(data.toString());
});
stream.on('exit', (code) => {
if (code === 0) {
self._state('running');
resolve();
} else {
reject('restart fail');
}
});
}
});
});
}
async _state(state) {
ws.broadcast(`/restart/${this.project._id}`, {
host: this.info.host,
state: state
});
await RestartInfo.updateState(this.info._id, state);
}
_log(msg) {
console.log("log: ", msg);
ws.broadcast(`/restart/${this.project._id}/log`, {
host: this.info.host,
msg: msg
});
}
get remoteRunningDir() {
return path.join(this.server.deployDir, this.project.name, 'current', this.project.name);
}
}
export default DeleteRestart;
\ No newline at end of file
... ...
'use strict';
import Model from './model';
class DeleteRestart extends Model {
constructor() {
super('delete_restart');
}
async updateState(id, state) {
await this.update({
_id: id
}, {
$set: {
state: state
}
});
}
}
export default DeleteRestart;
\ No newline at end of file
... ...
... ... @@ -6,6 +6,7 @@ import BuildingModel from './building';
import ProjectModel from './project';
import DeployModel from './deploy';
import RestartModel from './restart';
import DeleteRestartModel from './delete_restart';
import UserModel from './user';
import HotfixModel from './hotfix';
import OperationLoggerModel from './operation_logger';
... ... @@ -19,6 +20,7 @@ const Building = new BuildingModel();
const Project = new ProjectModel();
const DeployInfo = new DeployModel();
const RestartInfo = new RestartModel();
const DeleteRestartInfo = new DeleteRestartModel();
const User = new UserModel();
const Hotfix = new HotfixModel();
const OperationLogger = new OperationLoggerModel();
... ... @@ -38,5 +40,6 @@ export {
OperationLogger,
PageCache,
MemcachedHost,
RestartInfo
RestartInfo,
DeleteRestartInfo
};
\ No newline at end of file
... ...
... ... @@ -6,6 +6,7 @@ import Rp from 'request-promise';
import Build from '../../ci/build';
import Deploy from '../../ci/deploy';
import Restart from '../../ci/restart';
import DeleteRestart from '../../ci/delete_restart';
import Operation from '../../logger/operation';
import ws from '../../../lib/ws';
... ... @@ -14,7 +15,8 @@ import {
Project,
Server,
DeployInfo,
RestartInfo
RestartInfo,
DeleteRestartInfo
} from '../../models';
let r = new Router();
... ... @@ -319,6 +321,67 @@ const p = {
code: 200
};
},
project_deleterestart: async(ctx) => {
const projectId = ctx.request.body.id;
const host = ctx.request.body.host;
const env = ctx.request.body.env;
const project = await Project.findById(projectId);
if (!project) {
ctx.body = {
code: 201,
msg: '该项目不存在'
};
return;
}
let hosts = [];
if (host === 'all') {
// 全部重启
hosts = project.deploy[env].target;
} else {
// 单台重启
hosts.push(host);
}
hosts.forEach(async(host) => {
let doc = await DeployInfo.findOne({
projectId: projectId,
host: host,
env: env
});
if (!doc) {
return;
}
let info = {
projectId: projectId,
host: host,
env: env,
createdAt: new Date(),
updatedAt: new Date(),
state: 'waiting'
};
let delrestartDoc = await DeleteRestartInfo.insert(info);
let deleteRestart = new DeleteRestart(project);
info._id = delrestartDoc[0]._id;
deleteRestart.deleteRestart(info);
await Operation.action(ctx.session.user, 'PROJECT_DELETERESTART', '项目删除重启', {
_id: info._id,
project: project.name,
branch: project.deploy[env].branchName,
env: env
});
});
ctx.body = {
code: 200
};
},
project_monit: async(ctx) => {
const projectId = ctx.request.body.id;
const env = ctx.request.body.env;
... ... @@ -335,7 +398,7 @@ const p = {
hosts.forEach((host) => {
var obj = {
'total': 0,
'status': {}
'status': {},
};
Rp({
... ... @@ -344,6 +407,11 @@ const p = {
}).then(function(data) {
var processes = data.processes || [];
processes.forEach(function(p) {
if (p.name === 'pm2-server-monit') {
obj.cpuUsg = p.pm2_env.axm_monitor['CPU usage'].value;
obj.freeMen = p.pm2_env.axm_monitor['Free memory'].value;
}
if (p.name === project.name) {
obj.total++;
... ... @@ -379,5 +447,6 @@ r.post('/save', p.save);
r.post('/build/:pid', p.project_build);
r.post('/deploy/:building', p.project_deploy);
r.post('/restart', p.project_restart);
r.post('/deleterestart', p.project_deleterestart);
r.post('/monit', p.project_monit);
export default r;
\ No newline at end of file
... ...
... ... @@ -49,6 +49,7 @@
<h4>服务器信息</h4>
<p>点击状态Label,可以查看实时日志</p>
<button class="btn btn-info btn-xs restart-btn" data-host='all'>全部重启</button>
<button class="btn btn-warning btn-xs deleterestart-btn" data-host='all'>全部删除重启</button>
</div>
<div class="panel-body">
<div class="row">
... ... @@ -65,17 +66,22 @@
未知部署{{/if}}</code></h5>
</div><!-- media-body -->
<hr class="mt10 mb10">
<div class="clearfix mt5">
<div class="clearfix mt5" style="padding-left:10px;">
<button class="btn btn-info btn-xs restart-btn" data-host='{{host}}'>重启</button>
<button class="btn btn-warning btn-xs deleterestart-btn" data-host='{{host}}'>删除重启</button>
</div>
<div class="clearfix mt5" style="padding-left:10px;">
<h5 class="md-title mt10">服务器状态</h5>
<span class="label label-serstatus"></span>
</div>
<div class="clearfix mt5">
<div class="col-xs-5 project-env" data-env="test">
<div class="col-xs-3 project-env" data-env="test">
<h5 class="md-title mt10">当前状态</h5>
<span class="label label-success deploy-log-btn" data-host="{{host}}"><i
class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}}
未知部署{{/if}}</b></span>
</div>
<div class="col-xs-7">
<div class="col-xs-3">
<h5 class="md-title mt10">进程状态</h5>
<span class="label label-status"></span>
</div>
... ... @@ -197,7 +203,29 @@
env: env
},function(ret) {
if (ret.code == 200) {
layer.msg('正在重起中');
layer.msg('正在重启中');
} else {
layer.msg(ret.msg, {icon: 5});
}
});
});
});
$('.deleterestart-btn').click(function(){
var id = $('#info').data('id');
var env = $('#info').data('env');
var host = $(this).data('host');
layer.confirm('确定删除后重启吗?', {
btn: ['确定', '取消']
}, function() {
$.post('/projects/deleterestart', {
id: id,
host: host,
env: env
},function(ret) {
if (ret.code == 200) {
layer.msg('正在删除重启中');
} else {
layer.msg(ret.msg, {icon: 5});
}
... ... @@ -258,17 +286,24 @@
$('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state);
});
ws.on('/deleterestart/{{project._id}}', function(data) {
console.log(data);
$('#d-' + data.host.replace(/\./g, '-')).find('b').text(data.state);
});
ws.on('/monit/{{project._id}}', function(data) {
console.log(data);
var label = $('#d-' + data.host.replace(/\./g, '-')).find('.label-status');
var servers = $('#d-' + data.host.replace(/\./g, '-')).find('.label-serstatus');
data = data.monitdata;
if (data.errmsg) {
label.text(data.errmsg).addClass('label-danger');
label.text(data.errmsg).removeClass('label-success').addClass('label-danger');
servers.text("获取失败").removeClass('label-success').addClass('label-danger');
} else {
var msg = "线程:" + data.total
var msg = "线程:" + data.total
for(var s in data.status) {
msg += '; [' + s + ']状态数:' + data.status[s];
msg += '; [' + s + ']:' + data.status[s];
}
label.text(msg);
... ... @@ -277,6 +312,11 @@
} else {
label.removeClass('label-danger').addClass('label-success');
}
msg = "";
if (data.cpuUsg) msg += "CPU:" + data.cpuUsg + ";";
if (data.freeMen) msg += "可用内存:" + data.freeMen;
servers.text(msg).removeClass('label-danger').addClass('label-success');
}
});
... ...