Showing
5 changed files
with
279 additions
and
10 deletions
apps/ci/delete_restart.js
0 → 100644
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; |
apps/models/delete_restart.js
0 → 100644
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 |
-
Please register or login to post a comment