├── webui ├── webapp │ ├── static │ │ ├── .gitkeep │ │ ├── favicon.ico │ │ ├── favicon.ico_1 │ │ ├── favicon_2.ico │ │ ├── rockman5.png │ │ ├── rockman6.png │ │ ├── rockman7.png │ │ ├── rockman5@2x.png │ │ ├── rockman6@2x.png │ │ ├── rockman6@3x.png │ │ ├── rockman7@2x.png │ │ ├── rockman7@3x.png │ │ ├── rockman7.svg │ │ ├── rockman6.svg │ │ └── rockman5.svg │ ├── README.md │ ├── src │ │ ├── assets │ │ │ ├── styles │ │ │ │ ├── page.scss │ │ │ │ ├── main.scss │ │ │ │ ├── mixin.scss │ │ │ │ └── common.scss │ │ │ ├── script │ │ │ │ ├── common.js │ │ │ │ └── extend.js │ │ │ ├── logo.png │ │ │ ├── img │ │ │ │ └── bg.jpg │ │ │ ├── imgs │ │ │ │ ├── log.png │ │ │ │ ├── error.png │ │ │ │ ├── h5 │ │ │ │ │ ├── 01.jpg │ │ │ │ │ ├── 02.jpg │ │ │ │ │ ├── 03.jpg │ │ │ │ │ ├── 04.jpg │ │ │ │ │ ├── 05.jpg │ │ │ │ │ ├── 06.jpg │ │ │ │ │ └── 07.jpg │ │ │ │ ├── favicon.ico │ │ │ │ └── error-img.png │ │ │ ├── logo_1.png │ │ │ ├── logo_2.png │ │ │ ├── logo_3.png │ │ │ ├── rockman3.png │ │ │ ├── logo │ │ │ │ ├── rockman1.png │ │ │ │ ├── rockman3.png │ │ │ │ ├── rockman1@2x.png │ │ │ │ ├── rockman1@3x.png │ │ │ │ ├── rockman3@2x.png │ │ │ │ └── rockman3@3x.png │ │ │ ├── rockman1@2x.png │ │ │ ├── rockman1@3x.png │ │ │ ├── rockman3@2x.png │ │ │ ├── rockman3@3x.png │ │ │ ├── iconfont │ │ │ │ ├── iconfont.eot │ │ │ │ ├── iconfont.ttf │ │ │ │ ├── iconfont.woff │ │ │ │ ├── iconfont.woff2 │ │ │ │ └── iconfont.css │ │ │ └── css │ │ │ │ ├── common.less │ │ │ │ └── ViewContainer.less │ │ ├── views │ │ │ ├── home.vue │ │ │ ├── node │ │ │ │ ├── index.vue │ │ │ │ └── tableFormsViewGrid.js │ │ │ ├── login_1.vue │ │ │ ├── main │ │ │ │ ├── main.less │ │ │ │ └── index.vue │ │ │ └── task │ │ │ │ ├── components │ │ │ │ ├── stateLogs.vue │ │ │ │ ├── submitLogs.vue │ │ │ │ ├── glue.vue │ │ │ │ └── execLogs.vue │ │ │ │ └── logdetail.vue │ │ ├── api │ │ │ ├── cluster.js │ │ │ ├── login.js │ │ │ ├── api.js │ │ │ ├── logs.js │ │ │ ├── node.js │ │ │ ├── task.js │ │ │ └── mock.js │ │ ├── store │ │ │ ├── store.js │ │ │ └── modules │ │ │ │ ├── app.js │ │ │ │ └── user.js │ │ ├── app.vue │ │ ├── common │ │ │ ├── global.js │ │ │ ├── tableminix.js │ │ │ ├── http.js │ │ │ └── utils.js │ │ ├── router │ │ │ └── index.js │ │ └── main.js │ ├── .eslintignore │ ├── config │ │ ├── prod.env.js │ │ ├── dev.env.js │ │ └── index.js │ ├── .editorconfig │ ├── .gitignore │ ├── .postcssrc.js │ ├── index.html │ ├── .babelrc │ ├── build │ │ ├── vue-loader.conf.js │ │ ├── build.js │ │ ├── check-versions.js │ │ ├── webpack.base.conf.js │ │ ├── utils.js │ │ └── webpack.dev.conf.js │ ├── .eslintrc.js │ └── package.json ├── const │ └── const.go ├── validate │ └── validate.go ├── contract │ ├── QueryPageRequest.go │ └── Response.go ├── controllers │ ├── UserController.go │ ├── NodeController.go │ ├── Controller.go │ ├── ClusterController.go │ └── LogController.go └── server.go ├── runtime ├── runtime_test.go └── executor │ ├── test │ └── plugin │ │ └── hello.go │ ├── executor.go │ ├── goexec.go │ ├── httpexec.go │ └── shellexec.go ├── util ├── file │ ├── path_test.go │ ├── file.go │ ├── file_test.go │ └── path.go ├── netx │ └── netx.go ├── sysx │ ├── sysx_test.go │ └── sysx.go ├── exception │ └── exception.go ├── http │ ├── http_test.go │ └── http.go ├── json │ ├── json.go │ └── json_test.go └── uuid │ └── uuid_test.go ├── core ├── pageinfo.go ├── exception.go ├── const.go ├── cluster.go ├── executor.go ├── task.go ├── result.go ├── node.go └── resource.go ├── protected ├── model │ ├── LoginUser.go │ ├── PageInfo.go │ ├── LogInfo.go │ └── ExecutorInfo.go ├── service │ ├── BaseService.go │ └── LogService.go └── repository │ └── BaseRepository.go ├── tools ├── run.sh └── catool.sh ├── README.md ├── config ├── config_test.go └── config.go ├── docs └── list.md ├── rpc ├── handler │ ├── handler_test.go │ ├── handler.go │ └── handler_worker.go ├── packet │ └── reply.go ├── client │ └── client_master.go └── server.go ├── registry ├── consul │ ├── locker.go │ ├── consul_test.go │ └── consul.go └── registry.go ├── .gitignore ├── metrics ├── metrics_test.go ├── prometheus │ ├── metrics_test.go │ └── metrics.go ├── standard │ ├── counter.go │ └── counter_test.go └── metrics.go ├── node ├── node_flag.go └── node_worker.go ├── resources ├── config │ └── develop │ │ ├── app.conf │ │ └── dotlog.conf └── tls │ ├── client.crt │ ├── server.crt │ ├── client.key │ └── server.key ├── logger └── logger.go ├── scheduler └── scheduler.go └── main.go /webui/webapp/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webui/webapp/README.md: -------------------------------------------------------------------------------- 1 | # 任务管理系统 2 | -------------------------------------------------------------------------------- /runtime/runtime_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | -------------------------------------------------------------------------------- /util/file/path_test.go: -------------------------------------------------------------------------------- 1 | package _file 2 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/styles/page.scss: -------------------------------------------------------------------------------- 1 | /*各个页面私有css*/ 2 | -------------------------------------------------------------------------------- /webui/webapp/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /webui/webapp/src/views/home.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/script/common.js: -------------------------------------------------------------------------------- 1 | var test1 = function () { alert(11) } 2 | export { test1 } -------------------------------------------------------------------------------- /core/pageinfo.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type PageInfo struct { 4 | PageIndex int 5 | PageSize int 6 | } 7 | -------------------------------------------------------------------------------- /webui/webapp/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/favicon.ico -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo.png -------------------------------------------------------------------------------- /webui/webapp/static/favicon.ico_1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/favicon.ico_1 -------------------------------------------------------------------------------- /webui/webapp/static/favicon_2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/favicon_2.ico -------------------------------------------------------------------------------- /webui/webapp/static/rockman5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman5.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman6.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman7.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/img/bg.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/log.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo_1.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo_2.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo_3.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/rockman3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/rockman3.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman5@2x.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman6@2x.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman6@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman6@3x.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman7@2x.png -------------------------------------------------------------------------------- /webui/webapp/static/rockman7@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/static/rockman7@3x.png -------------------------------------------------------------------------------- /protected/model/LoginUser.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type LoginUser struct { 4 | UserName string 5 | Token string 6 | } 7 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/error.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/01.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/02.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/03.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/04.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/05.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/06.jpg -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/h5/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/h5/07.jpg -------------------------------------------------------------------------------- /core/exception.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "errors" 4 | 5 | var ErrorRpcClientCreate = errors.New("create rpc client error") 6 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/favicon.ico -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo/rockman1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo/rockman1.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo/rockman3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo/rockman3.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/rockman1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/rockman1@2x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/rockman1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/rockman1@3x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/rockman3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/rockman3@2x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/rockman3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/rockman3@3x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/imgs/error-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/imgs/error-img.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/iconfont/iconfont.eot -------------------------------------------------------------------------------- /webui/webapp/src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /webui/webapp/src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo/rockman1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo/rockman1@2x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo/rockman1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo/rockman1@3x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo/rockman3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo/rockman3@2x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/logo/rockman3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/logo/rockman3@3x.png -------------------------------------------------------------------------------- /webui/webapp/src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfeel/rockman/HEAD/webui/webapp/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /webui/webapp/src/assets/script/extend.js: -------------------------------------------------------------------------------- 1 | //对vue参数进行扩展 2 | var extend = function (param) { 3 | console.log(param) 4 | } 5 | export { extend } -------------------------------------------------------------------------------- /webui/webapp/src/assets/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "./base"; 2 | @import "./mixin"; 3 | @import "./common"; 4 | @import "./page"; 5 | @import "../theme/default/styls"; 6 | -------------------------------------------------------------------------------- /core/const.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | const ClusterKeyPrefix = "devfeel/rockman/" 4 | const DefaultTimeLayout = "2006-01-02 15:04:05" 5 | const DefaultLeaderCheckExecutorInterval = 60 6 | -------------------------------------------------------------------------------- /webui/webapp/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | let HOST = process.argv.splice(2)[0] || 'prod'; 3 | module.exports = { 4 | NODE_ENV: '"production"', 5 | HOST: '"' + HOST + '"' 6 | } 7 | -------------------------------------------------------------------------------- /webui/webapp/src/api/cluster.js: -------------------------------------------------------------------------------- 1 | import {get} from '../common/http.js' 2 | import GLOBAL from '../common/global.js' 3 | 4 | export const getClusterInfo = (data) => get(`${GLOBAL.HOME}/cluster/info`, data) 5 | -------------------------------------------------------------------------------- /tools/run.sh: -------------------------------------------------------------------------------- 1 | master1: nohup ./rockman -outerhost=116.62.16.66 -cluster=rock & 2 | master2: nohup ./rockman -outerhost=118.31.32.168 -cluster=rock & 3 | 4 | plugin: go build -buildmode=plugin -o plugin.so plugin.go -------------------------------------------------------------------------------- /webui/const/const.go: -------------------------------------------------------------------------------- 1 | package _const 2 | 3 | const ItemKeyNode = "rockman.node" 4 | 5 | const ( 6 | DefaultPageSize = 20 7 | ) 8 | 9 | const ( 10 | SuccessCode = 0 11 | ErrorCode = -9999 12 | ) 13 | -------------------------------------------------------------------------------- /webui/webapp/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /webui/webapp/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | HOST: '"dev"' 8 | }) 9 | -------------------------------------------------------------------------------- /util/netx/netx.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | import "net" 4 | 5 | func CheckTcpConnect(endPoint string) bool { 6 | conn, err := net.Dial("tcp", endPoint) 7 | if err == nil { 8 | conn.Close() 9 | } 10 | return err == nil 11 | } 12 | -------------------------------------------------------------------------------- /webui/webapp/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /runtime/executor/test/plugin/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | task "github.com/devfeel/dottask" 6 | ) 7 | 8 | func Exec(ctx *task.TaskContext) error { 9 | fmt.Println("print from plugin") 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rockman 2 | A distributed task scheduling platform. 3 | 4 | * Rockman是一个分布式调度解决方案,由两个相互独立的子项目dottask和Rockman组成 5 | * dottask: 定位为轻量级任务调度框架,使用go包的形式提供定时任务的调度服务 6 | * Rockman: 定位为分布式任务调度服务,是用完整集群提供资源治理、应用分发以及进程隔离等功能 7 | 8 | Public at 2020.04.15 16:15. 9 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/yaml.v2" 6 | "testing" 7 | ) 8 | 9 | func TestGetDefaultProfileYaml(t *testing.T) { 10 | ob, _ := yaml.Marshal(DefaultProfile()) 11 | fmt.Println(fmt.Sprint(string(ob))) 12 | } 13 | -------------------------------------------------------------------------------- /webui/webapp/src/api/login.js: -------------------------------------------------------------------------------- 1 | import {get, post} from '../common/http.js' 2 | import GLOBAL from '../common/global.js' 3 | 4 | export const getUserInfo = (data) => post(`${GLOBAL.HOME}/getUserInfo`, data) 5 | export const login = (data) => get(`${GLOBAL.HOME}/user/login`, data) 6 | -------------------------------------------------------------------------------- /docs/list.md: -------------------------------------------------------------------------------- 1 | Leader\Worker协议: 2 | master: 3 | 1. ElectionLeader 4 | 2. SubmitExecutor 5 | 3. QueryWorkers 6 | 7 | worker: 8 | 1. GetLeaderInfo 9 | 2. RegisterWorker 10 | 3. RegisterExecutor 11 | 4. StartExecutor 12 | 5. StopExecutor 13 | 6. RemoveExecutor 14 | 7. QueryExecutors 15 | -------------------------------------------------------------------------------- /util/sysx/sysx_test.go: -------------------------------------------------------------------------------- 1 | package sysx 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetCpuTimeState(t *testing.T) { 9 | fmt.Println(GetCpuTimeState()) 10 | } 11 | 12 | func TestGetCpuUsedPercent(t *testing.T) { 13 | fmt.Println(GetCpuUsedPercent()) 14 | } 15 | -------------------------------------------------------------------------------- /core/cluster.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type ClusterInfo struct { 4 | ClusterId string 5 | RegistryServerUrl string 6 | LeaderKey string 7 | LeaderServer string 8 | NodeNum int 9 | WatchLeaderRetryLimit int 10 | QueryResourceInterval int 11 | } 12 | -------------------------------------------------------------------------------- /rpc/handler/handler_test.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shirou/gopsutil/mem" 6 | "testing" 7 | ) 8 | 9 | func Test_GetMemory(t *testing.T) { 10 | v, _ := mem.VirtualMemory() 11 | fmt.Printf("UsedPercent:%f\n", v.UsedPercent) 12 | fmt.Println((int)(v.UsedPercent)) 13 | } 14 | -------------------------------------------------------------------------------- /webui/webapp/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin hover{ 2 | cursor: pointer; 3 | opacity: 0.75; 4 | } 5 | @mixin flexCenter{ 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | @mixin bgImg80($src){ 11 | background: url($src) center center /80% no-repeat; 12 | } 13 | -------------------------------------------------------------------------------- /registry/consul/locker.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import "github.com/hashicorp/consul/api" 4 | 5 | type Locker struct { 6 | Locker *api.Lock 7 | } 8 | 9 | func (l *Locker) Lock() (<-chan struct{}, error) { 10 | return l.Locker.Lock(nil) 11 | } 12 | 13 | func (l *Locker) UnLock() error { 14 | return l.Locker.Unlock() 15 | } 16 | -------------------------------------------------------------------------------- /webui/webapp/src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import user from './modules/user' 4 | // import app from './modules/app' 5 | 6 | Vue.use(Vuex); 7 | 8 | export default new Vuex.Store({ 9 | modules: { 10 | user 11 | }, 12 | getters: { 13 | token: state => state.user.token 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | /.idea 17 | /bin 18 | -------------------------------------------------------------------------------- /protected/service/BaseService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/devfeel/cache" 5 | "github.com/devfeel/dotlog" 6 | "github.com/devfeel/rockman/logger" 7 | ) 8 | 9 | type BaseService struct { 10 | RedisCache cache.RedisCache 11 | } 12 | 13 | func GetLogger() dotlog.Logger { 14 | return logger.GetLogger(logger.LoggerName_Service) 15 | } 16 | -------------------------------------------------------------------------------- /webui/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Rockman by Devfeel 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /core/executor.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import _json "github.com/devfeel/rockman/util/json" 4 | 5 | type ExecutorInfo struct { 6 | TaskConfig *TaskConfig 7 | Worker *NodeInfo 8 | } 9 | 10 | func (e *ExecutorInfo) Json() string { 11 | return _json.GetJsonString(e) 12 | } 13 | 14 | func (e *ExecutorInfo) LoadFromJson(json string) error { 15 | return _json.Unmarshal(json, e) 16 | } 17 | -------------------------------------------------------------------------------- /webui/validate/validate.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "errors" 5 | "github.com/devfeel/rockman/webui/contract" 6 | ) 7 | 8 | func IsNilString(val string, errCode int, errMsg string) (*contract.Response, error) { 9 | if val != "" { 10 | return contract.SuccessResponse(nil), nil 11 | } else { 12 | return contract.NewResponse(errCode, errMsg, nil), errors.New("val is nil") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /metrics/metrics_test.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "testing" 4 | 5 | func TestStandardMetrics_GetDefaultCounter(t *testing.T) { 6 | var needVal int64 = 1 7 | m := NewMetrics() 8 | c := m.GetStandardCounter("test") 9 | c.Inc() 10 | c2 := m.GetStandardCounter("test") 11 | if c2.Count() == needVal { 12 | t.Log("success") 13 | } else { 14 | t.Error("val not need", needVal, c2.Count()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webui/webapp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": [ 9 | "> 1%", 10 | "last 2 versions", 11 | "not ie <= 8" 12 | ] 13 | } 14 | } 15 | ], 16 | "stage-2" 17 | ], 18 | "plugins": [ 19 | "transform-vue-jsx", 20 | "transform-runtime" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /webui/webapp/src/api/api.js: -------------------------------------------------------------------------------- 1 | import {get, post} from '../common/http.js' 2 | import GLOBAL from '../common/global.js' 3 | 4 | export const getTableList = (data) => post(`${GLOBAL.HOME}/table/list`, data) 5 | 6 | export const getFirstTree = () => get(`${GLOBAL.HOME}/tree/first`) 7 | 8 | export const getTreeList = (data) => post(`${GLOBAL.HOME}/tree/list`, data) 9 | 10 | export const getTreeOnce = () => get(`${GLOBAL.HOME}/tree/once`) 11 | -------------------------------------------------------------------------------- /webui/webapp/src/api/logs.js: -------------------------------------------------------------------------------- 1 | import { get } from '../common/http.js'; 2 | import GLOBAL from '../common/global.js'; 3 | 4 | export const getNodeTraceList = data => get(`${GLOBAL.HOME}/log/trace`, data); 5 | export const getTaskExecList = data => get(`${GLOBAL.HOME}/log/exec`, data); 6 | export const getTaskStateList = data => get(`${GLOBAL.HOME}/log/state`, data); 7 | export const getTaskSubmitList = data => get(`${GLOBAL.HOME}/log/submit`, data); 8 | -------------------------------------------------------------------------------- /metrics/prometheus/metrics_test.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus/promhttp" 5 | "log" 6 | "net/http" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestNodeStartCounter(t *testing.T) { 12 | DefaultCounter.WithLabelValues("NodeStart").Inc() 13 | http.Handle("/metrics", promhttp.Handler()) 14 | log.Fatal(http.ListenAndServe("0.0.0.0:8081", nil)) 15 | time.Sleep(time.Hour) 16 | } 17 | -------------------------------------------------------------------------------- /node/node_flag.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/devfeel/rockman/core" 5 | "time" 6 | ) 7 | 8 | func (n *Node) setExecutorChangeFlag() error { 9 | _, err := n.Registry.Set(getExecutorChangeFlagKey(n.ClusterId()), time.Now().Format(core.DefaultTimeLayout), nil) 10 | return err 11 | } 12 | 13 | func getExecutorChangeFlagKey(clusterId string) string { 14 | return core.ClusterKeyPrefix + clusterId + "/flags/executor-change" 15 | } 16 | -------------------------------------------------------------------------------- /util/exception/exception.go: -------------------------------------------------------------------------------- 1 | package exception 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime/debug" 7 | ) 8 | 9 | // CatchError is the unified exception handler 10 | func CatchError(title string, err interface{}) (errmsg string) { 11 | errmsg = fmt.Sprintln(err) 12 | stack := string(debug.Stack()) 13 | os.Stdout.Write([]byte(title + " error! => " + errmsg + " => " + stack)) 14 | return title + " error! => " + errmsg + " => " + stack 15 | } 16 | -------------------------------------------------------------------------------- /webui/webapp/src/api/node.js: -------------------------------------------------------------------------------- 1 | import {get, post} from '../common/http.js' 2 | import GLOBAL from '../common/global.js' 3 | 4 | export const getNodeList = (data) => get(`${GLOBAL.HOME}/node/list`, data) 5 | 6 | export const getNodeOnce = (data) => get(`${GLOBAL.HOME}/node/get`, data) 7 | 8 | export const nodeSave = (data) => post(`${GLOBAL.HOME}/node/save`, data) 9 | 10 | export const nodeDelete = (data) => get(`${GLOBAL.HOME}/node/delete`, data) 11 | -------------------------------------------------------------------------------- /webui/webapp/src/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 28 | -------------------------------------------------------------------------------- /tools/catool.sh: -------------------------------------------------------------------------------- 1 | !/bin/bash 2 | # call this script with an email address (valid or not). 3 | # like: 4 | # ./catool.sh pzrr@qq.com 5 | echo "make server cert" 6 | openssl req -new -nodes -x509 -out server.pem -keyout server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=dotweb.cn/emailAddress=$1" 7 | echo "make client cert" 8 | openssl req -new -nodes -x509 -out client.pem -keyout client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=dotweb.cn/emailAddress=$1" 9 | -------------------------------------------------------------------------------- /core/task.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import task "github.com/devfeel/dottask" 4 | 5 | type TaskConfig struct { 6 | TaskID string 7 | TaskType string 8 | IsRun bool 9 | Handler task.TaskHandle `json:"-"` 10 | DueTime int64 11 | Interval int64 12 | Express string 13 | TaskData interface{} 14 | HAFlag bool //HA flag, if set true, leader will watch it, when it offline will resubmit 15 | TargetType string 16 | TargetConfig interface{} 17 | DistributeType int 18 | } 19 | -------------------------------------------------------------------------------- /webui/contract/QueryPageRequest.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import "github.com/devfeel/rockman/protected/model" 4 | 5 | type ExecutorQR struct { 6 | model.PageRequest 7 | NodeID string 8 | } 9 | 10 | type TaskExecLogQR struct { 11 | model.PageRequest 12 | TaskID string 13 | } 14 | 15 | type TaskStateLogQR struct { 16 | model.PageRequest 17 | TaskID string 18 | } 19 | 20 | type TaskSubmitLogQR struct { 21 | model.PageRequest 22 | TaskID string 23 | } 24 | 25 | type NodeTraceLogQR struct { 26 | model.PageRequest 27 | NodeID string 28 | } 29 | -------------------------------------------------------------------------------- /util/http/http_test.go: -------------------------------------------------------------------------------- 1 | package _http 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestHttpGet(t *testing.T) { 9 | url := "http://www.baidu.com" 10 | result := HttpGet(url, 30*time.Second) 11 | if result.Error != nil { 12 | t.Error(result.Error) 13 | } else { 14 | t.Log("HttpGet success") 15 | } 16 | } 17 | 18 | func TestHttpPost(t *testing.T) { 19 | url := "http://www.baidu.com" 20 | result := HttpPost(url, "", "", 30*time.Second) 21 | if result.Error != nil { 22 | t.Error(result.Error) 23 | } else { 24 | t.Log("HttpPost success") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webui/controllers/UserController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/devfeel/dotweb" 5 | "github.com/devfeel/rockman/protected/model" 6 | ) 7 | 8 | type UserController struct { 9 | } 10 | 11 | func (c *UserController) Login(ctx dotweb.Context) error { 12 | userName := ctx.QueryString("UserName") 13 | userPwd := ctx.QueryString("UserPwd") 14 | loginUser := model.LoginUser{} 15 | loginUser.Token = userName + "|" + userPwd 16 | loginUser.UserName = userName 17 | if userName == "root" && userPwd == "root" { 18 | return ctx.WriteJson(SuccessResponse(loginUser)) 19 | } 20 | return ctx.WriteJson(FailedResponse(-1000, "password error!")) 21 | } 22 | -------------------------------------------------------------------------------- /webui/webapp/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /protected/model/PageInfo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "strconv" 4 | 5 | type ( 6 | PageResult struct { 7 | TotalCount int64 8 | PageData interface{} 9 | } 10 | 11 | PageRequest struct { 12 | PageIndex int64 13 | PageSize int64 14 | } 15 | ) 16 | 17 | func (page *PageRequest) GetSkip() int64 { 18 | if page.PageIndex <= 0 { 19 | page.PageIndex = 1 20 | } 21 | return (page.PageIndex - 1) * page.PageSize 22 | } 23 | 24 | func (page *PageRequest) GetLimit() int64 { 25 | return page.PageSize 26 | } 27 | 28 | func (page *PageRequest) GetPageSql() string { 29 | return " limit " + strconv.FormatInt(page.GetSkip(), 10) + "," + strconv.FormatInt(page.GetLimit(), 10) 30 | } 31 | -------------------------------------------------------------------------------- /webui/webapp/src/common/global.js: -------------------------------------------------------------------------------- 1 | // 根据 process.env.HOST 的值判断当前是什么环境,host配置在 config下的dev.env.js和prod.env.js 2 | // 如命令:npm run build -- test ,process.env.HOST就为:'test' 3 | // 如命令:npm run dev ,process.env.HOST就为:'dev' 4 | // 如命令:npm run build ,process.env.HOST就为:'prod' 5 | 6 | const HOST = process.env.HOST; 7 | 8 | const URL_CONFIG = { 9 | 'dev': { 10 | // home: 'http://localhost:8080' 11 | // home: 'http://172.1.1.0/dev' 12 | home: '/api' 13 | }, 14 | 'test': { 15 | home: 'http://172.1.1.0/test' 16 | }, 17 | 'prod': { 18 | home: 'http://localhost:8080/api' 19 | } 20 | } 21 | let {home} = URL_CONFIG[HOST] 22 | 23 | export default { 24 | HOME: home 25 | } 26 | -------------------------------------------------------------------------------- /webui/webapp/src/api/task.js: -------------------------------------------------------------------------------- 1 | import {get, post} from '../common/http.js' 2 | import GLOBAL from '../common/global.js' 3 | 4 | export const getTaskList = (data) => get(`${GLOBAL.HOME}/task/list`, data) 5 | 6 | export const getExecLogList = (data) => get(`${GLOBAL.HOME}/task/execlogs`, data) 7 | 8 | export const getStateLogList = (data) => get(`${GLOBAL.HOME}/task/statelogs`, data) 9 | 10 | export const getTaskOnce = (data) => get(`${GLOBAL.HOME}/task/get`, data) 11 | 12 | export const taskSave = (data) => post(`${GLOBAL.HOME}/task/save`, data) 13 | 14 | export const taskUpdate = (data) => post(`${GLOBAL.HOME}/task/update`, data) 15 | 16 | export const taskDelete = (data) => get(`${GLOBAL.HOME}/task/delete`, data) 17 | -------------------------------------------------------------------------------- /util/json/json.go: -------------------------------------------------------------------------------- 1 | package _json 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // GetJsonString marshals the object as string 8 | func GetJsonString(obj interface{}) string { 9 | resByte, err := json.Marshal(obj) 10 | if err != nil { 11 | return "" 12 | } 13 | return string(resByte) 14 | } 15 | 16 | // Marshal marshals the value as string 17 | func Marshal(v interface{}) (string, error) { 18 | resByte, err := json.Marshal(v) 19 | if err != nil { 20 | return "", err 21 | } else { 22 | return string(resByte), nil 23 | } 24 | } 25 | 26 | // Unmarshal parses the JSON-encoded data and stores the result 27 | func Unmarshal(value string, v interface{}) error { 28 | return json.Unmarshal([]byte(value), v) 29 | } 30 | -------------------------------------------------------------------------------- /webui/contract/Response.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import _const "github.com/devfeel/rockman/webui/const" 4 | 5 | type Response struct { 6 | RetCode int 7 | RetMsg string 8 | Message interface{} 9 | } 10 | 11 | func NewResponse(retCode int, retMsg string, message interface{}) *Response { 12 | return &Response{ 13 | RetCode: retCode, 14 | RetMsg: retMsg, 15 | Message: message, 16 | } 17 | } 18 | 19 | func SuccessResponse(message interface{}) *Response { 20 | return &Response{ 21 | RetCode: _const.SuccessCode, 22 | RetMsg: "", 23 | Message: message, 24 | } 25 | } 26 | 27 | func FailedResponse(retCode int, retMsg string) *Response { 28 | return &Response{ 29 | RetCode: retCode, 30 | RetMsg: retMsg, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /util/sysx/sysx.go: -------------------------------------------------------------------------------- 1 | package sysx 2 | 3 | import ( 4 | "errors" 5 | "github.com/shirou/gopsutil/cpu" 6 | "github.com/shirou/gopsutil/mem" 7 | ) 8 | 9 | func GetCpuUsedPercent() int { 10 | state, err := GetCpuTimeState() 11 | if err != nil { 12 | return 100 13 | } 14 | return (int)(((state.User + state.System) / (state.User + state.System + state.Idle)) * 100) 15 | } 16 | 17 | func GetCpuTimeState() (*cpu.TimesStat, error) { 18 | states, err := cpu.Times(false) 19 | if err != nil { 20 | return nil, err 21 | } 22 | for _, state := range states { 23 | if state.CPU == "cpu-total" { 24 | return &state, nil 25 | } 26 | } 27 | return nil, errors.New("not exists cpu info") 28 | } 29 | 30 | func GetMemoryUsedPercent() int { 31 | v, err := mem.VirtualMemory() 32 | if err != nil { 33 | return 100 34 | } 35 | return (int)(v.UsedPercent) 36 | } 37 | -------------------------------------------------------------------------------- /webui/controllers/NodeController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/devfeel/dotweb" 5 | "github.com/devfeel/rockman/core" 6 | "github.com/devfeel/rockman/node" 7 | _const "github.com/devfeel/rockman/webui/const" 8 | ) 9 | 10 | type NodeController struct { 11 | } 12 | 13 | func (c *NodeController) ShowNodes(ctx dotweb.Context) error { 14 | item, isExists := ctx.AppItems().Get(_const.ItemKeyNode) 15 | if !isExists { 16 | return ctx.WriteJson(NewResponse(-1001, "not exists node in app items", nil)) 17 | } 18 | node, isOk := item.(*node.Node) 19 | if !isOk { 20 | return ctx.WriteJson(NewResponse(-1002, "not exists correct node in app items", nil)) 21 | } 22 | var nodes []*core.NodeInfo 23 | for _, n := range node.Cluster.Nodes { 24 | nodes = append(nodes, n) 25 | } 26 | return ctx.WriteJson(NewResponse(0, "", nodes)) 27 | } 28 | -------------------------------------------------------------------------------- /rpc/packet/reply.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/devfeel/rockman/core" 5 | "strconv" 6 | ) 7 | 8 | type RpcReply struct { 9 | RetCode int 10 | RetMsg string 11 | Message interface{} 12 | } 13 | 14 | func (r *RpcReply) IsSuccess() bool { 15 | return r.RetCode == core.SuccessCode 16 | } 17 | 18 | func (r *RpcReply) FailureMessage() string { 19 | return strconv.Itoa(r.RetCode) + "," + r.RetMsg 20 | } 21 | 22 | func CreateRpcReply(retCode int, retMsg string, message interface{}) RpcReply { 23 | return RpcReply{RetCode: retCode, RetMsg: retMsg, Message: message} 24 | } 25 | 26 | func FailedReply(retCode int, retMsg string) RpcReply { 27 | return RpcReply{RetCode: retCode, RetMsg: retMsg} 28 | } 29 | 30 | func SuccessRpcReply(message interface{}) RpcReply { 31 | return RpcReply{RetCode: core.SuccessCode, RetMsg: "", Message: message} 32 | } 33 | -------------------------------------------------------------------------------- /metrics/prometheus/metrics.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "github.com/devfeel/rockman/logger" 5 | "github.com/prometheus/client_golang/prometheus" 6 | "github.com/prometheus/client_golang/prometheus/promhttp" 7 | "net/http" 8 | ) 9 | 10 | func StartMetricsWeb(addr string) error { 11 | http.Handle("/metrics", promhttp.Handler()) 12 | logger.Default().Debug("MetricsWeb begin listen " + addr) 13 | return http.ListenAndServe(addr, nil) 14 | } 15 | 16 | func InitCounter() *prometheus.CounterVec { 17 | defaultCounter := createCounterVec("Default") 18 | prometheus.MustRegister(defaultCounter) 19 | return defaultCounter 20 | } 21 | 22 | func createCounterVec(name string) *prometheus.CounterVec { 23 | opt := prometheus.CounterOpts{ 24 | Namespace: "Rockman", 25 | Subsystem: "", 26 | Name: name} 27 | labelName := []string{"Label"} 28 | return prometheus.NewCounterVec(opt, labelName) 29 | } 30 | -------------------------------------------------------------------------------- /webui/webapp/static/rockman7.svg: -------------------------------------------------------------------------------- 1 | rockman7 -------------------------------------------------------------------------------- /webui/webapp/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const app = { 2 | state: { 3 | keepAlive: [] 4 | }, 5 | mutations: { 6 | SET_KEEP_ALIVE: (state, data) => { 7 | state.keepAlive = data 8 | } 9 | }, 10 | actions: { 11 | changeKeepAlive: ({commit, state}, name) => { 12 | // 由于vue的文件名首字母大写,导致默认生成的vue文件的name为大写,但是router中的name为小写 13 | // keepAlive中的name要与vue文件中name一致,所以此处做下转换 14 | name = name.replace(name[0], name[0].toUpperCase()); 15 | let keepAlive = [...state.keepAlive] 16 | console.log('name', name, JSON.stringify(state.keepAlive)) 17 | if (name && !keepAlive.includes(name)) { 18 | keepAlive.push(name) 19 | } 20 | console.log('keepAlive', JSON.stringify(keepAlive)) 21 | commit('SET_KEEP_ALIVE', keepAlive) 22 | }, 23 | clearKeepAlive: ({commit, state}) => { 24 | commit('SET_KEEP_ALIVE', []) 25 | } 26 | } 27 | } 28 | export default app 29 | -------------------------------------------------------------------------------- /core/result.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "strconv" 4 | 5 | const ( 6 | SuccessCode = 0 7 | ErrorCode = -9999 8 | ) 9 | 10 | type Result struct { 11 | RetCode int 12 | RetMsg string 13 | Error error 14 | } 15 | 16 | func NewResult(retCode int, retMsg string, err error) *Result { 17 | return &Result{RetCode: retCode, RetMsg: retMsg, Error: err} 18 | } 19 | 20 | func ErrorResult(err error) *Result { 21 | return &Result{RetCode: ErrorCode, RetMsg: err.Error(), Error: err} 22 | } 23 | 24 | func SuccessResult() *Result { 25 | return &Result{RetCode: SuccessCode, RetMsg: "", Error: nil} 26 | } 27 | 28 | func FailedResult(retCode int, retMsg string) *Result { 29 | return &Result{RetCode: retCode, RetMsg: retMsg, Error: nil} 30 | } 31 | 32 | func (r *Result) IsSuccess() bool { 33 | return r.Error == nil && r.RetCode == SuccessCode 34 | } 35 | 36 | func (r *Result) Message() string { 37 | if r.Error != nil { 38 | return r.Error.Error() 39 | } 40 | return strconv.Itoa(r.RetCode) + "," + r.RetMsg 41 | } 42 | -------------------------------------------------------------------------------- /node/node_worker.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/devfeel/rockman/core" 5 | "github.com/devfeel/rockman/logger" 6 | ) 7 | 8 | // RegisterExecutor 9 | func (n *Node) RegisterExecutor(taskInfo *core.TaskConfig) *core.Result { 10 | lt := "Node RegisterExecutor [" + taskInfo.TaskID + "] " 11 | if !n.IsWorker() { 12 | logger.Node().Debug(lt + "failed, current node is not worker.") 13 | return core.FailedResult(-1001, "current node is not worker") 14 | } 15 | _, err := n.Runtime.CreateExecutor(taskInfo) 16 | if err != nil { 17 | logger.Node().Warn(lt + "CreateExecutor error:" + err.Error()) 18 | return core.FailedResult(-2001, err.Error()) 19 | } else { 20 | // update node info 21 | nodeInfo := n.refreshNodeInfo() 22 | // reg to registry server 23 | _, err := n.Registry.Set(nodeInfo.GetNodeKey(n.ClusterId()), nodeInfo.Json(), nil) 24 | if err != nil { 25 | logger.Node().Warn(lt + "Registry Set error:" + err.Error()) 26 | } 27 | 28 | return core.SuccessResult() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /util/json/json_test.go: -------------------------------------------------------------------------------- 1 | package _json 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // 以下是功能测试 8 | 9 | type ( 10 | colorGroup struct { 11 | ID int 12 | Name string 13 | Colors []string 14 | } 15 | mymap map[string]interface{} 16 | ) 17 | 18 | func Test_GetJsonString_1(t *testing.T) { 19 | g := newGroup() 20 | json := GetJsonString(g) 21 | t.Log(json) 22 | } 23 | 24 | func Test_Marshal_1(t *testing.T) { 25 | g := newGroup() 26 | json, err := Marshal(g) 27 | if err != nil { 28 | t.Error(err) 29 | } else { 30 | t.Log(json) 31 | } 32 | 33 | } 34 | 35 | func Test_Unmarshal_1(t *testing.T) { 36 | var group colorGroup 37 | g := newGroup() 38 | json := GetJsonString(g) 39 | err := Unmarshal(json, &group) 40 | if err != nil { 41 | t.Error(err) 42 | } else { 43 | t.Log(group) 44 | } 45 | 46 | } 47 | 48 | func newGroup() colorGroup { 49 | group := colorGroup{ 50 | ID: 1, 51 | Name: "Reds", 52 | Colors: []string{"Crimson", "Red", "Ruby", "Maroon"}, 53 | } 54 | return group 55 | } 56 | -------------------------------------------------------------------------------- /util/file/file.go: -------------------------------------------------------------------------------- 1 | package _file 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | func GetCurrentDirectory() string { 10 | dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) 11 | return strings.Replace(dir, "\\", "/", -1) 12 | } 13 | 14 | // check filename exists 15 | func Exists(filename string) bool { 16 | _, err := os.Stat(filename) 17 | return err == nil || os.IsExist(err) 18 | } 19 | 20 | func ExistsInPath(root string, fileName string) bool { 21 | fullPath, err := FullPath(fileName) 22 | if err != nil { 23 | return false 24 | } 25 | if !Exists(fullPath) { 26 | return false 27 | } 28 | fullRoot, err := FullPath(root) 29 | if err != nil { 30 | return false 31 | } 32 | if !Exists(fullRoot) { 33 | return false 34 | } 35 | return strings.HasPrefix(fullPath, fullRoot) 36 | } 37 | 38 | func FullPath(fileName string) (string, error) { 39 | path, err := filepath.Abs(fileName) 40 | if err != nil { 41 | return "", err 42 | } 43 | return strings.Replace(path, "\\", "/", -1), nil 44 | } 45 | -------------------------------------------------------------------------------- /resources/config/develop/app.conf: -------------------------------------------------------------------------------- 1 | global: 2 | retrylimit: 5 3 | checknetinterval: 0 4 | databaseconnectstring: rock:rock@tcp(118.31.32.168:3306)/rockman?charset=utf8&allowOldPasswords=1&loc=Asia%2FShanghai&parseTime=true 5 | node: 6 | nodeid: a1e97685392845f7b5bbd18f38a10461 7 | nodename: "" 8 | ismaster: true 9 | isworker: true 10 | leadercheckexecutorinterval: 60 11 | rpc: 12 | outerhost: "" 13 | outerport: "" 14 | rpchost: "" 15 | rpcport: "2398" 16 | rpcprotocol: "" 17 | enabletls: false 18 | servercertfile: tls/server.crt 19 | serverkeyfile: tls/server.key 20 | clientcertfile: tls/client.crt 21 | clientkeyfile: tls/client.key 22 | webui: 23 | httphost: "" 24 | httpport: "8080" 25 | runtime: 26 | isrun: true 27 | logpath: ./logs/runtime 28 | enableshellscript: false 29 | logger: 30 | logpath: ./logs 31 | cluster: 32 | clusterid: dev-rock 33 | registryserver: 116.62.16.66:8500 34 | watchleaderretrylimit: 10 35 | queryresourceinterval: 60 36 | prometheus: 37 | isrun: true 38 | httphost: "" 39 | httpport: "8081" -------------------------------------------------------------------------------- /metrics/standard/counter.go: -------------------------------------------------------------------------------- 1 | package standard 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | ) 7 | 8 | // StandardCounter is the standard implementation of a Counter 9 | type StandardCounter struct { 10 | count int64 11 | startTime time.Time 12 | } 13 | 14 | func NewStandardCounter() *StandardCounter { 15 | return &StandardCounter{startTime: time.Now()} 16 | } 17 | 18 | func (c *StandardCounter) StartTime() time.Time { 19 | return c.startTime 20 | } 21 | 22 | // Clear sets the counter to zero. 23 | func (c *StandardCounter) Clear() { 24 | atomic.StoreInt64(&c.count, 0) 25 | } 26 | 27 | // Count returns the current count. 28 | func (c *StandardCounter) Count() int64 { 29 | return atomic.LoadInt64(&c.count) 30 | } 31 | 32 | // Dec decrements the counter by 1. 33 | func (c *StandardCounter) Dec() { 34 | c.Add(-1) 35 | } 36 | 37 | // Inc increments the counter by 1. 38 | func (c *StandardCounter) Inc() { 39 | c.Add(1) 40 | } 41 | 42 | // Add increments the counter by the given value. 43 | func (c *StandardCounter) Add(value int64) { 44 | atomic.AddInt64(&c.count, value) 45 | } 46 | -------------------------------------------------------------------------------- /metrics/standard/counter_test.go: -------------------------------------------------------------------------------- 1 | package standard 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStandardCounter_Inc(t *testing.T) { 8 | var wantCount int64 = 1 9 | counter := NewStandardCounter() 10 | counter.Inc() 11 | realCount := counter.Count() 12 | if realCount != wantCount { 13 | t.Error("count not correct:", wantCount, realCount) 14 | } else { 15 | t.Log("success.") 16 | } 17 | } 18 | 19 | func TestStandardCounter_Dec(t *testing.T) { 20 | var wantCount int64 = -1 21 | counter := NewStandardCounter() 22 | counter.Dec() 23 | realCount := counter.Count() 24 | if realCount != wantCount { 25 | t.Error("count not correct:", wantCount, realCount) 26 | } else { 27 | t.Log("success.") 28 | } 29 | } 30 | 31 | func TestStandardCounter_Clear(t *testing.T) { 32 | var wantCount int64 = 0 33 | counter := NewStandardCounter() 34 | counter.Inc() 35 | counter.Inc() 36 | counter.Inc() 37 | counter.Clear() 38 | realCount := counter.Count() 39 | if realCount != wantCount { 40 | t.Error("count not correct:", wantCount, realCount) 41 | } else { 42 | t.Log("success.") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/node.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "github.com/devfeel/rockman/util/json" 4 | 5 | type NodeInfo struct { 6 | NodeID string 7 | Cluster string 8 | Host string 9 | Port string 10 | OuterHost string 11 | OuterPort string 12 | Executors []string 13 | IsMaster bool 14 | IsWorker bool 15 | IsOnline bool 16 | } 17 | 18 | func (n *NodeInfo) EndPoint() string { 19 | host := n.Host 20 | if n.OuterHost != "" { 21 | host = n.OuterHost 22 | } 23 | port := n.Port 24 | if n.OuterPort != "" { 25 | host = n.OuterPort 26 | } 27 | return host + ":" + port 28 | } 29 | 30 | func (n *NodeInfo) Json() string { 31 | return _json.GetJsonString(n) 32 | } 33 | 34 | func (n *NodeInfo) LoadFromJson(json string) error { 35 | return _json.Unmarshal(json, n) 36 | } 37 | 38 | func (n *NodeInfo) GetEmptyResource() *ResourceInfo { 39 | return &ResourceInfo{EndPoint: n.EndPoint()} 40 | } 41 | 42 | func (n *NodeInfo) GetNodeKey(clusterId string) string { 43 | return GetNodeKeyPrefix(clusterId) + n.EndPoint() 44 | } 45 | 46 | func GetNodeKeyPrefix(clusterId string) string { 47 | return ClusterKeyPrefix + clusterId + "/nodes/" 48 | } 49 | -------------------------------------------------------------------------------- /protected/model/LogInfo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type TaskExecLog struct { 6 | LogID int64 7 | TaskID string 8 | NodeID string 9 | NodeEndPoint string 10 | StartTime time.Time 11 | EndTime time.Time 12 | IsSuccess bool 13 | FailureType string 14 | FailureCause string 15 | CreateTime time.Time 16 | } 17 | 18 | type TaskStateLog struct { 19 | LogID int64 20 | TaskID string 21 | NodeID string 22 | NodeEndPoint string 23 | State bool 24 | Message string 25 | CreateTime time.Time 26 | } 27 | 28 | type TaskSubmitLog struct { 29 | LogID int64 30 | TaskID string 31 | NodeID string 32 | NodeEndPoint string 33 | IsSuccess bool 34 | FailureType string 35 | FailureCause string 36 | CreateTime time.Time 37 | } 38 | 39 | type NodeTraceLog struct { 40 | LogID int64 41 | NodeID string 42 | NodeEndPoint string 43 | IsLeader bool 44 | IsMaster bool 45 | IsWorker bool 46 | Event string 47 | IsSuccess bool 48 | FailureType string 49 | FailureCause string 50 | CreateTime time.Time 51 | } 52 | -------------------------------------------------------------------------------- /webui/webapp/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // 生成器函数*的前后空格 25 | 'generator-star-spacing': 'off', 26 | // 忽略end标签错误 27 | 'vue/no-parsing-error': [2, { "x-invalid-end-tag": false }], 28 | // allow debugger during development 29 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 30 | // 配置首行缩进 31 | 'indent':0, 32 | //不检查分号 33 | 'semi': 0, 34 | ////函数定义时括号前面要不要有空格 35 | 'space-before-function-paren': 0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webui/webapp/static/rockman6.svg: -------------------------------------------------------------------------------- 1 | rockman6 -------------------------------------------------------------------------------- /resources/config/develop/dotlog.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /util/uuid/uuid_test.go: -------------------------------------------------------------------------------- 1 | package _uuid 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/devfeel/dotweb/test" 7 | ) 8 | 9 | // Test_GetUUID_V1_32 test uuid with v1 and return 32 len string 10 | func Test_GetUUID_V1_32(t *testing.T) { 11 | uuid := NewV1().String32() 12 | t.Log("GetUUID:", uuid) 13 | test.Equal(t, 32, len(uuid)) 14 | } 15 | 16 | // Test_GetUUID_V1 test uuid with v1 and return 36 len string 17 | func Test_GetUUID_V1(t *testing.T) { 18 | uuid := NewV1().String() 19 | t.Log("GetUUID:", uuid) 20 | test.Equal(t, 36, len(uuid)) 21 | } 22 | 23 | func Benchmark_GetUUID_V1_32(b *testing.B) { 24 | for i := 0; i < b.N; i++ { 25 | NewV1().String32() 26 | } 27 | } 28 | 29 | // Test_GetUUID_V4_32 test uuid with v1 and return 32 len string 30 | func Test_GetUUID_V4_32(t *testing.T) { 31 | uuid := NewV4().String32() 32 | t.Log("GetUUID:", uuid) 33 | test.Equal(t, 32, len(uuid)) 34 | } 35 | 36 | // Test_GetUUID_V4 test uuid with v1 and return 36 len string 37 | func Test_GetUUID_V4(t *testing.T) { 38 | uuid := NewV4().String() 39 | t.Log("GetUUID:", uuid) 40 | test.Equal(t, 36, len(uuid)) 41 | } 42 | func Benchmark_GetUUID_V4_32(b *testing.B) { 43 | for i := 0; i < b.N; i++ { 44 | NewV4().String32() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /util/file/file_test.go: -------------------------------------------------------------------------------- 1 | package _file 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | // 以下是功能测试 10 | 11 | func Test_GetCurrentDirectory_1(t *testing.T) { 12 | thisDir := GetCurrentDirectory() 13 | t.Log(thisDir) 14 | } 15 | 16 | func Test_GetFileExt_1(t *testing.T) { 17 | fn := "/download/vagrant_1.9.2.dmg" 18 | fileExt := filepath.Ext(fn) 19 | if len(fileExt) < 1 { 20 | t.Error("fileExt null!") 21 | } else { 22 | t.Log(fileExt) 23 | } 24 | } 25 | 26 | func Test_GetFileExt_2(t *testing.T) { 27 | fn := "/download/vagrant_1.abc" 28 | fileExt := filepath.Ext(fn) 29 | if len(fileExt) < 1 { 30 | t.Error("fileExt null!") 31 | } else { 32 | t.Log(fileExt) 33 | } 34 | } 35 | 36 | func Test_Exist_1(t *testing.T) { 37 | fn := "testdata/file.test" 38 | // fn := "/Users/kevin/Downloads/commdownload.dmg" 39 | isExist := Exists(fn) 40 | if isExist { 41 | t.Log(isExist) 42 | } else { 43 | t.Log("请修改测试代码中文件的路径!") 44 | } 45 | } 46 | 47 | func Test_ExistsInPath(t *testing.T) { 48 | fmt.Println(ExistsInPath("../../", "../../main.go")) 49 | } 50 | 51 | // 以下是性能测试 52 | func BenchmarkExistsInPath(b *testing.B) { 53 | for i := 0; i < b.N; i++ { 54 | ExistsInPath("../../", "../../main.go") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /webui/controllers/Controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/devfeel/dotweb" 5 | "github.com/devfeel/rockman/config" 6 | "github.com/devfeel/rockman/node" 7 | "github.com/devfeel/rockman/rpc/client" 8 | _const "github.com/devfeel/rockman/webui/const" 9 | "github.com/devfeel/rockman/webui/contract" 10 | ) 11 | 12 | func getNode(ctx dotweb.Context) *node.Node { 13 | nodeItem, exists := ctx.AppItems().Get(_const.ItemKeyNode) 14 | if !exists { 15 | return nil 16 | } 17 | return nodeItem.(*node.Node) 18 | } 19 | 20 | func getLeader(ctx dotweb.Context) string { 21 | leader, _ := getNode(ctx).Cluster.GetLeaderInfo() 22 | return leader 23 | } 24 | 25 | func GetRpcClient(endPoint string) *client.RpcClient { 26 | config := config.GetProfile() 27 | return client.NewRpcClient(endPoint, config.Rpc.EnableTls, config.Rpc.ClientCertFile, config.Rpc.ClientKeyFile) 28 | } 29 | 30 | func NewResponse(retCode int, retMsg string, message interface{}) *contract.Response { 31 | return contract.NewResponse(retCode, retMsg, message) 32 | } 33 | 34 | func SuccessResponse(message interface{}) *contract.Response { 35 | return contract.SuccessResponse(message) 36 | } 37 | 38 | func FailedResponse(retCode int, retMsg string) *contract.Response { 39 | return contract.FailedResponse(retCode, retMsg) 40 | } 41 | -------------------------------------------------------------------------------- /webui/webapp/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /webui/webapp/src/common/tableminix.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | queryParam: { 5 | PageIndex: 1, 6 | PageSize: 10 7 | // params: {} 8 | }, 9 | dataSource: { 10 | colDefs: { 11 | BodyFieldParams: [] 12 | }, 13 | pageData: [], 14 | totalCount: 0 15 | }, 16 | 17 | ignoreColumns: [], 18 | detailColumns: [], 19 | 20 | tableHeight: 250 21 | } 22 | }, 23 | methods: { 24 | onDataSourceChange(ds) { 25 | this.dataSource = { 26 | ColDefs: { 27 | BodyFieldParams: [] 28 | }, 29 | Result: [], 30 | TotalCount: 0 31 | }; 32 | this.$nextTick(_ => { 33 | this.dataSource = ds; 34 | }); 35 | }, 36 | onColumnVisible(col, detail) { 37 | if (detail) { 38 | if (!col.Visible) return false; 39 | if (!this.detailColumns || this.detailColumns.length === 0) return true; 40 | return this.detailColumns.includes(col.FieldName); 41 | } else { 42 | return col.Visible; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /protected/repository/BaseRepository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/devfeel/database/mysql" 5 | "github.com/devfeel/rockman/logger" 6 | ) 7 | 8 | type BaseRepository struct { 9 | mysql.MySqlDBContext 10 | databaseLogger logger.Logger 11 | } 12 | 13 | func (base *BaseRepository) InitLogger() { 14 | base.databaseLogger = logger.GetLogger(logger.LoggerName_Repository) 15 | base.GetCommand().SetOnTrace(base.Trace) 16 | base.GetCommand().SetOnDebug(base.Debug) 17 | base.GetCommand().SetOnInfo(base.Info) 18 | base.GetCommand().SetOnWarn(base.Warn) 19 | base.GetCommand().SetOnError(base.Error) 20 | } 21 | 22 | func (base *BaseRepository) Trace(content interface{}) { 23 | if base.databaseLogger != nil { 24 | base.databaseLogger.Trace(content) 25 | } 26 | } 27 | 28 | func (base *BaseRepository) Debug(content interface{}) { 29 | if base.databaseLogger != nil { 30 | base.databaseLogger.Debug(content) 31 | } 32 | } 33 | 34 | func (base *BaseRepository) Info(content interface{}) { 35 | if base.databaseLogger != nil { 36 | base.databaseLogger.Info(content) 37 | } 38 | } 39 | 40 | func (base *BaseRepository) Warn(content interface{}) { 41 | if base.databaseLogger != nil { 42 | base.databaseLogger.Warn(content) 43 | } 44 | } 45 | 46 | func (base *BaseRepository) Error(err error, content interface{}) { 47 | if base.databaseLogger != nil { 48 | base.databaseLogger.Error(err, content) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "github.com/devfeel/dotlog" 4 | 5 | const ( 6 | defaultDateFormatForFileName = "2006_01_02" 7 | defaultFullTimeLayout = "2006-01-02 15:04:05.999999" 8 | ) 9 | 10 | const ( 11 | LoggerName_Service = "ServiceLogger" 12 | LoggerName_Repository = "RepositoryLogger" 13 | LoggerName_Node = "NodeLogger" 14 | LoggerName_Cluster = "ClusterLogger" 15 | LoggerName_Runtime = "RuntimeLogger" 16 | LoggerName_Task = "TaskLogger" 17 | LoggerName_Default = "DefaultLogger" 18 | LoggerName_Rpc = "RpcLogger" 19 | ) 20 | 21 | type Logger interface { 22 | dotlog.Logger 23 | } 24 | 25 | func StartLogService(confPath string) error { 26 | return dotlog.StartLogService(confPath + "/dotlog.conf") 27 | } 28 | 29 | func GetLogger(loggerName string) Logger { 30 | return dotlog.GetLogger(loggerName) 31 | } 32 | 33 | func Default() Logger { 34 | return GetLogger(LoggerName_Default) 35 | } 36 | 37 | func Runtime() Logger { 38 | return GetLogger(LoggerName_Runtime) 39 | } 40 | 41 | func Task() Logger { 42 | return GetLogger(LoggerName_Task) 43 | } 44 | 45 | func Rpc() Logger { 46 | return GetLogger(LoggerName_Rpc) 47 | } 48 | 49 | func Node() Logger { 50 | return GetLogger(LoggerName_Node) 51 | } 52 | 53 | func Cluster() Logger { 54 | return GetLogger(LoggerName_Cluster) 55 | } 56 | 57 | func Service() Logger { 58 | return GetLogger(LoggerName_Service) 59 | } 60 | -------------------------------------------------------------------------------- /resources/tls/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID1zCCAr+gAwIBAgIJANINJBfiRLCZMA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAlkb3R3ZWIuY24x 5 | GjAYBgkqhkiG9w0BCQEWC3B6cnJAcXEuY29tMB4XDTIwMDQwNDAwNDQwN1oXDTMw 6 | MDQwMjAwNDQwN1owgYExCzAJBgNVBAYTAkRFMQwwCgYDVQQIDANOUlcxDjAMBgNV 7 | BAcMBUVhcnRoMRcwFQYDVQQKDA5SYW5kb20gQ29tcGFueTELMAkGA1UECwwCSVQx 8 | EjAQBgNVBAMMCWRvdHdlYi5jbjEaMBgGCSqGSIb3DQEJARYLcHpyckBxcS5jb20w 9 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcWUyAhOZnN1RI4RvWFtLZ 10 | eu3+2En6RwdDapuoZu5a0L1v+r86beSUhFPVgZ5LS5oFqvcnC26PDZjxy+qhf5v1 11 | 18pDjeQ9C/RKsEWIPqhe+sAJOjF/t2zY/82UqAg3JELCAbr4f2SieUTFLbGXJZZ/ 12 | AKXle3MrQkJinyq37oaSTKftHBwoUwnuP7lJ65g5tsltPjgrIxubPzJzbBVdiFOI 13 | /7cO+0Xd0tfITmIRJBtPvBbQGevg/Z68eNxY93sxErHWZK38m5yk+KRRwMMYQecx 14 | L0mR4d5NkszvyKeX/JT3/0lNLkcipq6Mqa/HqAtT3cJPRsPdYsMPxvdBNecaE3dl 15 | AgMBAAGjUDBOMB0GA1UdDgQWBBQIzrv5R0ix6L2w6SCDcsruNh592zAfBgNVHSME 16 | GDAWgBQIzrv5R0ix6L2w6SCDcsruNh592zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 17 | DQEBCwUAA4IBAQCXG0RCTtg7bSG+wsdZWCTx0Kj+jUr8eOKNHrJt5UB1Bed0BHAf 18 | tI7rw4sm1icE6g5BVlnWTVKC/F/FHPhMkHRzCZeWa2un6WJuxqfFt7X2TCBFdQa6 19 | VNIepjVEzPP++oo0ft+jgPZltE7AFNjLnrKgm+iWIQqN7x8DBbxNzKD8GOC/IG8l 20 | Zn91ZghHVhlO5A7wscBOz606wQwGbJXEuEjGJO/TEQLzVmOOhlqSsAnN9RjWucuP 21 | QCiiSBUmJFjzWb+0yurpJwewjOYhBub2KM10q/F/A/dM4YZNfhSDii8nAdPvXOsn 22 | zLD6pn7Xwl53SVW9iYSIVcuRuWuB121tlLOv 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /resources/tls/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID1zCCAr+gAwIBAgIJAIAmwy0aAsgPMA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAlkb3R3ZWIuY24x 5 | GjAYBgkqhkiG9w0BCQEWC3B6cnJAcXEuY29tMB4XDTIwMDQwNDAwNDQwN1oXDTMw 6 | MDQwMjAwNDQwN1owgYExCzAJBgNVBAYTAkRFMQwwCgYDVQQIDANOUlcxDjAMBgNV 7 | BAcMBUVhcnRoMRcwFQYDVQQKDA5SYW5kb20gQ29tcGFueTELMAkGA1UECwwCSVQx 8 | EjAQBgNVBAMMCWRvdHdlYi5jbjEaMBgGCSqGSIb3DQEJARYLcHpyckBxcS5jb20w 9 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2zF9Jjbnj5CygCeK4nnDf 10 | cTCPyF3YlOgdeGuNBxLjJz8FpfD9chgLV3qhUEqw3BeltmAvCrSymPokuzxnaKMZ 11 | xCie/kAC++LgvqgwvK7aj+drXTpRNHzQ/D10/IKJGPyTFGWihYxOLf86BRoPDmfh 12 | Px38H5wEmt2HjCr/xcKmGqETM2DRB00OZADCzQNwyNG7wQNCA/PW0lik2yODt2Ao 13 | vU1MKOjMQ/KhCrGvz543D/BLRVAe3RhRyOO8FXS+A1CK9jUuiEvVhKvtWLPHxlkC 14 | Lvueub3szklzgVsitLVE9vmFjzzvwsV1RjLtf5yvkdHbqYucW55YSkw7lBphqF/L 15 | AgMBAAGjUDBOMB0GA1UdDgQWBBQSSHQiQn5o5aIjady0Wnc/vxVt4zAfBgNVHSME 16 | GDAWgBQSSHQiQn5o5aIjady0Wnc/vxVt4zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 17 | DQEBCwUAA4IBAQCSbD34r/dWWaHv1it56THpJZHLg027/a7Dstdr1WqmU9ppGDn9 18 | rutnaXnwJLKz2XQFumltEi4fzpALxYnN1yWCo6a08rqu7jXwxVPcxvv7bW2WEBaX 19 | z8Vk+4TVv5keBoTGSZgoLDfyhssb78o0O/sQx63VO8wkRWRSRS3FKb/v/uqdbtEy 20 | GI0qzjdvXvKxLBTroPLTIrnqUZwiK4SPfxsCKNXpYeN6YpcJmoBRFRe2CuuCEKpN 21 | 20id+48CYNPo9x9B9e5/AnNnwqEFVQp1N7NNy18Qk+eeTj5jkqpo+0IMKOY4lVoh 22 | KRSwcPP5XCbrUA8Dn3nluFN8OLu/G2uG4cz9 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /webui/webapp/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | export default new Router({ 7 | mode: 'history', 8 | routes: [ 9 | { 10 | path: '/static', 11 | redirect: { name: 'home' } 12 | }, 13 | { 14 | path: '/static/index', 15 | component: resolve => require(['../views/main/index.vue'], resolve), 16 | children: [ 17 | { 18 | path: '/static/home', 19 | name: 'home', 20 | component: resolve => require(['../views/home/index.vue'], resolve) 21 | }, 22 | { 23 | path: '/static/node', 24 | name: 'node', 25 | component: resolve => require(['../views/node/index.vue'], resolve) 26 | }, 27 | { 28 | path: '/static/task', 29 | name: 'task', 30 | component: resolve => require(['../views/task/index.vue'], resolve) 31 | }, 32 | { 33 | path: '/static/task/detail', 34 | name: 'detail', 35 | component: resolve => require(['../views/task/detail.vue'], resolve) 36 | }, 37 | { 38 | path: '/static/task/logdetail', 39 | name: 'logdetail', 40 | component: resolve => require(['../views/task/logdetail.vue'], resolve) 41 | } 42 | ] 43 | }, 44 | { 45 | path: '/static/login', 46 | name: 'login', 47 | component: resolve => require(['../views/login.vue'], resolve) 48 | } 49 | ] 50 | }) 51 | -------------------------------------------------------------------------------- /webui/webapp/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /runtime/executor/executor.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "errors" 5 | "github.com/devfeel/dottask" 6 | "github.com/devfeel/rockman/core" 7 | ) 8 | 9 | const ( 10 | TargetType_Http = "http" 11 | TargetType_Shell = "shell" 12 | TargetType_GoSo = "goso" 13 | TargetType_Code = "code" 14 | 15 | CorrectStatus = "200 OK" 16 | CorrectResult = "OK" 17 | ) 18 | 19 | type ( 20 | Executor interface { 21 | GetTask() task.Task 22 | SetTask(task.Task) 23 | GetTaskID() string 24 | GetTargetType() string 25 | GetTaskConfig() *core.TaskConfig 26 | Exec(*task.TaskContext) error 27 | } 28 | 29 | Exec func(ctx *task.TaskContext) error 30 | 31 | baseExecutor struct { 32 | Task task.Task 33 | TaskConfig *core.TaskConfig 34 | } 35 | ) 36 | 37 | var ErrorNotMatchConfigType = errors.New("not match config type") 38 | 39 | func (exec *baseExecutor) GetTask() task.Task { 40 | return exec.Task 41 | } 42 | 43 | func (exec *baseExecutor) SetTask(task task.Task) { 44 | exec.Task = task 45 | } 46 | 47 | func (exec *baseExecutor) GetTaskID() string { 48 | return exec.TaskConfig.TaskID 49 | } 50 | 51 | func (exec *baseExecutor) GetTaskConfig() *core.TaskConfig { 52 | return exec.TaskConfig 53 | } 54 | 55 | func (exec *baseExecutor) GetTargetType() string { 56 | return exec.TaskConfig.TargetType 57 | } 58 | 59 | // ValidateTargetType validate the TargetType is supported 60 | func ValidateTargetType(execType string) bool { 61 | if execType != TargetType_Http && execType != TargetType_GoSo && execType != TargetType_Shell { 62 | return false 63 | } 64 | return true 65 | } 66 | -------------------------------------------------------------------------------- /core/resource.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type ( 4 | ResourceInfo struct { 5 | EndPoint string 6 | CpuRate int //cpu rate, refresh per 1 minute 7 | MemoryRate int //memory rate, refresh per 1 minute 8 | TaskCount int //job count 9 | LoadValue int //load value = cpu * 30 + memory * 30 + jobs * 40 10 | } 11 | 12 | LoadResources []*ResourceInfo 13 | CpuResources []*ResourceInfo 14 | MemoryResources []*ResourceInfo 15 | JobResources []*ResourceInfo 16 | ) 17 | 18 | // refreshLoadValue refresh resource's load value 19 | func (r *ResourceInfo) RefreshLoadValue() int { 20 | r.LoadValue = r.CpuRate*30 + r.MemoryRate*30 + r.TaskCount*40 21 | return r.LoadValue 22 | } 23 | 24 | func (rs LoadResources) Len() int { 25 | return len(rs) 26 | } 27 | func (rs LoadResources) Less(i, j int) bool { 28 | return rs[i].LoadValue > rs[j].LoadValue 29 | } 30 | func (rs LoadResources) Swap(i, j int) { 31 | rs[i], rs[j] = rs[j], rs[i] 32 | } 33 | 34 | func (rs CpuResources) Len() int { 35 | return len(rs) 36 | } 37 | func (rs CpuResources) Less(i, j int) bool { 38 | return rs[i].CpuRate > rs[j].CpuRate 39 | } 40 | func (rs CpuResources) Swap(i, j int) { 41 | rs[i], rs[j] = rs[j], rs[i] 42 | } 43 | 44 | func (rs MemoryResources) Len() int { 45 | return len(rs) 46 | } 47 | func (rs MemoryResources) Less(i, j int) bool { 48 | return rs[i].MemoryRate > rs[j].MemoryRate 49 | } 50 | func (rs MemoryResources) Swap(i, j int) { 51 | rs[i], rs[j] = rs[j], rs[i] 52 | } 53 | 54 | func (rs JobResources) Len() int { 55 | return len(rs) 56 | } 57 | func (rs JobResources) Less(i, j int) bool { 58 | return rs[i].TaskCount > rs[j].TaskCount 59 | } 60 | func (rs JobResources) Swap(i, j int) { 61 | rs[i], rs[j] = rs[j], rs[i] 62 | } 63 | -------------------------------------------------------------------------------- /webui/webapp/src/views/node/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 45 | 63 | -------------------------------------------------------------------------------- /webui/webapp/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import {getUserInfo} from '@/api/login' 2 | 3 | const user = { 4 | state: { 5 | token: '', 6 | info: '', 7 | theme: '' 8 | }, 9 | mutations: { 10 | SET_TOKEN: (state, token) => { 11 | state.token = token 12 | }, 13 | SET_INFO: (state, info) => { 14 | state.info = info 15 | }, 16 | SET_THEME: (state, theme) => { 17 | state.theme = theme 18 | } 19 | }, 20 | 21 | actions: { 22 | // 获取用户信息 23 | GetUserInfo({commit, state}, loginForm) { 24 | return new Promise((resolve, reject) => { 25 | getUserInfo(loginForm).then(res => { 26 | if (res.state === 'success') { 27 | // 将用户信息存入store 28 | commit('SET_INFO', {name: res.name, id: res.id}) 29 | if (res.theme) { 30 | this.dispatch('ChangeTheme', res.theme) 31 | } else { 32 | } 33 | // 将用户信息存到本地缓存 34 | sessionStorage.setItem('userData', JSON.stringify(res)); 35 | resolve() 36 | } else { 37 | reject(res.message) 38 | } 39 | }).catch(error => { 40 | reject(error) 41 | }) 42 | }); 43 | }, 44 | // 登出 45 | Logout({commit, state}) { 46 | commit('SET_INFO', '') 47 | sessionStorage.removeItem('userData') 48 | }, 49 | // 改变用户主题 50 | ChangeTheme({commit, state}, theme) { 51 | commit('SET_THEME', theme) 52 | // 将用户改变的主题数据,存到缓存的userData里 53 | let userData = JSON.parse(sessionStorage.getItem('userData')); 54 | sessionStorage.setItem('userData', JSON.stringify({...userData, theme: theme})); 55 | // 存到数据库(不方便mock所以省略) 56 | } 57 | } 58 | } 59 | 60 | export default user 61 | -------------------------------------------------------------------------------- /webui/webapp/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import 'element-ui/lib/theme-chalk/index.css' 6 | import router from './router' 7 | import ElementUI from 'element-ui' 8 | import './assets/iconfont/iconfont.css' 9 | import GLOBAL from './common/global.js' 10 | import store from './store/store.js' 11 | import * as Utils from './common/utils.js' 12 | 13 | Vue.use(ElementUI); 14 | 15 | // 挂载到Vue实例上面 16 | Vue.prototype.GLOBAL = GLOBAL 17 | Vue.prototype.Utils = Utils 18 | 19 | if (process.env.NODE_ENV === 'development') { 20 | require('./api/mock'); 21 | } 22 | 23 | // 页面刷新时,重新赋值 24 | if (window.sessionStorage.getItem('Token')) { 25 | // let data = JSON.parse(window.sessionStorage.getItem('Token')); 26 | let token = window.sessionStorage.getItem('Token'); 27 | store.commit('SET_TOKEN', token) 28 | // store.dispatch('ChangeTheme', data.theme) 29 | } 30 | 31 | router.beforeEach(({meta, path}, from, next) => { 32 | // var {auth = true} = meta 33 | // true用户已登录, false用户未登录 34 | if (window.sessionStorage.getItem('Token') && path === '/static/login') { 35 | router.push({ path: '/static/home' }); 36 | } 37 | if (path === '/static/login') { 38 | window.sessionStorage.removeItem('Token'); 39 | } 40 | if (!window.sessionStorage.getItem('Token') && path !== '/static/login') { 41 | next({ path: '/static/login' }); 42 | } else { 43 | next(); 44 | } 45 | }) 46 | 47 | Vue.config.productionTip = false 48 | 49 | let vm = new Vue({ 50 | el: '#app', 51 | store, 52 | router, 53 | components: {App}, 54 | template: '' 55 | }) 56 | 57 | Vue.use({ 58 | vm 59 | }) 60 | -------------------------------------------------------------------------------- /webui/webapp/src/views/login_1.vue: -------------------------------------------------------------------------------- 1 | 16 | 40 | 67 | -------------------------------------------------------------------------------- /resources/tls/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcWUyAhOZnN1RI 3 | 4RvWFtLZeu3+2En6RwdDapuoZu5a0L1v+r86beSUhFPVgZ5LS5oFqvcnC26PDZjx 4 | y+qhf5v118pDjeQ9C/RKsEWIPqhe+sAJOjF/t2zY/82UqAg3JELCAbr4f2SieUTF 5 | LbGXJZZ/AKXle3MrQkJinyq37oaSTKftHBwoUwnuP7lJ65g5tsltPjgrIxubPzJz 6 | bBVdiFOI/7cO+0Xd0tfITmIRJBtPvBbQGevg/Z68eNxY93sxErHWZK38m5yk+KRR 7 | wMMYQecxL0mR4d5NkszvyKeX/JT3/0lNLkcipq6Mqa/HqAtT3cJPRsPdYsMPxvdB 8 | NecaE3dlAgMBAAECggEAUTNbPNU87vZ1yfGix29bvidOAycDhAaex5pGyHU+Cxt3 9 | ZAe8ZrTOkmh9WGXOxqKSnNoXAD13PaJeJWTnDyKuf5dJA0ILxO9d1BRHW2DzvSSc 10 | lgnq6czXgNQrl0eg12k2bLYkh6SymikkTaF8G14Pqre7aujmVR+yxRMws91jnA+S 11 | fUMfoRe0x/lG5euZr5SWX+I3mthIMTP+HAoM7VofNLEzG8e+HxE8V8dPA+16KPJ8 12 | qoKlg/ZW73UFo84oU0R8w1g26glDh4RdScIKuQNXlj2NkPw0kDZQb7Yb4sLbh3MD 13 | 9X6lwt+pRzottuaqKeyka895CEMRXQ7HoHxSRsW+IQKBgQD2J3d8oW/RqQ4zD5fc 14 | fMC65yS8pEEPzKNxtNiQBtyJ+LZ0cgN7SxOU+QjlOvr6ua4HxPgEZl1KC2X/Hb/6 15 | Rj/N+6otIay+74Y59kX3EazGGdChRZAu5my/SaVhbunQCN9QDjEbvED6rZPZBrNx 16 | T5QzdEBWa+bAzeTK9jip+xFOvQKBgQDlKZgl6/Rsxn3IKw7OTq/M8ofwj4CBN8TQ 17 | W5z59Ik0Tq26mDBdShy/l6g1F+rs+yEh2Q4KImzpU6Km3angPQSahos0GR8JdQro 18 | pOb28cYrQta5spRXtHSfwj4+osY8ABb1lWoqlr/4bDKr4vd+pudTC7OJkV46E1Ou 19 | TzVjrBgJyQKBgQDddmavT8N92162G9piei8gOSO5QQJ0R4XpoG6TLkkvLQBixc1S 20 | GRrWFjSloKn7+CCWRuVpd/uIybaLj+WQM7HmDf1uu3RjGLcn4OXTwygx1zn4TtDJ 21 | EXFXrNfJUHO2QrU/nnUaYgYIQzDYuompcZKBtJ7J4ixq3RNyc5JJhGRu9QKBgQCR 22 | RRnfSEGtRvLrlmgPXpBRQpnsZHvY7kqErt7HET58+zdkZx+Zs+afifsmlR/m1Je/ 23 | RkKBKCuUieE9GJ2cufGWJ55NC3PCYgDlU8vaVZADUp6eH1esdYfM0qz0tekmlgP3 24 | 8JzZnLDbQXOpxPqRvio8WdmZZp6uABx3Dl1Q7wJaCQKBgG/eRJlcCRSAsev/yQV0 25 | dwHoTN0dvBoVfr0N28SPCePTux04/9Q+MBEh1SFyNwCHC72R2bI9IwJgDiNiyUYi 26 | 4wHifrgJpDeSCDOYvYxLWDeyKIf3oa9Ssl40hqnFwFvrs+cIYkYSJeENsR1iaC4j 27 | vp9hrVEJ1lgCTQj8E8ZFCXOA 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /resources/tls/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2zF9Jjbnj5Cyg 3 | CeK4nnDfcTCPyF3YlOgdeGuNBxLjJz8FpfD9chgLV3qhUEqw3BeltmAvCrSymPok 4 | uzxnaKMZxCie/kAC++LgvqgwvK7aj+drXTpRNHzQ/D10/IKJGPyTFGWihYxOLf86 5 | BRoPDmfhPx38H5wEmt2HjCr/xcKmGqETM2DRB00OZADCzQNwyNG7wQNCA/PW0lik 6 | 2yODt2AovU1MKOjMQ/KhCrGvz543D/BLRVAe3RhRyOO8FXS+A1CK9jUuiEvVhKvt 7 | WLPHxlkCLvueub3szklzgVsitLVE9vmFjzzvwsV1RjLtf5yvkdHbqYucW55YSkw7 8 | lBphqF/LAgMBAAECggEAJmCL1MZjc3vQ/px0s+vXqKF734PiZ/kDLkS817PXfEce 9 | DRKEoMEWWTM5dKqFs9HRSSIb2WR3AVPqmFGS4ebu7xbmrFKWn8nAHQ0KBIlocExr 10 | +ndtKeVKGxoJ0Qi+YwgS5tIOcGt3RGaVwdmWWfjdSEXXBrqgJ7tXrQB5eo9LSVHM 11 | 4ZMDQoXvIPxz9dEC+k00DzDU9j9b43B/nQk/Vo28s59yeNMFzu+qv+bch3ehHDfk 12 | miWX04Xqac1m/ROzf7n+gU3fyCJ1OLkoso7o7dtMJEUhbTFhFFxeXPpk9I37fdyJ 13 | G3HGH214SmWJFdKu2BBWZkpPRbKoWmFuiPVPptyJQQKBgQDmdGSEFoMqfyidFzc/ 14 | XvWo3bxJWvIkLS3zeKgj3LehRq8/uy62o5QycAUNvijBCtIDXlQcWYZN4iSaEmZK 15 | 3OFuutKJG+9rmZ7m7ojYjv0G42hmcKozZO+/5z6KBpgDRSw1b3zg3Kza7AE7ebPJ 16 | MPS7/cm+zshFswEy2WbR/AV94QKBgQDLD6MHkXe7NGL74YY71KUaYmu55rEFLWJT 17 | BZgnC4HaSHDi5c++bivNhXtfBW1SzYTHzkti0sI92rkEywguvwl3eWQfK1HxdbMF 18 | 6r5NQyoScJ5TMm/NtAgW8nMeh8SAgpd9B79Pfj7C9raQ3upPGVZ3jh2eBwN71DH9 19 | +DfziCWbKwKBgQDg/Xtw1cXDBJgzHgpLLTtrXOfJOigzF4TIMeD9+iuxIaBQ+T83 20 | AmbaAzF75E5HgXfd4AcAEosjmIghf5sstY7Fm1GWmJZbl4RiczlZK+huBDDV6mLQ 21 | gFftJCeTe7KHZs4tcSYbJnb/2O+vEDdtUgVtT+3aAmnWWlfuO/+9PG5kIQKBgDyH 22 | EbJ80vKzX9ny1H9bGEewFKpaMGP5rjhPD5Z74J3/P97yX6VE07u0URz20ip/Zlfk 23 | jyYK6qljXAkot+yvNxQs1AJSeF+AFPfrjAQdxBmxBhCIlTj8s/ibFmOAVrtzsdIs 24 | EAOS/wRAZBT0zWT8iYCjlQJ41rn/e1NXvN3TtFn3AoGBAJtO+z3JLIS8yifeOl3w 25 | jnjr38xV2SXCN9Jx/l33mShmEcswMTIo32scm76Yi/Addly+/FE1CY85adShixrd 26 | 2QEdRsM/nGcKSZYwPQ4qreUR8trIOwoYRiMl7z+afXsFqvu3XJ/hwhtls4Sdt/gr 27 | 8dyFo2X/IfEHfIJQXEYEw9nN 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /registry/consul/consul_test.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/consul/api" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | var consulServer = "116.62.16.66:8500" 11 | var nodeKeyPrefix = "locker_rockman" 12 | 13 | func TestCreateLocker(t *testing.T) { 14 | lockKey := "locker_rockman" 15 | client, err := NewConsulClient(consulServer) 16 | if err != nil { 17 | t.Error("create consul client error", err) 18 | return 19 | } 20 | 21 | for i := 0; i < 3; i++ { 22 | go func(i int) { 23 | lock, err := client.CreateLocker(lockKey) 24 | if err != nil { 25 | fmt.Println(time.Now(), i, "create lock err", err) 26 | return 27 | } 28 | _, err = lock.Locker.Lock(nil) 29 | if err != nil { 30 | fmt.Println(time.Now(), i, "lock err", err) 31 | } else { 32 | fmt.Println(time.Now(), i, "lock success") 33 | time.Sleep(time.Minute) 34 | err = lock.Locker.Unlock() 35 | fmt.Println(time.Now(), i, "unlock success") 36 | } 37 | }(i) 38 | } 39 | time.Sleep(time.Hour) 40 | } 41 | 42 | func TestConsulClient_ListKV(t *testing.T) { 43 | client, err := NewConsulClient(consulServer) 44 | if err != nil { 45 | t.Error("create consul client error", err) 46 | return 47 | } 48 | nodeKVs, meta, err := client.ListKV(nodeKeyPrefix, nil) 49 | if err != nil { 50 | fmt.Println("RefreshNodes1 error: " + err.Error()) 51 | return 52 | } 53 | fmt.Println("RefreshNodes1 LastIndex:", meta.LastIndex) 54 | opt := &api.QueryOptions{ 55 | WaitIndex: meta.LastIndex, 56 | WaitTime: time.Minute * 10, 57 | } 58 | nodeKVs, meta, err = client.ListKV(nodeKeyPrefix, opt) 59 | if err != nil { 60 | fmt.Println("RefreshNodes2 error: " + err.Error()) 61 | } 62 | for _, s := range nodeKVs { 63 | fmt.Println(s.Key, string(s.Value), s.Session, meta.LastIndex) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webui/webapp/src/common/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import {Message} from 'view-design'; 3 | 4 | axios.interceptors.request.use(config => { 5 | config.headers = { 6 | 'Content-Type': 'application/json; charset=UTF-8' 7 | }; 8 | config.data = JSON.stringify(config.data); 9 | return config; 10 | }, error => { 11 | return Promise.reject(error) 12 | }) 13 | 14 | const codeMessage = { 15 | 200: '服务器成功返回请求的数据。', 16 | 201: '新建或修改数据成功。', 17 | 202: '一个请求已经进入后台排队(异步任务)。', 18 | 204: '删除数据成功。', 19 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 20 | 401: '用户没有权限(令牌、用户名、密码错误)。', 21 | 403: '用户得到授权,但是访问是被禁止的。', 22 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 23 | 406: '请求的格式不可得。', 24 | 410: '请求的资源被永久删除,且不会再得到的。', 25 | 422: '当创建一个对象时,发生一个验证错误。', 26 | 500: '服务器发生错误,请检查服务器。', 27 | 502: '网关错误。', 28 | 503: '服务不可用,服务器暂时过载或维护。', 29 | 504: '网关超时。' 30 | } 31 | 32 | axios.interceptors.response.use(response => { 33 | return Promise.resolve(response.data); 34 | }, error => { 35 | if (error.response) { 36 | let code = error.response.status; 37 | switch (code) { 38 | case 500: 39 | Message.error(error.response.data.RetMsg) 40 | break; 41 | default: 42 | Message.error(codeMessage[code]) 43 | } 44 | } 45 | return Promise.reject(error.response.data);// 返回接口返回的错误信息 46 | }) 47 | 48 | export const post = 49 | (url, data = {}) => { 50 | return axios({ 51 | method: 'post', 52 | url, 53 | data: data, 54 | timeout: 10000, 55 | headers: { 56 | 'X-Requested-With': 'XMLHttpRequest' // requestedWith 为 XMLHttpRequest 则为 Ajax 请求。 57 | } 58 | }) 59 | } 60 | export const get = 61 | (url, params = {}, config = {}) => { 62 | return axios({ 63 | method: 'get', 64 | url, 65 | params, // get 请求时带的参数 66 | timeout: 10000 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /webui/webapp/src/common/utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import 'moment/locale/zh-cn' 3 | import {Message} from 'view-design'; 4 | 5 | // 处理时间戳 6 | export const dealTimestamp = (timestamp, formatter = 'YYYY-MM-DD HH:mm:ss') => { 7 | if (timestamp) { 8 | return moment(Number(timestamp)).format(formatter) 9 | } else { 10 | return '' 11 | } 12 | } 13 | 14 | // 处理Date时间格式 15 | export const dealDate = (date, formatter = 'YYYY-MM-DD HH:mm:ss') => { 16 | if (date) { 17 | return moment(date).format(formatter) 18 | } else { 19 | return '' 20 | } 21 | } 22 | 23 | // 获取地址栏参数 24 | export const getUrlParam = (paraName) => { 25 | var url = document.location.toString(); 26 | var arrObj = url.split('?'); 27 | if (arrObj.length > 1) { 28 | var arrPara = arrObj[1].split('&'); 29 | var arr; 30 | for (var i = 0; i < arrPara.length; i++) { 31 | arr = arrPara[i].split('='); 32 | 33 | if (arr != null && arr[0] === paraName) { 34 | return arr[1]; 35 | } 36 | } 37 | return ''; 38 | } else { 39 | return ''; 40 | } 41 | } 42 | 43 | /** 44 | * 提示框 45 | * @param text 46 | * @param type(error warning info 默认是success) 47 | */ 48 | export const showMsg = (text, type = 'success') => { 49 | Message({ 50 | showClose: true, 51 | message: text, 52 | type: type, 53 | duration: 2000 54 | }) 55 | } 56 | 57 | /** 58 | * 将时间数组割成开始时间和结束时间 59 | */ 60 | export const dealTimeArr = (time, startName = 'startTime', endName = 'endTime') => { 61 | if (time) { 62 | return { 63 | [startName]: time[0], 64 | [endName]: time[1] 65 | } 66 | } 67 | return { 68 | [startName]: null, 69 | [endName]: null 70 | } 71 | } 72 | 73 | /** 74 | * 获取当前时间 75 | */ 76 | export const getNowTime = (formatter = 'YYYY-MM-DD') => { 77 | return moment().format(formatter) 78 | } 79 | -------------------------------------------------------------------------------- /webui/webapp/src/views/main/main.less: -------------------------------------------------------------------------------- 1 | .main{ 2 | .logo-con{ 3 | height: 64px; 4 | padding: 10px; 5 | img{ 6 | height: 44px; 7 | width: auto; 8 | display: block; 9 | margin: 0 auto; 10 | } 11 | } 12 | .header-con{ 13 | background: #fff; 14 | padding: 0 20px; 15 | width: 100%; 16 | } 17 | .main-layout-con{ 18 | height: 100%; 19 | overflow: hidden; 20 | } 21 | .main-content-con{ 22 | height: ~"calc(100% - 60px)"; 23 | overflow: hidden; 24 | } 25 | .tag-nav-wrapper{ 26 | padding: 0; 27 | height:40px; 28 | background:#F0F0F0; 29 | } 30 | .content-wrapper{ 31 | padding: 18px; 32 | height: ~"calc(100% - 80px)"; 33 | overflow: auto; 34 | } 35 | .left-sider{ 36 | .ivu-layout-sider-children{ 37 | overflow-y: scroll; 38 | margin-right: -18px; 39 | } 40 | } 41 | } 42 | .ivu-menu-item > i{ 43 | margin-right: 12px !important; 44 | } 45 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 46 | margin-right: 8px !important; 47 | } 48 | .collased-menu-dropdown{ 49 | width: 100%; 50 | margin: 0; 51 | line-height: normal; 52 | padding: 7px 0 6px 16px; 53 | clear: both; 54 | font-size: 12px !important; 55 | white-space: nowrap; 56 | list-style: none; 57 | cursor: pointer; 58 | transition: background 0.2s ease-in-out; 59 | &:hover{ 60 | background: rgba(100, 100, 100, 0.1); 61 | } 62 | & * { 63 | color: #515a6e; 64 | } 65 | .ivu-menu-item > i{ 66 | margin-right: 12px !important; 67 | } 68 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 69 | margin-right: 8px !important; 70 | } 71 | } 72 | 73 | .ivu-select-dropdown.ivu-dropdown-transfer{ 74 | max-height: 400px; 75 | } -------------------------------------------------------------------------------- /webui/webapp/src/assets/css/common.less: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing:border-box; 3 | -moz-box-sizing:border-box; /* Firefox */ 4 | -webkit-box-sizing:border-box; /* Safari */ 5 | } 6 | .el-pager li{ 7 | font-weight: 100; 8 | margin-right: 9px; 9 | border: 1px solid #eee; 10 | border-radius: 3px; 11 | min-width: 28px; 12 | } 13 | .el-pager li.active,.el-pager li:hover{ 14 | background: #ed4014; 15 | color: white; 16 | } 17 | .el-pagination__editor.el-input .el-input__inner{ 18 | height: 23px; 19 | } 20 | 21 | 22 | .animated { 23 | -webkit-animation-duration: 0.5s; 24 | animation-duration: 0.5s; 25 | -webkit-animation-fill-mode: both; 26 | animation-fill-mode: both; 27 | } 28 | 29 | @media (print), (prefers-reduced-motion) { 30 | .animated { 31 | -webkit-animation: unset !important; 32 | animation: unset !important; 33 | -webkit-transition: none !important; 34 | transition: none !important; 35 | } 36 | } 37 | 38 | @-webkit-keyframes fadeInDown { 39 | from { 40 | opacity: 1; 41 | -webkit-transform: translate3d(0, -100%, 0); 42 | transform: translate3d(0, -100%, 0); 43 | } 44 | 45 | to { 46 | opacity: 1; 47 | -webkit-transform: translate3d(0, 0, 0); 48 | transform: translate3d(0, 0, 0); 49 | } 50 | } 51 | 52 | @keyframes fadeInDown { 53 | from { 54 | opacity: 0; 55 | -webkit-transform: translate3d(0, -100%, 0); 56 | transform: translate3d(0, -100%, 0); 57 | } 58 | 59 | to { 60 | opacity: 1; 61 | -webkit-transform: translate3d(0, 0, 0); 62 | transform: translate3d(0, 0, 0); 63 | } 64 | } 65 | 66 | .fadeInDown { 67 | -webkit-animation-name: fadeInDown; 68 | animation-name: fadeInDown; 69 | } 70 | .ivu-message{ 71 | z-index: 999999999 !important; 72 | } 73 | .ivu-form-item-content{ 74 | text-align: left; 75 | } -------------------------------------------------------------------------------- /webui/controllers/ClusterController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/devfeel/dotweb" 7 | "github.com/devfeel/rockman/metrics" 8 | "html/template" 9 | ) 10 | 11 | type ClusterController struct { 12 | } 13 | 14 | func (c *ClusterController) ShowMetrics(ctx dotweb.Context) error { 15 | return ctx.WriteJson(SuccessResponse(metrics.GetAllCountInfo())) 16 | } 17 | 18 | func (c *ClusterController) ShowClusterInfo(ctx dotweb.Context) error { 19 | node := getNode(ctx) 20 | if node == nil { 21 | return ctx.WriteJson(NewResponse(-1001, "not exists node in app items", nil)) 22 | } 23 | return ctx.WriteJson(SuccessResponse(node.Cluster.ClusterInfo())) 24 | } 25 | 26 | func (c *ClusterController) ShowExecutors(ctx dotweb.Context) error { 27 | node := getNode(ctx) 28 | if node == nil { 29 | return ctx.WriteJson(NewResponse(-1001, "not exists node in app items", nil)) 30 | } 31 | return ctx.WriteHtml(FormatJson(NewResponse(0, "", node.Cluster.ExecutorInfos))) 32 | } 33 | 34 | func (c *ClusterController) ShowResources(ctx dotweb.Context) error { 35 | node := getNode(ctx) 36 | if node == nil { 37 | return ctx.WriteJson(NewResponse(-1001, "not exists node in app items", nil)) 38 | } 39 | return ctx.WriteHtml(FormatJson(NewResponse(0, "", node.Cluster.Scheduler.Resources()))) 40 | } 41 | 42 | func FormatJson(data interface{}) string { 43 | 44 | // 格式化Json,添加\t符 45 | by, _ := json.MarshalIndent(data, "", "\t") 46 | task := string(by) 47 | 48 | content := struct { 49 | Task string 50 | }{ 51 | Task: task, 52 | } 53 | 54 | // 定义html格式 55 | const html = ` 56 | 57 | 58 | 59 | 60 |
61 |
{{.Task}}
62 |
63 | 64 | ` 65 | 66 | // 使用html渲染json 67 | var doc bytes.Buffer 68 | temp, _ := template.New("").Parse(html) 69 | temp.Execute(&doc, content) 70 | 71 | return doc.String() 72 | } 73 | -------------------------------------------------------------------------------- /webui/webapp/static/rockman5.svg: -------------------------------------------------------------------------------- 1 | rockman5 -------------------------------------------------------------------------------- /webui/webapp/src/assets/styles/common.scss: -------------------------------------------------------------------------------- 1 | /*页面通用css*/ 2 | body { 3 | font-size: 14px; 4 | width: 100%; 5 | height: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .textPage { 10 | li { 11 | line-height: 28px; 12 | } 13 | } 14 | 15 | .content-box { 16 | position: absolute; 17 | left: 250px; 18 | right: 0; 19 | top: 70px; 20 | bottom: 0; 21 | -webkit-transition: left .3s ease-in-out; 22 | transition: left .3s ease-in-out; 23 | } 24 | 25 | .content-box.content-collapse { 26 | left: 65px; 27 | } 28 | 29 | .content { 30 | width: 100%; 31 | height: calc(100% - 50px); 32 | overflow-y: scroll; 33 | box-sizing: border-box; 34 | } 35 | 36 | .container { 37 | margin: 15px; 38 | padding: 15px; 39 | border-radius: 2px; 40 | //box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05); 41 | min-height: calc(100% - 100px); 42 | } 43 | 44 | .crumbs { 45 | position: relative; 46 | z-index: 1; 47 | height: 50px; 48 | display: flex; 49 | align-items: center; 50 | font-size: 16px; 51 | padding-left: 15px; 52 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05); 53 | } 54 | 55 | .filter-box { 56 | display: flex; 57 | } 58 | 59 | .pLabel { 60 | margin-bottom: 10px; 61 | padding-left: 13px; 62 | font-size: 16px; 63 | } 64 | 65 | .pageTitle { 66 | font-size: 20px; 67 | font-weight: bold; 68 | margin-bottom: 30px; 69 | } 70 | 71 | .curp:hover { 72 | @include hover(); 73 | } 74 | 75 | .fade-enter-active, .fade-leave-active { 76 | transition: opacity .5s; 77 | } 78 | 79 | .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ 80 | { 81 | opacity: 0; 82 | } 83 | 84 | .fade-enter-active, .fade-leave-active { 85 | transition: opacity .3s; 86 | } 87 | 88 | .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ 89 | { 90 | opacity: 0; 91 | } 92 | 93 | /* fade-transform */ 94 | .fade-transform-leave-active, 95 | .fade-transform-enter-active { 96 | transition: all .2s; 97 | } 98 | 99 | .fade-transform-enter { 100 | opacity: 0; 101 | transform: translateX(30px); 102 | } 103 | 104 | .fade-transform-leave-to { 105 | opacity: 0; 106 | transform: translateX(-30px); 107 | } 108 | -------------------------------------------------------------------------------- /protected/service/LogService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/devfeel/rockman/protected/model" 5 | "github.com/devfeel/rockman/protected/repository" 6 | "time" 7 | ) 8 | 9 | type LogService struct { 10 | BaseService 11 | repo *repository.LogRepo 12 | execService *ExecutorService 13 | } 14 | 15 | func NewLogService() *LogService { 16 | service := &LogService{ 17 | repo: repository.NewLogRepo(), 18 | execService: NewExecutorService(), 19 | } 20 | return service 21 | } 22 | 23 | // WriteExecLog 24 | func (service *LogService) WriteExecLog(log *model.TaskExecLog) error { 25 | log.CreateTime = time.Now() 26 | _, err := service.repo.WriteExecLog(log) 27 | return err 28 | } 29 | 30 | // QueryExecLogs 31 | func (service *LogService) QueryExecLogs(taskId string, pageReq *model.PageRequest) (*model.PageResult, error) { 32 | result, err := service.repo.QueryExecLogs(taskId, pageReq) 33 | return result, err 34 | } 35 | 36 | // WriteNodeTraceLog 37 | func (service *LogService) WriteNodeTraceLog(log *model.NodeTraceLog) error { 38 | log.CreateTime = time.Now() 39 | _, err := service.repo.WriteNodeTraceLog(log) 40 | return err 41 | } 42 | 43 | // WriteSubmitLog 44 | func (service *LogService) WriteSubmitLog(log *model.TaskSubmitLog) error { 45 | log.CreateTime = time.Now() 46 | _, err := service.repo.WriteSubmitLog(log) 47 | if err != nil { 48 | return err 49 | } 50 | if log.IsSuccess { 51 | runInfo := &model.ExecutorRunInfo{ 52 | TaskID: log.TaskID, 53 | NodeID: log.NodeID, 54 | NodeEndPoint: log.NodeEndPoint, 55 | } 56 | service.execService.SetExecutorRunInfo(runInfo) 57 | } 58 | return nil 59 | } 60 | 61 | // QueryStateLog 62 | func (service *LogService) QueryStateLog(taskId string, pageReq *model.PageRequest) (*model.PageResult, error) { 63 | result, err := service.repo.QueryStateLog(taskId, pageReq) 64 | return result, err 65 | } 66 | 67 | // QueryTaskSubmitLog 68 | func (service *LogService) QueryTaskSubmitLog(taskId string, pageReq *model.PageRequest) (*model.PageResult, error) { 69 | result, err := service.repo.QueryTaskSubmitLog(taskId, pageReq) 70 | return result, err 71 | } 72 | 73 | // QueryNodeTraceLog 74 | func (service *LogService) QueryNodeTraceLog(nodeId string, pageReq *model.PageRequest) (*model.PageResult, error) { 75 | result, err := service.repo.QueryNodeTraceLog(nodeId, pageReq) 76 | return result, err 77 | } 78 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/css/ViewContainer.less: -------------------------------------------------------------------------------- 1 | 2 | .layout-container { 3 | // border-top: 1px solid #eee; 4 | background: #eee; 5 | // padding: 15px; 6 | /* margin-bottom: 40px; */ 7 | } 8 | // .animated { 9 | // -webkit-animation-duration: 1s; 10 | // animation-duration: 1s; 11 | // -webkit-animation-fill-mode: both; 12 | // animation-fill-mode: both; 13 | // } 14 | // .animated.infinite { 15 | // -webkit-animation-iteration-count: infinite; 16 | // animation-iteration-count: infinite; 17 | // } 18 | // .animated.hinge { 19 | // -webkit-animation-duration: 2s; 20 | // animation-duration: 2s; 21 | // } 22 | // .animated.bounceIn, 23 | // .animated.bounceOut, 24 | // .animated.flipOutX, 25 | // .animated.flipOutY { 26 | // -webkit-animation-duration: 0.75s; 27 | // animation-duration: 0.75s; 28 | // } 29 | // @-webkit-keyframes fadeInDown { 30 | // 0% { 31 | // opacity: 0; 32 | // -webkit-transform: translate3d(0, -100%, 0); 33 | // transform: translate3d(0, -100%, 0); 34 | // } 35 | // to { 36 | // opacity: 1; 37 | // -webkit-transform: none; 38 | // transform: none; 39 | // } 40 | // } 41 | // @keyframes fadeInDown { 42 | // 0% { 43 | // opacity: 0; 44 | // -webkit-transform: translate3d(0, -100%, 0); 45 | // transform: translate3d(0, -100%, 0); 46 | // } 47 | // to { 48 | // opacity: 1; 49 | // -webkit-transform: none; 50 | // transform: none; 51 | // } 52 | // } 53 | // .fadeInDown { 54 | // -webkit-animation-name: fadeInDown; 55 | // animation-name: fadeInDown; 56 | // } 57 | 58 | // @-webkit-keyframes slideInDown { 59 | // 0% { 60 | // -webkit-transform: translate3d(0, -100%, 0); 61 | // transform: translate3d(0, -100%, 0); 62 | // visibility: visible; 63 | // } 64 | // to { 65 | // -webkit-transform: translateZ(0); 66 | // transform: translateZ(0); 67 | // } 68 | // } 69 | // @keyframes slideInDown { 70 | // 0% { 71 | // -webkit-transform: translate3d(0, -100%, 0); 72 | // transform: translate3d(0, -100%, 0); 73 | // visibility: visible; 74 | // } 75 | // to { 76 | // -webkit-transform: translateZ(0); 77 | // transform: translateZ(0); 78 | // } 79 | // } 80 | // .slideInDown { 81 | // -webkit-animation-name: slideInDown; 82 | // animation-name: slideInDown; 83 | // } -------------------------------------------------------------------------------- /rpc/client/client_master.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/devfeel/rockman/core" 5 | "github.com/devfeel/rockman/logger" 6 | "github.com/devfeel/rockman/rpc/packet" 7 | ) 8 | 9 | func (c *RpcClient) CallQueryClusterExecutors(taskId string) (error, *packet.RpcReply) { 10 | client, err := c.getConnClient() 11 | if err != nil { 12 | logger.Default().Error(err, "getConnClient error") 13 | return err, nil 14 | } 15 | var reply packet.RpcReply 16 | err = client.Call("Rpc.QueryClusterExecutors", taskId, &reply) 17 | if err != nil { 18 | return err, nil 19 | } 20 | return nil, &reply 21 | } 22 | 23 | func (c *RpcClient) CallRegisterNode(worker *core.NodeInfo) (error, *packet.RpcReply) { 24 | client, err := c.getConnClient() 25 | if err != nil { 26 | logger.Default().Error(err, "getConnClient error") 27 | return err, nil 28 | } 29 | var reply packet.RpcReply 30 | err = client.Call("Rpc.RegisterNode", worker, &reply) 31 | if err != nil { 32 | return err, nil 33 | } 34 | return nil, &reply 35 | } 36 | 37 | func (c *RpcClient) CallQueryNodes(pageInfo *core.PageInfo) (error, *packet.RpcReply) { 38 | client, err := c.getConnClient() 39 | if err != nil { 40 | logger.Default().Error(err, "getConnClient error") 41 | return err, nil 42 | } 43 | var reply packet.RpcReply 44 | err = client.Call("Rpc.QueryNodes", pageInfo, &reply) 45 | if err != nil { 46 | return err, nil 47 | } 48 | return nil, &reply 49 | } 50 | 51 | func (c *RpcClient) CallSubmitExecutor(execInfo *core.ExecutorInfo) (error, *packet.RpcReply) { 52 | client, err := c.getConnClient() 53 | if err != nil { 54 | logger.Default().Error(err, "getConnClient error") 55 | return err, nil 56 | } 57 | var reply packet.RpcReply 58 | err = client.Call("Rpc.SubmitExecutor", execInfo, &reply) 59 | if err != nil { 60 | return err, nil 61 | } 62 | return nil, &reply 63 | } 64 | 65 | func (c *RpcClient) CallSubmitStartExecutor(taskId string) (error, *packet.RpcReply) { 66 | client, err := c.getConnClient() 67 | if err != nil { 68 | logger.Default().Error(err, "getConnClient error") 69 | return err, nil 70 | } 71 | var reply packet.RpcReply 72 | err = client.Call("Rpc.SubmitStartExecutor", taskId, &reply) 73 | if err != nil { 74 | return err, nil 75 | } 76 | return nil, &reply 77 | } 78 | 79 | func (c *RpcClient) CallSubmitStopExecutor(taskId string) (error, *packet.RpcReply) { 80 | client, err := c.getConnClient() 81 | if err != nil { 82 | logger.Default().Error(err, "getConnClient error") 83 | return err, nil 84 | } 85 | var reply packet.RpcReply 86 | err = client.Call("Rpc.SubmitStopExecutor", taskId, &reply) 87 | if err != nil { 88 | return err, nil 89 | } 90 | return nil, &reply 91 | } 92 | -------------------------------------------------------------------------------- /rpc/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/devfeel/rockman/core" 5 | "github.com/devfeel/rockman/logger" 6 | "github.com/devfeel/rockman/node" 7 | "github.com/devfeel/rockman/rpc/packet" 8 | "github.com/devfeel/rockman/util/sysx" 9 | "strconv" 10 | ) 11 | 12 | type RpcHandler struct { 13 | node *node.Node 14 | } 15 | 16 | func NewRpcHandler(node *node.Node) *RpcHandler { 17 | return &RpcHandler{node: node} 18 | } 19 | 20 | // Echo 21 | func (h *RpcHandler) Echo(content string, reply *string) error { 22 | logger.Rpc().Debug("RpcServer.Echo:" + content) 23 | *reply = content 24 | return nil 25 | } 26 | 27 | // QueryResource query resource info from worker node 28 | func (h *RpcHandler) QueryResource(content string, reply *packet.RpcReply) error { 29 | if !h.node.IsWorker() { 30 | logger.Rpc().Warn("QueryResource failed: can not query resource from not worker node") 31 | *reply = packet.FailedReply(-1001, "can not query resource from not worker nodee") 32 | return nil 33 | } 34 | resource := &core.ResourceInfo{} 35 | resource.EndPoint = h.node.NodeInfo().EndPoint() 36 | resource.TaskCount = h.node.Runtime.TaskService.Count() 37 | resource.CpuRate = sysx.GetCpuUsedPercent() 38 | resource.MemoryRate = sysx.GetMemoryUsedPercent() 39 | 40 | logger.Rpc().DebugS("RpcServer.QueryResource success", *resource) 41 | *reply = packet.SuccessRpcReply(resource) 42 | return nil 43 | } 44 | 45 | // QueryExecutors return executors in runtime by taskId 46 | // if taskId is nil, return all executors 47 | func (h *RpcHandler) QueryExecutors(taskId string, reply *packet.RpcReply) error { 48 | logTitle := "RpcServer.QueryExecutors [" + taskId + "] " 49 | if !h.getNode().IsWorker() { 50 | logger.Rpc().Warn(logTitle + "unworker node can not query executor") 51 | *reply = packet.FailedReply(-1001, "unworker node can not query executor") 52 | return nil 53 | } 54 | configs := h.getNode().Runtime.QueryAllExecutorConfig() 55 | if taskId != "" { 56 | exec, isOk := configs[taskId] 57 | if !isOk { 58 | *reply = packet.FailedReply(-2001, "not exists this taskId") 59 | return nil 60 | } else { 61 | logger.Rpc().Debug(logTitle + "success") 62 | configs = make(map[string]core.TaskConfig) 63 | configs[taskId] = exec 64 | } 65 | } else { 66 | logger.Rpc().Debug(logTitle + "success, config count = " + strconv.Itoa(len(configs))) 67 | } 68 | execInfos := make(map[string]*core.ExecutorInfo) 69 | for k, v := range configs { 70 | execInfos[k] = &core.ExecutorInfo{ 71 | TaskConfig: &v, 72 | Worker: h.getNode().NodeInfo(), 73 | } 74 | } 75 | *reply = packet.SuccessRpcReply(execInfos) 76 | return nil 77 | } 78 | 79 | func (h *RpcHandler) getNode() *node.Node { 80 | return h.node 81 | } 82 | -------------------------------------------------------------------------------- /protected/model/ExecutorInfo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/devfeel/rockman/core" 7 | "github.com/devfeel/rockman/logger" 8 | "github.com/devfeel/rockman/runtime/executor" 9 | "time" 10 | ) 11 | 12 | type ( 13 | ExecutorInfo struct { 14 | ID int64 15 | TaskID string 16 | TaskType string 17 | IsRun bool 18 | DueTime int64 19 | Interval int64 20 | Express string 21 | TaskData string 22 | TargetType string 23 | TargetConfig string 24 | RealTargetConfig interface{} 25 | NodeID string 26 | DistributeType int 27 | IsSubmitToCluster bool 28 | Remark string 29 | } 30 | ExecutorRunInfo struct { 31 | LogID int64 32 | TaskID string 33 | NodeID string 34 | NodeEndPoint string 35 | LastUpdateTime time.Time 36 | CreateTime time.Time 37 | } 38 | ) 39 | 40 | func (e *ExecutorInfo) TaskConfig() *core.TaskConfig { 41 | e.InitTargetConfig() 42 | 43 | defer func() { 44 | if err := recover(); err != nil { 45 | errInfo := errors.New(fmt.Sprintln(err)) 46 | logger.Default().Error(errInfo, "ExecutorInfo.TaskConfig() throw unhandled error:"+errInfo.Error()) 47 | } 48 | }() 49 | 50 | conf := &core.TaskConfig{} 51 | conf.TaskID = e.TaskID 52 | conf.TaskType = e.TaskType 53 | conf.TargetType = e.TargetType 54 | conf.IsRun = e.IsRun 55 | conf.DueTime = e.DueTime 56 | conf.Interval = e.Interval 57 | conf.Express = e.Express 58 | conf.TaskData = e.TaskData 59 | conf.DistributeType = e.DistributeType 60 | conf.HAFlag = true 61 | if e.TargetType == executor.TargetType_Http { 62 | conf.TargetConfig = e.RealTargetConfig.(*executor.HttpConfig) 63 | } 64 | if e.TargetType == executor.TargetType_GoSo { 65 | conf.TargetConfig = e.RealTargetConfig.(*executor.GoConfig) 66 | } 67 | if e.TargetType == executor.TargetType_Shell { 68 | conf.TargetConfig = e.RealTargetConfig.(*executor.ShellConfig) 69 | } 70 | return conf 71 | } 72 | 73 | func (e *ExecutorInfo) InitTargetConfig() { 74 | if e.RealTargetConfig != nil { 75 | return 76 | } 77 | if e.TargetType == executor.TargetType_Http { 78 | conf := new(executor.HttpConfig) 79 | err := conf.LoadFromJson(e.TargetConfig) 80 | if err == nil { 81 | e.RealTargetConfig = conf 82 | } 83 | } 84 | if e.TargetType == executor.TargetType_GoSo { 85 | conf := new(executor.GoConfig) 86 | err := conf.LoadFromJson(e.TargetConfig) 87 | if err == nil { 88 | e.RealTargetConfig = conf 89 | } 90 | } 91 | if e.TargetType == executor.TargetType_Shell { 92 | conf := new(executor.ShellConfig) 93 | err := conf.LoadFromJson(e.TargetConfig) 94 | if err == nil { 95 | e.RealTargetConfig = conf 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /webui/webapp/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, // 输出路径 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src') 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /webui/webapp/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { 15 | target: 'http://localhost:8080', // 目标接口域名 16 | changeOrigin: true, // 是否跨域 17 | pathRewrite: { 18 | '^/api': '/api' // 重写接口 19 | } 20 | } 21 | }, 22 | 23 | // Various Dev Server settings 24 | host: 'localhost', // can be overwritten by process.env.HOST 25 | port: 9091, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 26 | autoOpenBrowser: false, 27 | errorOverlay: true, 28 | notifyOnErrors: true, 29 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 30 | 31 | // Use Eslint Loader? 32 | // If true, your code will be linted during bundling and 33 | // linting errors and warnings will be shown in the console. 34 | useEslint: true, 35 | // If true, eslint errors and warnings will also be shown in the error overlay 36 | // in the browser. 37 | showEslintErrorsInOverlay: false, 38 | 39 | /** 40 | * Source Maps 41 | */ 42 | 43 | // https://webpack.js.org/configuration/devtool/#development 44 | devtool: 'cheap-module-eval-source-map', 45 | 46 | // If you have problems debugging vue-files in devtools, 47 | // set this to false - it *may* help 48 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 49 | cacheBusting: true, 50 | 51 | cssSourceMap: true 52 | }, 53 | 54 | build: { 55 | // Template for index.html 56 | index: path.resolve(__dirname, '../../../webapp/index.html'), 57 | 58 | // Paths 59 | assetsRoot: path.resolve(__dirname, '../../../webapp'), 60 | assetsSubDirectory: 'static', 61 | assetsPublicPath: '/', 62 | /** 63 | * Source Maps 64 | */ 65 | 66 | productionSourceMap: true, 67 | // https://webpack.js.org/configuration/devtool/#production 68 | devtool: '#source-map', 69 | 70 | // Gzip off by default as many popular static hosts such as 71 | // Surge or Netlify already gzip all static assets for you. 72 | // Before setting to `true`, make sure to: 73 | // npm install --save-dev compression-webpack-plugin 74 | productionGzip: false, 75 | productionGzipExtensions: ['js', 'css'], 76 | 77 | // Run the build command with an extra argument to 78 | // View the bundle analyzer report after build finishes: 79 | // `npm run build --report` 80 | // Set to `true` or `false` to always turn it on or off 81 | bundleAnalyzerReport: process.env.npm_config_report 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /rpc/server.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "errors" 7 | "github.com/devfeel/rockman/config" 8 | "github.com/devfeel/rockman/logger" 9 | "github.com/devfeel/rockman/node" 10 | "github.com/devfeel/rockman/rpc/handler" 11 | "io/ioutil" 12 | "net" 13 | "net/rpc" 14 | "net/rpc/jsonrpc" 15 | ) 16 | 17 | const ( 18 | DefaultHost = "127.0.0.1" 19 | DefaultRpcPort = "2398" //2398 = 1983+0415 my birthday! 20 | ) 21 | 22 | type RpcServer struct { 23 | config *config.Profile 24 | RpcHost string 25 | RpcPort string 26 | RpcProtocol string 27 | Node *node.Node 28 | } 29 | 30 | func NewRpcServer(profile *config.Profile, node *node.Node) *RpcServer { 31 | s := new(RpcServer) 32 | s.config = profile 33 | s.Node = node 34 | s.RpcHost = profile.Rpc.RpcHost 35 | s.RpcPort = profile.Rpc.RpcPort 36 | s.RpcProtocol = profile.Rpc.RpcProtocol 37 | logger.Default().Debug("RpcServer init success.") 38 | return s 39 | } 40 | 41 | func (s *RpcServer) Listen() error { 42 | var listener net.Listener 43 | var err error 44 | 45 | if s.config.Rpc.EnableTls { 46 | tlsConfig, err := s.createTlsConfig() 47 | if err != nil { 48 | logger.Default().Error(err, "RPCServer createTlsConfig error") 49 | return err 50 | } 51 | listener, err = tls.Listen("tcp", s.RpcHost+":"+s.RpcPort, tlsConfig) 52 | } else { 53 | listener, err = net.Listen("tcp", s.RpcHost+":"+s.RpcPort) 54 | } 55 | 56 | if err != nil { 57 | return err 58 | } 59 | defer listener.Close() 60 | 61 | srv := rpc.NewServer() 62 | if err := srv.RegisterName("Rpc", handler.NewRpcHandler(s.Node)); err != nil { 63 | logger.Default().Error(err, "RPCServer lis.RegisterName error") 64 | return err 65 | } 66 | 67 | logger.Default().DebugF("RPCServer begin listen %s", listener.Addr()) 68 | 69 | for { 70 | conn, err := listener.Accept() 71 | if err != nil { 72 | logger.Default().Error(err, "lis.Accept() error") 73 | continue 74 | } 75 | go srv.ServeCodec(jsonrpc.NewServerCodec(conn)) 76 | } 77 | } 78 | 79 | func (s *RpcServer) createTlsConfig() (*tls.Config, error) { 80 | serverCertFile := s.config.Rpc.ServerCertFile 81 | serverKeyFile := s.config.Rpc.ServerKeyFile 82 | clientCertFile := s.config.Rpc.ClientCertFile 83 | cert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile) 84 | if err != nil { 85 | return nil, err 86 | } 87 | certBytes, err := ioutil.ReadFile(clientCertFile) 88 | if err != nil { 89 | return nil, err 90 | } 91 | clientCertPool := x509.NewCertPool() 92 | ok := clientCertPool.AppendCertsFromPEM(certBytes) 93 | if !ok { 94 | err = errors.New("AppendCertsFromPEM failed") 95 | return nil, err 96 | } 97 | tlsConfig := &tls.Config{ 98 | Certificates: []tls.Certificate{cert}, 99 | ClientAuth: tls.RequireAndVerifyClientCert, 100 | ClientCAs: clientCertPool, 101 | } 102 | return tlsConfig, nil 103 | } 104 | -------------------------------------------------------------------------------- /webui/controllers/LogController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/devfeel/dotweb" 5 | "github.com/devfeel/rockman/protected/service" 6 | _const "github.com/devfeel/rockman/webui/const" 7 | "github.com/devfeel/rockman/webui/contract" 8 | ) 9 | 10 | type LogController struct { 11 | } 12 | 13 | func NewLogController() *LogController { 14 | return &LogController{} 15 | } 16 | 17 | // ShowExecLogs 18 | func (c *LogController) ShowTaskExecLogs(ctx dotweb.Context) error { 19 | qr := new(contract.TaskExecLogQR) 20 | err := ctx.Bind(qr) 21 | if err != nil { 22 | return ctx.WriteJson(FailedResponse(-1001, "parameter bind failed: "+err.Error())) 23 | } 24 | if qr.PageSize <= 0 { 25 | qr.PageSize = _const.DefaultPageSize 26 | } 27 | logService := service.NewLogService() 28 | result, err := logService.QueryExecLogs(qr.TaskID, &qr.PageRequest) 29 | if err != nil { 30 | return ctx.WriteJson(FailedResponse(-2001, "Query error: "+err.Error())) 31 | } 32 | return ctx.WriteJson(SuccessResponse(result)) 33 | } 34 | 35 | // ShowStateLog 36 | func (c *LogController) ShowTaskStateLog(ctx dotweb.Context) error { 37 | qr := new(contract.TaskStateLogQR) 38 | err := ctx.Bind(qr) 39 | if err != nil { 40 | return ctx.WriteJson(FailedResponse(-1001, "parameter bind failed: "+err.Error())) 41 | } 42 | 43 | if qr.PageSize <= 0 { 44 | qr.PageSize = _const.DefaultPageSize 45 | } 46 | 47 | logService := service.NewLogService() 48 | result, err := logService.QueryStateLog(qr.TaskID, &qr.PageRequest) 49 | if err != nil { 50 | return ctx.WriteJson(FailedResponse(-2001, "Query error: "+err.Error())) 51 | } 52 | return ctx.WriteJson(SuccessResponse(result)) 53 | } 54 | 55 | // ShowStateLog 56 | func (c *LogController) ShowTaskSubmitLog(ctx dotweb.Context) error { 57 | qr := new(contract.TaskSubmitLogQR) 58 | err := ctx.Bind(qr) 59 | if err != nil { 60 | return ctx.WriteJson(FailedResponse(-1001, "parameter bind failed: "+err.Error())) 61 | } 62 | 63 | if qr.PageSize <= 0 { 64 | qr.PageSize = _const.DefaultPageSize 65 | } 66 | 67 | logService := service.NewLogService() 68 | result, err := logService.QueryTaskSubmitLog(qr.TaskID, &qr.PageRequest) 69 | if err != nil { 70 | return ctx.WriteJson(FailedResponse(-2001, "Query error: "+err.Error())) 71 | } 72 | return ctx.WriteJson(SuccessResponse(result)) 73 | } 74 | 75 | // ShowNodeTraceLog 76 | func (c *LogController) ShowNodeTraceLog(ctx dotweb.Context) error { 77 | qr := new(contract.NodeTraceLogQR) 78 | err := ctx.Bind(qr) 79 | if err != nil { 80 | return ctx.WriteJson(FailedResponse(-1001, "parameter bind failed: "+err.Error())) 81 | } 82 | 83 | if qr.PageSize <= 0 { 84 | qr.PageSize = _const.DefaultPageSize 85 | } 86 | 87 | logService := service.NewLogService() 88 | result, err := logService.QueryNodeTraceLog(qr.NodeID, &qr.PageRequest) 89 | if err != nil { 90 | return ctx.WriteJson(FailedResponse(-2001, "Query error: "+err.Error())) 91 | } 92 | return ctx.WriteJson(SuccessResponse(result)) 93 | } 94 | -------------------------------------------------------------------------------- /webui/webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rockman", 3 | "version": "1.0.0", 4 | "description": "rockman管理系统", 5 | "author": "billy", 6 | "private": false, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "element-ui": "^2.4.11", 16 | "echarts": "^4.2.1", 17 | "mockjs": "^1.0.1-beta3", 18 | "moment": "^2.24.0", 19 | "vue": "^2.5.16", 20 | "vue-bus": "^1.2.0", 21 | "vue-router": "^3.0.1", 22 | "vuex": "^3.1.0" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^7.1.2", 26 | "babel": "^6.23.0", 27 | "babel-core": "^6.22.1", 28 | "babel-eslint": "^8.2.1", 29 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 30 | "babel-loader": "^7.1.1", 31 | "babel-plugin-syntax-jsx": "^6.18.0", 32 | "babel-plugin-transform-runtime": "^6.22.0", 33 | "babel-plugin-transform-vue-jsx": "^3.5.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "chalk": "^2.0.1", 37 | "copy-webpack-plugin": "^4.0.1", 38 | "css-loader": "^0.28.0", 39 | "eslint": "^4.19.1", 40 | "eslint-config-standard": "^10.2.1", 41 | "eslint-friendly-formatter": "^4.0.0", 42 | "eslint-loader": "^2.0.0", 43 | "eslint-plugin-html": "^4.0.3", 44 | "eslint-plugin-import": "^2.7.0", 45 | "eslint-plugin-node": "^5.2.0", 46 | "eslint-plugin-promise": "^3.4.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "eslint-plugin-vue": "^4.0.0", 49 | "extract-text-webpack-plugin": "^3.0.0", 50 | "file-loader": "^1.1.4", 51 | "friendly-errors-webpack-plugin": "^1.6.1", 52 | "html-loader": "^0.3.0", 53 | "html-webpack-plugin": "^2.30.1", 54 | "iview-loader": "^1.2.1", 55 | "less": "^2.7.1", 56 | "less-loader": "^2.2.3", 57 | "node-notifier": "^5.1.2", 58 | "node-sass": "^4.13.1", 59 | "optimize-css-assets-webpack-plugin": "^3.2.0", 60 | "ora": "^1.2.0", 61 | "portfinder": "^1.0.13", 62 | "postcss-import": "^11.0.0", 63 | "postcss-loader": "^2.0.8", 64 | "postcss-url": "^7.2.1", 65 | "rimraf": "^2.6.0", 66 | "sass-loader": "^7.1.0", 67 | "semver": "^5.3.0", 68 | "shelljs": "^0.7.6", 69 | "style-loader": "^0.23.1", 70 | "uglifyjs-webpack-plugin": "^1.1.1", 71 | "url-loader": "^0.5.8", 72 | "vue-codemirror": "^4.0.6", 73 | "vue-hot-reload-api": "^1.3.3", 74 | "vue-html-loader": "^1.2.3", 75 | "vue-loader": "^13.3.0", 76 | "vue-style-loader": "^3.0.1", 77 | "vue-template-compiler": "^2.5.2", 78 | "webpack": "^3.12.0", 79 | "webpack-bundle-analyzer": "^3.3.2", 80 | "webpack-cli": "^3.0.0", 81 | "webpack-dev-server": "^2.9.1", 82 | "webpack-merge": "^4.1.0" 83 | }, 84 | "engines": { 85 | "node": ">= 6.0.0", 86 | "npm": ">= 3.0.0" 87 | }, 88 | "browserslist": [ 89 | "> 1%", 90 | "last 2 versions", 91 | "not ie <= 8" 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /webui/webapp/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /webui/webapp/src/views/task/components/stateLogs.vue: -------------------------------------------------------------------------------- 1 | 26 | 87 | 93 | -------------------------------------------------------------------------------- /runtime/executor/goexec.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "errors" 5 | "github.com/devfeel/dottask" 6 | "github.com/devfeel/mapper" 7 | "github.com/devfeel/rockman/core" 8 | "github.com/devfeel/rockman/logger" 9 | _file "github.com/devfeel/rockman/util/file" 10 | _json "github.com/devfeel/rockman/util/json" 11 | "plugin" 12 | ) 13 | 14 | /* 15 | build script on linux: go build --buildmode=plugin -o plugin.so plugin.go 16 | */ 17 | 18 | const GoFilePath = "plugins/" 19 | 20 | var ( 21 | ErrorGoSoFileNotInSpecifyPath = errors.New("go.so file not in specify path") 22 | ) 23 | 24 | type ( 25 | GoConfig struct { 26 | FileName string 27 | } 28 | 29 | GoExecutor struct { 30 | baseExecutor 31 | goConfig *GoConfig 32 | } 33 | ) 34 | 35 | func NewDebugGoExecutor(taskID string) Executor { 36 | conf := &core.TaskConfig{} 37 | conf.TaskID = taskID + "-debug" 38 | conf.TaskType = "cron" 39 | conf.IsRun = true 40 | conf.DueTime = 0 41 | conf.Interval = 0 42 | conf.Express = "0 * * * * *" 43 | conf.TaskData = "go.so" 44 | conf.TargetType = TargetType_GoSo 45 | conf.TargetConfig = &GoConfig{ 46 | FileName: "demo.so", 47 | } 48 | exec, _ := NewGoExecutor(conf) 49 | return exec 50 | } 51 | 52 | func NewGoExecutor(conf *core.TaskConfig) (*GoExecutor, error) { 53 | lt := "NewGoExecutor[" + conf.TaskID + "] " 54 | exec := new(GoExecutor) 55 | exec.TaskConfig = conf 56 | exec.TaskConfig.Handler = exec.Exec 57 | exec.goConfig = new(GoConfig) 58 | err := mapper.MapperMap(exec.TaskConfig.TargetConfig.(map[string]interface{}), exec.goConfig) 59 | if err != nil { 60 | logger.Runtime().Error(err, lt+"convert config error") 61 | return nil, err 62 | } 63 | 64 | exec.goConfig.FileName = GoFilePath + exec.goConfig.FileName 65 | if !_file.ExistsInPath(GoFilePath, exec.goConfig.FileName) { 66 | logger.Runtime().Debug(lt + "error: " + ErrorGoSoFileNotInSpecifyPath.Error()) 67 | return nil, ErrorGoSoFileNotInSpecifyPath 68 | } 69 | return exec, nil 70 | } 71 | 72 | func (exec *GoExecutor) Exec(ctx *task.TaskContext) error { 73 | logTitle := "GoExecutor [" + exec.GetTaskID() + "] [" + exec.goConfig.FileName + "] " 74 | p, err := plugin.Open(exec.goConfig.FileName) 75 | if err != nil { 76 | logger.Runtime().Error(err, logTitle+"open plugin error: "+err.Error()) 77 | ctx.Error = err 78 | return nil 79 | } 80 | s, err := p.Lookup("Exec") 81 | if err != nil { 82 | logger.Runtime().Error(err, logTitle+"lookup Exec error: "+err.Error()) 83 | ctx.Error = err 84 | return nil 85 | } 86 | if execFunc, ok := s.(func(ctx *task.TaskContext) error); ok { 87 | err := execFunc(ctx) 88 | if err != nil { 89 | logger.Runtime().Error(err, logTitle+"exec err:"+err.Error()) 90 | } else { 91 | logger.Runtime().DebugS(logTitle + "exec success") 92 | } 93 | ctx.Error = err 94 | return nil 95 | } else { 96 | err := errors.New("not match Exec function") 97 | logger.Runtime().Error(err, logTitle+"not match Exec function") 98 | ctx.Error = err 99 | return nil 100 | } 101 | } 102 | 103 | func (c *GoConfig) LoadFromJson(json string) error { 104 | return _json.Unmarshal(json, c) 105 | } 106 | -------------------------------------------------------------------------------- /util/file/path.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Based on the path package, Copyright 2009 The Go Authors. 3 | // Use of this source code is governed by a BSD-style license that can be found 4 | // in the LICENSE file. 5 | 6 | package _file 7 | 8 | // CleanPath is the URL version of path.Clean, it returns a canonical URL path 9 | // for p, eliminating . and .. elements. 10 | // 11 | // The following rules are applied iteratively until no further processing can 12 | // be done: 13 | // 1. Replace multiple slashes with a single slash. 14 | // 2. Eliminate each . path name element (the current directory). 15 | // 3. Eliminate each inner .. path name element (the parent directory) 16 | // along with the non-.. element that precedes it. 17 | // 4. Eliminate .. elements that begin a rooted path: 18 | // that is, replace "/.." by "/" at the beginning of a path. 19 | // 20 | // If the result of this process is an empty string, "/" is returned 21 | func CleanPath(p string) string { 22 | // Turn empty string into "/" 23 | if p == "" { 24 | return "/" 25 | } 26 | 27 | n := len(p) 28 | var buf []byte 29 | 30 | // Invariants: 31 | // reading from path; r is index of next byte to process. 32 | // writing to buf; w is index of next byte to write. 33 | 34 | // path must start with '/' 35 | r := 1 36 | w := 1 37 | 38 | if p[0] != '/' { 39 | r = 0 40 | buf = make([]byte, n+1) 41 | buf[0] = '/' 42 | } 43 | 44 | trailing := n > 2 && p[n-1] == '/' 45 | 46 | // A bit more clunky without a 'lazybuf' like the path package, but the loop 47 | // gets completely inlined (bufApp). So in contrast to the path package this 48 | // loop has no expensive function calls (except 1x make) 49 | 50 | for r < n { 51 | switch { 52 | case p[r] == '/': 53 | // empty path element, trailing slash is added after the end 54 | r++ 55 | 56 | case p[r] == '.' && r+1 == n: 57 | trailing = true 58 | r++ 59 | 60 | case p[r] == '.' && p[r+1] == '/': 61 | // . element 62 | r++ 63 | 64 | case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 65 | // .. element: remove to last / 66 | r += 2 67 | 68 | if w > 1 { 69 | // can backtrack 70 | w-- 71 | 72 | if buf == nil { 73 | for w > 1 && p[w] != '/' { 74 | w-- 75 | } 76 | } else { 77 | for w > 1 && buf[w] != '/' { 78 | w-- 79 | } 80 | } 81 | } 82 | 83 | default: 84 | // real path element. 85 | // add slash if needed 86 | if w > 1 { 87 | bufApp(&buf, p, w, '/') 88 | w++ 89 | } 90 | 91 | // copy element 92 | for r < n && p[r] != '/' { 93 | bufApp(&buf, p, w, p[r]) 94 | w++ 95 | r++ 96 | } 97 | } 98 | } 99 | 100 | // re-append trailing slash 101 | if trailing && w > 1 { 102 | bufApp(&buf, p, w, '/') 103 | w++ 104 | } 105 | 106 | if buf == nil { 107 | return p[:w] 108 | } 109 | return string(buf[:w]) 110 | } 111 | 112 | // internal helper to lazily create a buffer if necessary 113 | func bufApp(buf *[]byte, s string, w int, c byte) { 114 | if *buf == nil { 115 | if s[w] == c { 116 | return 117 | } 118 | 119 | *buf = make([]byte, len(s)) 120 | copy(*buf, s[:w]) 121 | } 122 | (*buf)[w] = c 123 | } 124 | -------------------------------------------------------------------------------- /webui/webapp/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /rpc/handler/handler_worker.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/devfeel/rockman/core" 5 | "github.com/devfeel/rockman/logger" 6 | "github.com/devfeel/rockman/rpc/packet" 7 | ) 8 | 9 | // RegisterExecutor register executor to runtime in worker node 10 | func (h *RpcHandler) RegisterExecutor(config *core.TaskConfig, reply *packet.RpcReply) error { 11 | lt := "RpcServer.RegisterExecutor: " 12 | if !h.getNode().IsWorker() { 13 | logger.Rpc().Warn("unworker node can not register executor") 14 | *reply = packet.FailedReply(-1001, "unworker node can not register executor") 15 | return nil 16 | } 17 | result := h.getNode().RegisterExecutor(config) 18 | if result.Error != nil { 19 | logger.Rpc().Warn(lt + "error:" + result.Error.Error()) 20 | *reply = packet.FailedReply(-9001, "error:"+result.Error.Error()) 21 | return nil 22 | } 23 | if !result.IsSuccess() { 24 | logger.Rpc().Warn(lt + "failed:" + result.Message()) 25 | *reply = packet.FailedReply(-9001, "failed:"+result.Message()) 26 | return nil 27 | } 28 | logger.Rpc().DebugS(lt+"success", config) 29 | *reply = packet.SuccessRpcReply(h.getNode().Runtime.Executors) 30 | return nil 31 | } 32 | 33 | // StartExecutor start executor by taskId 34 | func (h *RpcHandler) StartExecutor(taskId string, reply *packet.RpcReply) error { 35 | logTitle := "RpcServer.StartExecutor[" + taskId + "] " 36 | if !h.getNode().IsWorker() { 37 | logger.Rpc().Warn("unworker node can not start executor") 38 | *reply = packet.FailedReply(-1001, "unworker node can not start executor") 39 | return nil 40 | } 41 | err := h.getNode().Runtime.StartExecutor(taskId) 42 | if err != nil { 43 | logger.Rpc().Debug(logTitle + "error:" + err.Error()) 44 | logger.Rpc().Error(err, logTitle+"error") 45 | *reply = packet.FailedReply(-2001, err.Error()) 46 | } 47 | logger.Rpc().Debug(logTitle + "success") 48 | *reply = packet.SuccessRpcReply(nil) 49 | return nil 50 | } 51 | 52 | // StopExecutor stop executor by taskId 53 | func (h *RpcHandler) StopExecutor(taskId string, reply *packet.RpcReply) error { 54 | logTitle := "RpcServer.StopExecutor[" + taskId + "] " 55 | if !h.getNode().IsWorker() { 56 | logger.Rpc().Warn(logTitle + "unworker node can not stop executor") 57 | *reply = packet.FailedReply(-1001, "unworker node can not stop executor") 58 | return nil 59 | } 60 | 61 | err := h.getNode().Runtime.StopExecutor(taskId) 62 | if err != nil { 63 | logger.Rpc().Debug(logTitle + "error:" + err.Error()) 64 | logger.Rpc().Error(err, logTitle+"error") 65 | *reply = packet.FailedReply(-2001, logTitle+"error:"+err.Error()) 66 | } 67 | logger.Rpc().Debug(logTitle + "success") 68 | *reply = packet.SuccessRpcReply(nil) 69 | return nil 70 | } 71 | 72 | // RemoveExecutor remove executor by taskId 73 | // if task is running, auto stop it first 74 | func (h *RpcHandler) RemoveExecutor(taskId string, reply *packet.RpcReply) error { 75 | logTitle := "RpcServer.RemoveExecutor[" + taskId + "] " 76 | if !h.getNode().IsWorker() { 77 | logger.Rpc().Warn(logTitle + "unworker node can not remove executor") 78 | *reply = packet.FailedReply(-1001, "unworker node can not remove executor") 79 | return nil 80 | } 81 | err := h.getNode().Runtime.RemoveExecutor(taskId) 82 | if err != nil { 83 | logger.Rpc().Debug(logTitle + "error:" + err.Error()) 84 | logger.Rpc().Error(err, logTitle+"error") 85 | *reply = packet.FailedReply(-2001, logTitle+"error:"+err.Error()) 86 | } 87 | logger.Rpc().Debug(logTitle + "success") 88 | *reply = packet.SuccessRpcReply(h.getNode().Runtime.Executors) 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /webui/webapp/src/views/task/components/submitLogs.vue: -------------------------------------------------------------------------------- 1 | 32 | 93 | 99 | -------------------------------------------------------------------------------- /webui/webapp/src/views/task/components/glue.vue: -------------------------------------------------------------------------------- 1 | 22 | 78 | 126 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "github.com/devfeel/rockman/logger" 5 | "github.com/devfeel/rockman/registry/consul" 6 | "github.com/devfeel/rockman/util/netx" 7 | "github.com/hashicorp/consul/api" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | const defaultRetryPingLimit = 3 13 | 14 | type ( 15 | Registry struct { 16 | ServerUrl string 17 | *consul.ConsulClient 18 | OnServerOnline NetChangeHandle 19 | OnServerOffline NetChangeHandle 20 | isServerOnline bool 21 | isStart bool 22 | } 23 | 24 | NetChangeHandle func() 25 | ) 26 | 27 | func NewRegistry(regServer string) (*Registry, error) { 28 | reg := &Registry{} 29 | reg.ServerUrl = regServer 30 | reg.isServerOnline = true 31 | regClient, err := consul.NewConsulClient(regServer) 32 | if err != nil { 33 | logger.Node().Debug("Registry init error: " + err.Error()) 34 | logger.Node().Error(err, "Registry init error") 35 | return nil, err 36 | } 37 | logger.Node().Debug("Registry init success.") 38 | reg.ConsulClient = regClient 39 | return reg, nil 40 | } 41 | 42 | func (r *Registry) Start() error { 43 | if r.isStart { 44 | return nil 45 | } 46 | logger.Default().Debug("Registry start...") 47 | r.isStart = true 48 | r.watchPingRegistry() 49 | return nil 50 | } 51 | 52 | func (r *Registry) Stop() error { 53 | logger.Default().Debug("Registry Stop...") 54 | r.isStart = false 55 | return nil 56 | } 57 | 58 | // CreateLocker create locker to registry with key/value 59 | func (r *Registry) CreateLocker(key string, value string, ttl string) (*consul.Locker, error) { 60 | if ttl == "" { 61 | ttl = "10s" 62 | } 63 | opts := &api.LockOptions{ 64 | Key: key, 65 | Value: []byte(value), 66 | SessionOpts: &api.SessionEntry{ 67 | Name: key, 68 | TTL: ttl, 69 | Behavior: "delete", 70 | }, 71 | } 72 | locker, err := r.CreateLockerOpts(opts) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return locker, nil 77 | } 78 | 79 | // watchPingRegistry 80 | // check connect registry server 81 | func (r *Registry) watchPingRegistry() { 82 | lt := "Registry.watchPingRegistry " 83 | logger.Default().Debug(lt + "running...") 84 | 85 | doQuery := func() bool { 86 | // check connect registry server 87 | result := netx.CheckTcpConnect(r.ServerUrl) 88 | return result 89 | } 90 | 91 | go func() { 92 | var retryCount int 93 | limit := defaultRetryPingLimit 94 | for { 95 | if !r.isStart { 96 | logger.Default().DebugS(lt + "registry is not start, now stop watch.") 97 | return 98 | } 99 | time.Sleep(time.Second * 10) 100 | result := doQuery() 101 | if !result { 102 | if !r.isServerOnline { 103 | logger.Default().DebugS(lt + "ping registry failed.") 104 | continue 105 | } else { 106 | logger.Default().DebugS(lt + "ping registry failed[" + strconv.Itoa(retryCount) + "].") 107 | retryCount += 1 108 | } 109 | if retryCount > limit { 110 | logger.Default().DebugS(lt + "retry count more than " + strconv.Itoa(limit) + ", now is confirm unable to connect registry.") 111 | r.isServerOnline = false 112 | if r.OnServerOffline != nil { 113 | r.OnServerOffline() 114 | } 115 | } 116 | } else { 117 | if !r.isServerOnline { 118 | logger.Default().DebugS(lt + "ping registry success, now change server online.") 119 | r.isServerOnline = true 120 | if r.OnServerOnline != nil { 121 | r.OnServerOnline() 122 | } 123 | } 124 | retryCount = 0 125 | } 126 | } 127 | }() 128 | } 129 | -------------------------------------------------------------------------------- /webui/webapp/src/views/task/logdetail.vue: -------------------------------------------------------------------------------- 1 | 22 | 82 | 106 | -------------------------------------------------------------------------------- /webui/webapp/src/api/mock.js: -------------------------------------------------------------------------------- 1 | import GLOBAL from '../common/global.js' 2 | 3 | const Mock = require('mockjs') 4 | 5 | Mock.mock(GLOBAL.HOME + '/user/login', /post|get/i, function (options) { 6 | debugger 7 | let data = JSON.parse(options.body); 8 | // 模拟登录 9 | if (data.token === '123456') { 10 | return { 11 | Code: 200, 12 | Msg: '登录成功', 13 | Data: null 14 | } 15 | } else { 16 | return { 17 | Code: 400, 18 | Data: null, 19 | Msg: '口令错误!' 20 | } 21 | } 22 | }); 23 | 24 | Mock.mock(GLOBAL.HOME + '/getUserInfo', /post|get/i, function (options) { 25 | let data = JSON.parse(options.body); 26 | // 模拟登录成功与失败 27 | if (data.username === 'admin') { 28 | return { 29 | state: 'success', 30 | 'name': 'admin', 31 | 'id': '1' 32 | } 33 | } else if (data.username === 'oyyl') { 34 | return { 35 | state: 'success', 36 | 'name': '-D调定义之崽崽', 37 | 'id': '2', 38 | theme: 'dark' 39 | } 40 | } else { 41 | return { 42 | state: 'error', 43 | message: '用户名/密码错误' 44 | } 45 | } 46 | }); 47 | 48 | Mock.mock(GLOBAL.HOME + '/table/list', /post|get/i, function (options) { 49 | return [{ 50 | id: '1', 51 | date: '2016-05-03', 52 | name: '王小虎', 53 | address: '上海市普陀区金沙江路 1518 弄' 54 | }, { 55 | id: '2', 56 | date: '2016-05-02', 57 | name: '王小虎2', 58 | address: '上海市普陀区金沙江路 1518 弄' 59 | }, { 60 | id: '3', 61 | date: '2016-05-04', 62 | name: '王小虎3', 63 | address: '上海市普陀区金沙江路 1518 弄' 64 | }, { 65 | id: '4', 66 | date: '2016-05-01', 67 | name: '王小虎4', 68 | address: '上海市普陀区金沙江路 1518 弄' 69 | }, { 70 | id: '5', 71 | date: '2016-05-08', 72 | name: '王小虎5', 73 | address: '上海市普陀区金沙江路 1518 弄' 74 | }, { 75 | id: '6', 76 | date: '2016-05-06', 77 | name: '王小虎6', 78 | address: '上海市普陀区金沙江路 1518 弄' 79 | }, { 80 | id: '7', 81 | date: '2016-05-07', 82 | name: '王小虎7', 83 | address: '上海市普陀区金沙江路 1518 弄' 84 | }] 85 | }); 86 | 87 | Mock.mock(GLOBAL.HOME + '/tree/first', /post|get/i, function (options) { 88 | return [{ 89 | id: '1', 90 | name: '部门1' 91 | }, { 92 | id: '2', 93 | name: '部门2' 94 | }] 95 | }); 96 | 97 | Mock.mock(GLOBAL.HOME + '/tree/list', /post|get/i, function (options) { 98 | let data = JSON.parse(options.body); 99 | if (data.id === 1) { 100 | return [{ 101 | id: '11', 102 | name: '部门11', 103 | isLeaf: false 104 | }, { 105 | id: '21', 106 | name: '部门21', 107 | isLeaf: false 108 | }] 109 | } else { 110 | return [{ 111 | id: '3', 112 | name: '李某某', 113 | isLeaf: true 114 | }, { 115 | id: '4', 116 | name: '杨某某', 117 | isLeaf: true 118 | }] 119 | } 120 | }); 121 | 122 | Mock.mock(GLOBAL.HOME + '/tree/once', /post|get/i, function (options) { 123 | return [{ 124 | id: 1, 125 | label: '一级 1', 126 | children: [{ 127 | id: 4, 128 | label: '二级 1-1', 129 | children: [{ 130 | id: 9, 131 | label: '三级 1-1-1' 132 | }, { 133 | id: 10, 134 | label: '三级 1-1-2' 135 | }] 136 | }] 137 | }, { 138 | id: 2, 139 | label: '一级 2', 140 | children: [{ 141 | id: 5, 142 | label: '二级 2-1' 143 | }, { 144 | id: 6, 145 | label: '二级 2-2' 146 | }] 147 | }, { 148 | id: 3, 149 | label: '一级 3', 150 | children: [{ 151 | id: 7, 152 | label: '二级 3-1' 153 | }, { 154 | id: 8, 155 | label: '二级 3-2' 156 | }] 157 | }] 158 | }); 159 | -------------------------------------------------------------------------------- /webui/webapp/src/views/node/tableFormsViewGrid.js: -------------------------------------------------------------------------------- 1 | var viewGridOptions = { // 此处的权限是使用的当前页面的权限,而不是App_Transaction表的权限 2 | table: { 3 | key: 'Id', 4 | footer: 'Foots', 5 | cnName: '节点列表', 6 | name: 'App_Transaction', 7 | url: '/App_Transaction/', 8 | sortName: 'Id' 9 | }, 10 | extend: { 11 | text: '', 12 | buttons: { // 扩展按钮 13 | view: [] 14 | }, 15 | methods: {// 事件扩展 16 | onInit() { 17 | this.tableMaxHeight = 500; 18 | }, 19 | searchAfter(result) { // 查询ViewGird表数据后param查询参数,result回返查询的结果 20 | console.log({ title: this.table.cnName + ',查询结果', desc: '返回的对象:' + JSON.stringify(result) }); 21 | return true; 22 | } 23 | } 24 | }, 25 | editFormFileds: {'Name': '', 'TransactionType': '', 'CowType': '', PhoneNo: '', Describe: ''}, 26 | editFormOptions: [[{'title': '姓名', 'required': true, 'field': 'Name'}], 27 | [{'dataKey': 'cq', 'title': '是否买入', 'field': 'TransactionType', 'type': 'select'}], 28 | [{'dataKey': 'nav', 'title': '购买类型', 'field': 'CowType', 'type': 'select'}], 29 | [{'type': 'phone', 'title': '电话', 'field': 'PhoneNo', 'required': true}], 30 | [{'type': 'textarea', 'title': '描述', 'field': 'Describe', 'required': true}]], 31 | searchFormFileds: {'CowType': '', 'Creator': '', 'CreateDate': ''}, 32 | searchFormOptions: [[{'dataKey': 'nav', 'title': '购买类型', 'field': 'CowType', 'type': 'dropList'}, {'title': '提交人', 'field': 'Creator'}, {'title': '提交时间', 'field': 'CreateDate', 'type': 'datetime'}]], 33 | columns: [{field: 'Id', title: '主键ID', type: 'int', width: 90, hidden: true, readonly: true, require: true, align: 'left'}, 34 | {field: 'Name', title: '姓名', type: 'string', width: 120, require: true, align: 'left', sortable: true}, 35 | {field: 'PhoneNo', title: '电话', type: 'string', link: true, width: 150, require: true, align: 'left'}, 36 | {field: 'Quantity', title: '数量', type: 'int', width: 90, require: true, align: 'left'}, 37 | {field: 'TransactionType', title: '是否买入', type: 'int', bind: { key: 'cq', data: []}, width: 120, align: 'left'}, 38 | {field: 'CowType', title: '购买类型', type: 'string', bind: { key: 'nav', data: []}, width: 90, align: 'left'}, 39 | {field: 'Describe', title: '描述', type: 'string', width: 190, require: true, align: 'left'}, 40 | {field: 'Enable', title: '是否启用', type: 'byte', width: 90, hidden: true, align: 'left'}, 41 | {field: 'CreateID', title: '创建人Id', type: 'int', width: 90, hidden: true, align: 'left'}, 42 | {field: 'Creator', title: '提交人', type: 'string', width: 130, align: 'left'}, 43 | {field: 'CreateDate', title: '提交时间', type: 'datetime', width: 150, align: 'left', sortable: true}, 44 | {field: 'Modifier', title: '修改人', type: 'string', width: 130, hidden: true, align: 'left'}, 45 | {field: 'ModifyDate', title: '修改时间', type: 'datetime', width: 150, hidden: true, align: 'left', sortable: true}], 46 | detail: { 47 | cnName: '', 48 | columns: [], 49 | sortName: '', 50 | key: '' 51 | } 52 | }; 53 | export default viewGridOptions; 54 | -------------------------------------------------------------------------------- /webui/server.go: -------------------------------------------------------------------------------- 1 | package webui 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/devfeel/dotweb" 7 | "github.com/devfeel/middleware/cors" 8 | "github.com/devfeel/rockman/logger" 9 | "github.com/devfeel/rockman/node" 10 | _const "github.com/devfeel/rockman/webui/const" 11 | "github.com/devfeel/rockman/webui/controllers" 12 | ) 13 | 14 | type WebServer struct { 15 | webApp *dotweb.DotWeb 16 | listenAddr string 17 | } 18 | 19 | func NewWebServer(logPath string, node *node.Node) *WebServer { 20 | s := &WebServer{} 21 | s.webApp = dotweb.New() 22 | s.webApp.SetLogPath(logPath) 23 | s.webApp.SetEnabledLog(true) 24 | s.webApp.UseRequestLog() 25 | s.webApp.Use(cors.Middleware(cors.NewConfig().UseDefault())) 26 | s.webApp.Items.Set(_const.ItemKeyNode, node) 27 | 28 | s.initRoute() 29 | s.initModule() 30 | logger.Default().Debug("WebUI init success.") 31 | return s 32 | } 33 | 34 | func (s *WebServer) ListenAndServe(listenAddr string) error { 35 | s.listenAddr = listenAddr 36 | logger.Default().Debug("WebServer begin listen " + s.listenAddr) 37 | err := s.webApp.ListenAndServe(s.listenAddr) 38 | if err != nil { 39 | return err 40 | } 41 | return nil 42 | } 43 | 44 | func (s *WebServer) initRoute() { 45 | 46 | executorController := controllers.NewExecutorController() 47 | nodeController := new(controllers.NodeController) 48 | clusterController := new(controllers.ClusterController) 49 | userController := new(controllers.UserController) 50 | logController := controllers.NewLogController() 51 | 52 | g := s.webApp.HttpServer.Group("/api/task") 53 | g.GET("/list", executorController.ShowExecutors) 54 | g.POST("/save", executorController.SaveExecutor) 55 | g.POST("/update", executorController.UpdateExecutor) 56 | g.GET("/get", executorController.QueryById) 57 | g.GET("/execlogs", executorController.ShowExecLogs) 58 | g.GET("/statelogs", executorController.ShowStateLog) 59 | 60 | g = s.webApp.HttpServer.Group("/api/node") 61 | g.GET("/list", nodeController.ShowNodes) 62 | 63 | g = s.webApp.HttpServer.Group("/api/log") 64 | g.GET("/trace", logController.ShowNodeTraceLog) 65 | g.GET("/exec", logController.ShowTaskExecLogs) 66 | g.GET("/state", logController.ShowTaskStateLog) 67 | g.GET("/submit", logController.ShowTaskSubmitLog) 68 | 69 | g = s.webApp.HttpServer.Group("/api/cluster") 70 | g.GET("/resources", clusterController.ShowResources) 71 | g.GET("/executors", clusterController.ShowExecutors) 72 | g.GET("/info", clusterController.ShowClusterInfo) 73 | g.GET("/metrics", clusterController.ShowMetrics) 74 | 75 | g = s.webApp.HttpServer.Group("/api/user") 76 | g.GET("/login", userController.Login) 77 | 78 | s.webApp.HttpServer.ServerFile("/static/*", "webapp/") 79 | 80 | } 81 | 82 | func (s *WebServer) initModule() { 83 | s.webApp.HttpServer.RegisterModule(&dotweb.HttpModule{ 84 | OnBeginRequest: func(ctx dotweb.Context) { 85 | path := ctx.Request().URL.Path 86 | if path == "/static/index.html" || path == "/static/" { 87 | return 88 | } 89 | 90 | if strings.HasPrefix(path, "/static/") && !strings.HasPrefix(path, "/static/static") { 91 | ctx.Request().Request.URL.Path = "/static" + path 92 | if strings.Contains(path, "/js/") || 93 | strings.Contains(path, "/css/") || 94 | strings.Contains(path, "/img/") || 95 | strings.Contains(path, "/fonts/") || 96 | strings.HasSuffix(path, "/static/static") || 97 | strings.HasSuffix(path, "/static/static/") { 98 | return 99 | } 100 | } 101 | 102 | if strings.HasPrefix(path, "/api/") { 103 | return 104 | } 105 | ctx.Request().Request.URL.Path = "/static/index.html" 106 | }, 107 | OnEndRequest: func(ctx dotweb.Context) { 108 | }, 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /webui/webapp/src/views/task/components/execLogs.vue: -------------------------------------------------------------------------------- 1 | 34 | 94 | 100 | -------------------------------------------------------------------------------- /util/http/http.go: -------------------------------------------------------------------------------- 1 | package _http 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "time" 11 | ) 12 | 13 | const ( 14 | MaxIdleConns int = 100 15 | MaxIdleConnsPerHost int = 50 16 | IdleConnTimeout int = 90 17 | InsecureSkipVerify = true 18 | ) 19 | 20 | type HttpResult struct { 21 | Status string 22 | Body string 23 | ContentType string 24 | IntervalTime time.Duration 25 | Error error 26 | } 27 | 28 | func getTransport(timeout time.Duration) *http.Transport { 29 | return &http.Transport{ 30 | Proxy: http.ProxyFromEnvironment, 31 | DialContext: (&net.Dialer{ 32 | Timeout: timeout, 33 | }).DialContext, 34 | MaxIdleConns: MaxIdleConns, 35 | MaxIdleConnsPerHost: MaxIdleConnsPerHost, 36 | IdleConnTimeout: time.Duration(IdleConnTimeout) * time.Second, 37 | TLSClientConfig: &tls.Config{InsecureSkipVerify: InsecureSkipVerify}, 38 | } 39 | } 40 | 41 | func GetHttpClient(timeout time.Duration) *http.Client { 42 | return &http.Client{Transport: getTransport(timeout)} 43 | } 44 | 45 | func HttpHead(url string, timeout time.Duration) *HttpResult { 46 | startTime := time.Now() 47 | result := &HttpResult{} 48 | 49 | resp, err := GetHttpClient(timeout).Head(url) 50 | if err != nil { 51 | result.IntervalTime = time.Now().Sub(startTime) 52 | result.Error = err 53 | return result 54 | } 55 | defer resp.Body.Close() 56 | 57 | result.Status = resp.Status 58 | 59 | byteBody, err := ioutil.ReadAll(resp.Body) 60 | result.IntervalTime = time.Now().Sub(startTime) 61 | if err != nil { 62 | result.IntervalTime = time.Now().Sub(startTime) 63 | result.Error = err 64 | return result 65 | } 66 | result.Body = string(byteBody) 67 | result.ContentType = resp.Header.Get("Content-Type") 68 | result.IntervalTime = time.Now().Sub(startTime) 69 | return result 70 | } 71 | 72 | func HttpGet(url string, timeout time.Duration) *HttpResult { 73 | startTime := time.Now() 74 | result := &HttpResult{} 75 | 76 | resp, err := GetHttpClient(timeout).Get(url) 77 | if err != nil { 78 | result.IntervalTime = time.Now().Sub(startTime) 79 | result.Error = err 80 | return result 81 | } 82 | defer resp.Body.Close() 83 | 84 | result.Status = resp.Status 85 | 86 | byteBody, err := ioutil.ReadAll(resp.Body) 87 | result.IntervalTime = time.Now().Sub(startTime) 88 | if err != nil { 89 | result.IntervalTime = time.Now().Sub(startTime) 90 | result.Error = err 91 | return result 92 | } 93 | result.Body = string(byteBody) 94 | result.ContentType = resp.Header.Get("Content-Type") 95 | result.IntervalTime = time.Now().Sub(startTime) 96 | return result 97 | } 98 | 99 | func HttpPost(url string, postBody string, bodyType string, timeout time.Duration) *HttpResult { 100 | startTime := time.Now() 101 | result := &HttpResult{} 102 | 103 | postBytes := bytes.NewBuffer([]byte(postBody)) 104 | if bodyType == "" { 105 | bodyType = "application/x-www-form-urlencoded" 106 | } 107 | resp, err := GetHttpClient(timeout).Post(url, bodyType, postBytes) 108 | if err != nil { 109 | result.IntervalTime = time.Now().Sub(startTime) 110 | result.Error = err 111 | return result 112 | } 113 | defer resp.Body.Close() 114 | 115 | result.Status = resp.Status 116 | 117 | byteBody, err := ioutil.ReadAll(resp.Body) 118 | if err != nil { 119 | result.IntervalTime = time.Now().Sub(startTime) 120 | result.Error = err 121 | return result 122 | } 123 | result.Body = string(byteBody) 124 | result.ContentType = resp.Header.Get("Content-Type") 125 | result.IntervalTime = time.Now().Sub(startTime) 126 | return result 127 | 128 | } 129 | 130 | //从指定query集合获取指定key的值 131 | func GetQuery(querys url.Values, key string) string { 132 | if len(querys[key]) > 0 { 133 | return querys[key][0] 134 | } 135 | return "" 136 | } 137 | -------------------------------------------------------------------------------- /runtime/executor/httpexec.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "errors" 5 | "github.com/devfeel/dottask" 6 | "github.com/devfeel/mapper" 7 | "github.com/devfeel/rockman/core" 8 | "github.com/devfeel/rockman/logger" 9 | _http "github.com/devfeel/rockman/util/http" 10 | _json "github.com/devfeel/rockman/util/json" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | const ( 16 | HttpMethod_HEAD = "HEAD" 17 | HttpMethod_GET = "GET" 18 | HttpMethod_POST = "POST" 19 | ) 20 | 21 | type ( 22 | HttpConfig struct { 23 | Url string 24 | Method string 25 | ContentType string 26 | PostBody string 27 | Timeout int //单位为秒 28 | } 29 | 30 | HttpExecutor struct { 31 | baseExecutor 32 | httpConfig *HttpConfig 33 | } 34 | ) 35 | 36 | func NewDebugHttpExecutor(taskID string) Executor { 37 | conf := &core.TaskConfig{} 38 | conf.TaskID = taskID + "-debug" 39 | conf.TaskType = "cron" 40 | conf.IsRun = true 41 | conf.DueTime = 0 42 | conf.Interval = 0 43 | conf.Express = "0 * * * * *" 44 | conf.TaskData = "http-url" 45 | conf.TargetType = TargetType_Http 46 | conf.TargetConfig = &HttpConfig{ 47 | Url: "http://www.dotweb.cn", 48 | Method: HttpMethod_GET, 49 | } 50 | exec, _ := NewHttpExecutor(conf) 51 | return exec 52 | } 53 | 54 | func NewHttpExecutor(conf *core.TaskConfig) (*HttpExecutor, error) { 55 | lt := "NewHttpExecutor[" + conf.TaskID + "] " 56 | exec := new(HttpExecutor) 57 | exec.TaskConfig = conf 58 | exec.TaskConfig.Handler = exec.Exec 59 | exec.httpConfig = new(HttpConfig) 60 | err := mapper.MapperMap(exec.TaskConfig.TargetConfig.(map[string]interface{}), exec.httpConfig) 61 | if err != nil { 62 | logger.Runtime().Error(err, lt+"convert config error") 63 | return nil, err 64 | } 65 | if exec.httpConfig.Method == "" { 66 | exec.httpConfig.Method = HttpMethod_GET 67 | } 68 | exec.httpConfig.Method = strings.ToUpper(exec.httpConfig.Method) 69 | return exec, nil 70 | } 71 | 72 | func (exec *HttpExecutor) Exec(ctx *task.TaskContext) error { 73 | logTitle := "HttpExecutor [" + exec.GetTaskID() + "] [" + exec.httpConfig.Method + "] " 74 | if exec.httpConfig.Method == HttpMethod_HEAD { 75 | result := _http.HttpHead(exec.httpConfig.Url, time.Second*time.Duration(exec.httpConfig.Timeout)) 76 | logger.Runtime().DebugS(logTitle+"result= "+result.Status, "error=", result.Error) 77 | if result.Error != nil { 78 | ctx.Error = result.Error 79 | return nil 80 | } 81 | if result.Status != CorrectStatus { 82 | ctx.Error = errors.New("http response status not " + CorrectStatus + ", is " + result.Status) 83 | } 84 | return nil 85 | } 86 | if exec.httpConfig.Method == HttpMethod_GET { 87 | result := _http.HttpGet(exec.httpConfig.Url, time.Second*time.Duration(exec.httpConfig.Timeout)) 88 | logger.Runtime().DebugS(logTitle+"result= "+result.Status, "error=", result.Error) 89 | if result.Error != nil { 90 | ctx.Error = result.Error 91 | return nil 92 | } 93 | if result.Status != CorrectStatus { 94 | ctx.Error = errors.New("http response status not " + CorrectStatus + ", is " + result.Status) 95 | } 96 | return nil 97 | } 98 | if exec.httpConfig.Method == HttpMethod_POST { 99 | result := _http.HttpPost(exec.httpConfig.Url, exec.httpConfig.PostBody, exec.httpConfig.ContentType, time.Second*time.Duration(exec.httpConfig.Timeout)) 100 | logger.Runtime().DebugS(logTitle+"result= "+result.Status, "error=", result.Error) 101 | if result.Error != nil { 102 | ctx.Error = result.Error 103 | return nil 104 | } 105 | if result.Status != CorrectStatus { 106 | ctx.Error = errors.New("http response status not " + CorrectStatus + ", is " + result.Status) 107 | } 108 | return nil 109 | } 110 | 111 | logger.Runtime().Debug(logTitle + "not support http method [" + exec.httpConfig.Method + "]") 112 | ctx.Error = errors.New(logTitle + "not support http method [" + exec.httpConfig.Method + "]") 113 | return nil 114 | } 115 | 116 | func (c *HttpConfig) LoadFromJson(json string) error { 117 | return _json.Unmarshal(json, c) 118 | } 119 | -------------------------------------------------------------------------------- /webui/webapp/src/views/main/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 70 | 147 | -------------------------------------------------------------------------------- /scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "errors" 5 | "github.com/devfeel/rockman/core" 6 | "sort" 7 | "sync" 8 | ) 9 | 10 | const ( 11 | Balance_LowerLoad = iota //balance by lower load 12 | Balance_JobCount 13 | Balance_CpuRate 14 | Balance_MemoryRate 15 | ) 16 | 17 | type ( 18 | Scheduler interface { 19 | SetResource(resource *core.ResourceInfo) 20 | Resources() map[string]*core.ResourceInfo 21 | LoadResource(endPoint string) *core.ResourceInfo 22 | Schedule(balanceType int) ([]*core.ResourceInfo, error) 23 | } 24 | 25 | StandardScheduler struct { 26 | resources map[string]*core.ResourceInfo 27 | resourceLocker *sync.RWMutex 28 | } 29 | ) 30 | 31 | var ErrorNotSupportBalanceType = errors.New("not support balance type") 32 | var ErrorNotExistsEnoughResource = errors.New("not exists enough resource") 33 | 34 | func NewScheduler() Scheduler { 35 | scheduler := new(StandardScheduler) 36 | scheduler.resources = make(map[string]*core.ResourceInfo) 37 | scheduler.resourceLocker = new(sync.RWMutex) 38 | 39 | return scheduler 40 | } 41 | 42 | // RefreshResource refresh resource value 43 | func (s *StandardScheduler) SetResource(resource *core.ResourceInfo) { 44 | defer s.resourceLocker.Unlock() 45 | s.resourceLocker.Lock() 46 | resource.RefreshLoadValue() 47 | s.resources[resource.EndPoint] = resource 48 | } 49 | 50 | // GetResources return scheduler's resources 51 | func (s *StandardScheduler) Resources() map[string]*core.ResourceInfo { 52 | defer s.resourceLocker.RUnlock() 53 | s.resourceLocker.RLock() 54 | return s.resources 55 | } 56 | 57 | // LoadResource load resource by endPoint 58 | func (s *StandardScheduler) LoadResource(endPoint string) *core.ResourceInfo { 59 | defer s.resourceLocker.Unlock() 60 | s.resourceLocker.Lock() 61 | resource, isExists := s.resources[endPoint] 62 | if !isExists { 63 | resource := &core.ResourceInfo{} 64 | resource.RefreshLoadValue() 65 | s.resources[endPoint] = resource 66 | } 67 | return resource 68 | } 69 | 70 | // Schedule 71 | func (s *StandardScheduler) Schedule(balanceType int) ([]*core.ResourceInfo, error) { 72 | if s.Resources() == nil || len(s.Resources()) <= 0 { 73 | return nil, ErrorNotExistsEnoughResource 74 | } 75 | if balanceType == Balance_LowerLoad { 76 | rs := s.getSortLoadResources(s.Resources()) 77 | return rs, nil 78 | } 79 | 80 | if balanceType == Balance_CpuRate { 81 | rs := s.getSortCpuResources(s.Resources()) 82 | return rs, nil 83 | } 84 | 85 | if balanceType == Balance_MemoryRate { 86 | rs := s.getSortMemoryResources(s.Resources()) 87 | return rs, nil 88 | } 89 | 90 | if balanceType == Balance_JobCount { 91 | rs := s.getSortJobResources(s.Resources()) 92 | return rs, nil 93 | } 94 | 95 | return nil, ErrorNotSupportBalanceType 96 | } 97 | 98 | func (s *StandardScheduler) getSortLoadResources(resources map[string]*core.ResourceInfo) core.LoadResources { 99 | var loadResources core.LoadResources 100 | for _, resource := range resources { 101 | loadResources = append(loadResources, resource) 102 | } 103 | sort.Sort(loadResources) 104 | return loadResources 105 | } 106 | 107 | func (s *StandardScheduler) getSortCpuResources(resources map[string]*core.ResourceInfo) core.CpuResources { 108 | var loadResources core.CpuResources 109 | for _, resource := range resources { 110 | loadResources = append(loadResources, resource) 111 | } 112 | sort.Sort(loadResources) 113 | return loadResources 114 | } 115 | 116 | func (s *StandardScheduler) getSortMemoryResources(resources map[string]*core.ResourceInfo) core.MemoryResources { 117 | var loadResources core.MemoryResources 118 | for _, resource := range resources { 119 | loadResources = append(loadResources, resource) 120 | } 121 | sort.Sort(loadResources) 122 | return loadResources 123 | } 124 | 125 | func (s *StandardScheduler) getSortJobResources(resources map[string]*core.ResourceInfo) core.JobResources { 126 | var loadResources core.JobResources 127 | for _, resource := range resources { 128 | loadResources = append(loadResources, resource) 129 | } 130 | sort.Sort(loadResources) 131 | return loadResources 132 | } 133 | -------------------------------------------------------------------------------- /webui/webapp/src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1557116052159'); /* IE9 */ 3 | src: url('iconfont.eot?t=1557116052159#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAh0AAsAAAAAD0QAAAgoAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCELgqQDIxkATYCJAMsCxgABCAFhG0Hfxu7DKOilHAWkf1Vgk0nb2EvpqIxlU8udln0sWmHfywql87fsd6jl6yjnpXxQNzP96sWpfMQ3HkCj0YQCTgaY/88be79+8d2NTgW4CraDNhaG4yIQxu0CTMYWA1GN1YNK+5/brcNPBLKhYQl3hJmSfPs9vl/ckw0UQABYZ4DBP9Tay0/KqHp4u1DYajpxNZefDHfQVQSmaShQEiiHq9BqFAapVbs8T1zxcCg1AsmfG29mQAoBYiD5sf7CwZIHAXospPChkHNjRxJDFA2l54TY0B3EKHCGcR5ALf094M/pKQAQqII+K0W24MH6xe8cBD+C2qiPwjuGBabgQiYAfGCPnsLH3AOZylm+nqThgIDvH9d8gUvuBe8T7qyZpbj/+zylYzNCwOeIq+Mwp3+g8cgEWgirjAIUvBwjdB9QwleoHLJwYvLJQAvSS4pePEylfjAKQZJh1MIooRTAVIDpwRkFpwyEAeIbyu4OmEETEF4BojGwGM2eAIjPCz6hv8oi0R0eRfMS7zFpE/LVQbH8z6cn9SNoyjSu1ZYWheHbcT6CGFVeJd1ushupjVIMTq/1wZNRNf1O8M6mwW1DmyrazeZofuwUJWmX3aMStZdv3CRTq7uYdCZNAqtHnQGc8plNJbMDbMQUF7FgjD4CDli9HrBMIYlGbXixg3l9euKmzfLbt0qL8vFuggBlxizJ95gMJkQ0hgZZgtafjMj3dwauPpmY+1e+8YGy6IZpxvB+ojj14NW3Wiki2TQCxF7YisNrJvO5CpGvefGDO3VW9f6aw9qDkXq1nkL9p5jlCHWtERmsaAlh04c8BbMUfpFXqx5K/zjw0MsGdq9YWjxes+Ng+rOsE4DHNJUjjHvZXZf1VzL1NVFGzYg460OBmpWyK03rvgLeYzBeuZCyoVtasWNfb5OXm7SGLVD+J12P3LZgXTpnkOBouUHaxeaGV5rQSKNlZXq6rwVbT0Veq1MhWQPqa/g1aNOF1su72CEKxpI+Sw9si6Pptm0btFCz8fjMG2oigFUOxKHnEaLswvdeK4UzL21ZtCFOp0VYY2lB+cLC/FhGFY8bgynIJRu5biqB9GdU2G1kiUYYIEZ4yJ1HZn2ljCIBWAQBwgBZI/QrU2RqhiNpEDjbYEP7+1QViQhx5Rt1sYV6T0Zo6wkf4JioxBfXJdOrLqR7rbnVijeE1u6RaMo0q9dfz0P2hjGrd/sgzUyI6P11K+XlSzW+JabdF7FBi+DLGNNJrmApfULKBcJRv+PO05VVvb13EZbDi6LvBAN+nHUsyQo60mXTY+xxRMsj8av3IQNNPn1FELbX+7YLdbY+RJC7t8nV+w4deaKjY+Bwi6jPl2ROq8OFz2WTb245eJWlfyjbBVwH7gBduSKbAPYD/Iy87pIYgkRtY7vUMwez7XK2Z3HtyYXBb8FjfvI+8QYmzUvims/ByqIabt2TcNCiIX8QlChmNUwuk2bdnldd0NfctfnzztJr1X9Z7wL+rZMz0gO7Tw1w+GcSa1jHjIzukeJU5vO7u22nCE8GjaWeu/w9KMQPZR5wFTkXk4uLiFxcSnvN3YZQZQ1azho4JqlGqb+ZB6wV6iZTkfG1M7hDaF5iLFTQEWh+jG//DA184tjljhjqO6TTRcqloo3r9RK3AM9dLbHHUJT/CK7zdD9YgNXD/Q+HNgFfm0UcrxTPf2Ct0w4s4+/Z2mEPdui1qitl7OR5R4/a+1j+xIz5XH1grUphLnGLrTaP63tsDl4ZatWPn7F4BPUqVMZf55XJRbl5j3O8UHviRLifRAf9CDs8UA0H9RS3l0llUjV7vxnwJTSc+4XL50LtmEMpJTa+DKaNpVMu3gx2a6Snpd2yFXGrKx798p3clfCg4428tdPbENG6/M5cU/Hj917fUT47IPEOLzk8uWleAwcnBU+rPWyO2cK4mY5zE32SJQSbH3+3Io/lQY15M+RYjF5TqI8Q/YxG9+wadhyr3V/KMWdIWaSrO379zL9coqXl7Zu9ZuG5Q155kYNAS9oMnuT9LbPnlmfgcPqgNPqVzqeWQTPtjgsfhnMrktqo+TDWYP9BgNZmjeSb2A7AAd+2Cn7D4pdBMwQ04iJ9VX1TvuARQPszqfV9dWNr1Zfcz6a2Bvyiq6MuxKZBb0nPnJeq25S4Vhs3NBZPMP7SttxbY9QM8TmoTSZdG3q4i/1yfXDJ36M+ehcXNjgzdxDc980KFzs3Aodh9WnwITHAAD/rX5gZZq8NTBaJe9lxU02X+4y7goAgO8RXzy+ymPcGcY6uHE1JfE0bsqPOYDz0XjYEVww+9kJZOnKJZ0keV8p9i9p0e2vN/Rp5f/lBWJwW5gjhooCMLNRiDPivcZlF1OiZMPiyv4fHQ+BUswDM5nSnofu6ZCszNeoVwlCrvxBhBJmiUPjQIKVBlKUQlCK1eT0iofBDCimAUxMYQDhbQEEsUMA412wkKEPgIjMe+CCjwGjLJhckBWx12iXkp2Th10b7OVy7EtfPsq6PkgUoZ1WtTTpQ2kmazA5IanZdKKYUheHDCnFcmjuoyrlCBuz21EYMg5MWZGbT3vm+c9MTFT1HTrtxhGMlEoRWx5n9aCuGuteXCzmL5wbtX7+gQiFkC0dGXNn/UFSGbl+lCxBEsFywjFpzGvpPURJWBrqMR+qwSkWQY09RkJRz9DA/u0U4spN9TTgP6ZEby4VFU/PT6PPexWU+PFVQCCMSCRCLsgVUYj+TRZz4PpoH9D5ckRdWy7Tkohhst9woaQd9/LCSVrw2C24ygM2C4l6jSMFdZxCFT0lY9FKAA==') format('woff2'), 5 | url('iconfont.woff?t=1557116052159') format('woff'), 6 | url('iconfont.ttf?t=1557116052159') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1557116052159#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-project:before { 19 | content: "\e60c"; 20 | } 21 | 22 | .icon-home:before { 23 | content: "\e635"; 24 | } 25 | 26 | .icon-ai240:before { 27 | content: "\e6e4"; 28 | } 29 | 30 | .icon-ui:before { 31 | content: "\e61a"; 32 | } 33 | 34 | .icon-youjian:before { 35 | content: "\e65c"; 36 | } 37 | 38 | .icon-tubiao:before { 39 | content: "\e60f"; 40 | } 41 | 42 | .icon-cuowubaogao:before { 43 | content: "\e68d"; 44 | } 45 | 46 | .icon-biaoge:before { 47 | content: "\e600"; 48 | } 49 | 50 | .icon-buju:before { 51 | content: "\e655"; 52 | } 53 | 54 | .icon-jingzi:before { 55 | content: "\e610"; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /registry/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | consulapi "github.com/hashicorp/consul/api" 9 | ) 10 | 11 | type ( 12 | ConsulClient struct { 13 | client *consulapi.Client 14 | addr string 15 | } 16 | 17 | ServiceConfig struct { 18 | Name string 19 | Tags []string 20 | Address string 21 | Port int 22 | ChechUrl string 23 | } 24 | ) 25 | 26 | var ErrorNotExistsKey = errors.New("not exists this key") 27 | 28 | func NewConsulClient(addr string) (*ConsulClient, error) { 29 | client := &ConsulClient{} 30 | client.addr = addr 31 | 32 | config := consulapi.DefaultConfig() 33 | config.Address = addr 34 | innerClient, err := consulapi.NewClient(config) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | client.client = innerClient 40 | return client, nil 41 | } 42 | 43 | func (c *ConsulClient) RegisterService(service *ServiceConfig) error { 44 | registration := new(consulapi.AgentServiceRegistration) 45 | registration.ID = hashService(service) 46 | registration.Name = service.Name 47 | registration.Port = service.Port 48 | registration.Tags = service.Tags 49 | registration.Address = service.Address 50 | 51 | registration.Check = &consulapi.AgentServiceCheck{ 52 | HTTP: service.ChechUrl, 53 | Timeout: "10s", 54 | Interval: "10s", 55 | DeregisterCriticalServiceAfter: "30s", //check失败后30秒删除本服务 56 | } 57 | err := c.GetClient().Agent().ServiceRegister(registration) 58 | return err 59 | 60 | } 61 | 62 | func (c *ConsulClient) SearchService(addr string, serviceName string, tag string) ([]*ServiceConfig, error) { 63 | services, _, err := c.GetClient().Catalog().Service(serviceName, tag, nil) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | var appServices []*ServiceConfig 69 | for _, v := range services { 70 | appServices = append(appServices, &ServiceConfig{ 71 | Name: v.ServiceName, 72 | Tags: v.ServiceTags, 73 | Address: v.Address, 74 | Port: v.ServicePort, 75 | }) 76 | } 77 | return appServices, nil 78 | } 79 | 80 | func (c *ConsulClient) Get(key string, opt *consulapi.QueryOptions) (*consulapi.KVPair, *consulapi.QueryMeta, error) { 81 | client := c.GetClient() 82 | 83 | kvPair, meta, err := client.KV().Get(key, opt) 84 | if err != nil { 85 | return nil, nil, err 86 | } 87 | if kvPair == nil { 88 | return nil, nil, ErrorNotExistsKey 89 | } 90 | return kvPair, meta, nil 91 | } 92 | 93 | func (c *ConsulClient) Delete(key string, opt *consulapi.WriteOptions) (*consulapi.WriteMeta, error) { 94 | client := c.GetClient() 95 | meta, err := client.KV().Delete(key, opt) 96 | return meta, err 97 | } 98 | 99 | func (c *ConsulClient) Set(key string, value string, opt *consulapi.WriteOptions) (*consulapi.WriteMeta, error) { 100 | client := c.GetClient() 101 | kv := &consulapi.KVPair{ 102 | Key: key, 103 | Value: []byte(value), 104 | } 105 | meta, err := client.KV().Put(kv, opt) 106 | return meta, err 107 | } 108 | 109 | func (c *ConsulClient) CreateLockerOpts(opts *consulapi.LockOptions) (*Locker, error) { 110 | locker, err := c.GetClient().LockOpts(opts) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return &Locker{Locker: locker}, nil 115 | } 116 | 117 | func (c *ConsulClient) CreateLocker(key string) (*Locker, error) { 118 | locker, err := c.GetClient().LockKey(key) 119 | if err != nil { 120 | return nil, err 121 | } 122 | return &Locker{Locker: locker}, nil 123 | } 124 | 125 | func (c *ConsulClient) ListSession() ([]*consulapi.SessionEntry, *consulapi.QueryMeta, error) { 126 | return c.GetClient().Session().List(nil) 127 | } 128 | 129 | func (c *ConsulClient) ListKV(prefix string, opt *consulapi.QueryOptions) (consulapi.KVPairs, *consulapi.QueryMeta, error) { 130 | return c.GetClient().KV().List(prefix, opt) 131 | } 132 | 133 | func (c *ConsulClient) GetClient() *consulapi.Client { 134 | return c.client 135 | } 136 | 137 | // hashService hash service to string 138 | func hashService(service *ServiceConfig) string { 139 | data, err := json.Marshal(service) 140 | if err != nil { 141 | return "" 142 | } 143 | hash := sha256.Sum256(data) 144 | return hex.EncodeToString(hash[:]) 145 | } 146 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/devfeel/rockman/metrics/prometheus" 5 | "github.com/devfeel/rockman/metrics/standard" 6 | promclient "github.com/prometheus/client_golang/prometheus" 7 | "strings" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const ( 13 | LabelNodeStart = "NodeStart" 14 | LabelStartTheWorld = "StartTheWorld" 15 | LabelStopTheWorld = "StopTheWorld" 16 | LabelTaskExec = "TaskExec" 17 | LabelLeaderChange = "LeaderChange" 18 | LabelLeaderChangeFailed = "LeaderChangeFailed" 19 | LabelWorkerNodeOffline = "WorkerNodeOffline" 20 | LabelRegistryOnline = "RegistryOnline" 21 | LabelRegistryOffline = "RegistryOffline" 22 | ) 23 | 24 | type ( 25 | Metrics interface { 26 | GetStandardCounter(label string) Counter 27 | Inc(label string) 28 | Dec(label string) 29 | Add(label string, value int64) 30 | } 31 | 32 | // Counter incremented and decremented base on int64 value. 33 | Counter interface { 34 | StartTime() time.Time 35 | Clear() 36 | Count() int64 37 | Dec() 38 | Inc() 39 | Add(int64) 40 | } 41 | 42 | StandardMetrics struct { 43 | counters *sync.Map 44 | promCounter *promclient.CounterVec 45 | } 46 | 47 | Opts struct { 48 | // Namespace, Subsystem, and Name are components of the fully-qualified 49 | // name of the Metric (created by joining these components with 50 | // "_"). 51 | Namespace string 52 | Subsystem string 53 | Name string 54 | } 55 | ) 56 | 57 | var ( 58 | defaultMetrics Metrics 59 | labelMap map[string]string 60 | ) 61 | 62 | func init() { 63 | defaultMetrics = NewMetrics() 64 | labelMap = make(map[string]string) 65 | labelMap[LabelNodeStart] = LabelNodeStart 66 | labelMap[LabelStartTheWorld] = LabelStartTheWorld 67 | labelMap[LabelStopTheWorld] = LabelStopTheWorld 68 | labelMap[LabelTaskExec] = LabelTaskExec 69 | labelMap[LabelLeaderChange] = LabelLeaderChange 70 | labelMap[LabelLeaderChangeFailed] = LabelLeaderChangeFailed 71 | labelMap[LabelWorkerNodeOffline] = LabelWorkerNodeOffline 72 | labelMap[LabelRegistryOnline] = LabelRegistryOnline 73 | labelMap[LabelRegistryOffline] = LabelRegistryOffline 74 | } 75 | 76 | func Default() Metrics { 77 | return defaultMetrics 78 | } 79 | 80 | // NewCounter constructs a new StandardCounter. 81 | func NewCounter() Counter { 82 | return standard.NewStandardCounter() 83 | } 84 | 85 | // GetAllCountInfo get all counter's count for map[string]int64 86 | func GetAllCountInfo() map[string]int64 { 87 | m := make(map[string]int64) 88 | for _, label := range labelMap { 89 | m[label] = Default().GetStandardCounter(label).Count() 90 | } 91 | return m 92 | } 93 | 94 | func NewMetrics() Metrics { 95 | metrics := new(StandardMetrics) 96 | metrics.counters = new(sync.Map) 97 | metrics.promCounter = prometheus.InitCounter() 98 | return metrics 99 | } 100 | 101 | // GetCounter get Counter by key 102 | func (m *StandardMetrics) GetStandardCounter(label string) Counter { 103 | var counter Counter 104 | loadCounter, exists := m.counters.Load(label) 105 | if !exists { 106 | counter = NewCounter() 107 | m.counters.Store(label, counter) 108 | } else { 109 | counter = loadCounter.(Counter) 110 | } 111 | return counter 112 | } 113 | 114 | // Inc increments the counter by 1. 115 | func (m *StandardMetrics) Inc(label string) { 116 | m.GetStandardCounter(label).Inc() 117 | m.promCounter.WithLabelValues(label).Inc() 118 | } 119 | 120 | //Dec decrements the counter by 1. 121 | func (m *StandardMetrics) Dec(label string) { 122 | m.GetStandardCounter(label).Dec() 123 | m.promCounter.WithLabelValues(label).Add(-1) 124 | } 125 | 126 | // Add increments the counter by the given value. 127 | func (m *StandardMetrics) Add(label string, value int64) { 128 | m.GetStandardCounter(label).Add(value) 129 | m.promCounter.WithLabelValues(label).Add(float64(value)) 130 | } 131 | 132 | func buildFQName(namespace, subsystem, name string) string { 133 | if name == "" { 134 | return "" 135 | } 136 | switch { 137 | case namespace != "" && subsystem != "": 138 | return strings.Join([]string{namespace, subsystem, name}, "_") 139 | case namespace != "": 140 | return strings.Join([]string{namespace, name}, "_") 141 | case subsystem != "": 142 | return strings.Join([]string{subsystem, name}, "_") 143 | } 144 | return name 145 | } 146 | -------------------------------------------------------------------------------- /runtime/executor/shellexec.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/devfeel/dottask" 7 | "github.com/devfeel/mapper" 8 | "github.com/devfeel/rockman/config" 9 | "github.com/devfeel/rockman/core" 10 | "github.com/devfeel/rockman/logger" 11 | _file "github.com/devfeel/rockman/util/file" 12 | _json "github.com/devfeel/rockman/util/json" 13 | "os/exec" 14 | "strings" 15 | ) 16 | 17 | const ( 18 | ShellType_Script = "SCRIPT" 19 | ShellType_File = "FILE" 20 | ShellFilePath = "shells/" 21 | ) 22 | 23 | var ( 24 | ErrorNotSupportShellType = errors.New("not support shell type") 25 | ErrorShellFileNotInSpecifyPath = errors.New("shell file not in specify path") 26 | ErrorNotEnabledShellScriptMode = errors.New("shell script mode is not enabled") 27 | ) 28 | 29 | type ( 30 | ShellConfig struct { 31 | Type string //default will be ShellType_Script 32 | Script string 33 | } 34 | 35 | ShellExecutor struct { 36 | baseExecutor 37 | shellConfig *ShellConfig 38 | } 39 | ) 40 | 41 | func NewDebugShellExecutor(taskID string) Executor { 42 | conf := &core.TaskConfig{} 43 | conf.TaskID = taskID + "-debug" 44 | conf.TaskType = "cron" 45 | conf.IsRun = true 46 | conf.DueTime = 0 47 | conf.Interval = 0 48 | conf.Express = "0 * * * * *" 49 | conf.TaskData = "shell.sh" 50 | conf.TargetType = TargetType_Shell 51 | conf.TargetConfig = &ShellConfig{ 52 | Script: "demo.sh", 53 | Type: ShellType_File, 54 | } 55 | exec, _ := NewShellExecutor(conf) 56 | return exec 57 | } 58 | 59 | func NewShellExecutor(conf *core.TaskConfig) (*ShellExecutor, error) { 60 | lt := "NewShellExecutor[" + conf.TaskID + "] " 61 | exec := new(ShellExecutor) 62 | exec.TaskConfig = conf 63 | exec.TaskConfig.Handler = exec.Exec 64 | exec.shellConfig = new(ShellConfig) 65 | err := mapper.MapperMap(exec.TaskConfig.TargetConfig.(map[string]interface{}), exec.shellConfig) 66 | if err != nil { 67 | logger.Runtime().Error(err, lt+"convert config error") 68 | return nil, err 69 | } 70 | if exec.shellConfig.Type == "" { 71 | exec.shellConfig.Type = ShellType_File 72 | } 73 | exec.shellConfig.Type = strings.ToUpper(exec.shellConfig.Type) 74 | if !config.CurrentProfile.Runtime.EnableShellScript { 75 | if exec.shellConfig.Type == ShellType_Script { 76 | logger.Runtime().Debug(lt + "error: " + ErrorNotEnabledShellScriptMode.Error()) 77 | return nil, ErrorNotEnabledShellScriptMode 78 | } 79 | } 80 | 81 | if exec.shellConfig.Type != ShellType_File && exec.shellConfig.Type != ShellType_Script { 82 | logger.Runtime().Debug(lt + "error: " + ErrorNotSupportShellType.Error()) 83 | return nil, ErrorNotSupportShellType 84 | } 85 | 86 | if exec.shellConfig.Type == ShellType_File { 87 | exec.shellConfig.Script = ShellFilePath + exec.shellConfig.Script 88 | if !_file.ExistsInPath(ShellFilePath, exec.shellConfig.Script) { 89 | logger.Runtime().Debug(lt + "error: " + ErrorShellFileNotInSpecifyPath.Error()) 90 | return nil, ErrorShellFileNotInSpecifyPath 91 | } 92 | } 93 | return exec, nil 94 | } 95 | 96 | func (exec *ShellExecutor) Exec(ctx *task.TaskContext) error { 97 | logTitle := "ShellExecutor [" + exec.GetTaskID() + "] [" + exec.shellConfig.Type + "] " 98 | 99 | var result string 100 | var err error 101 | if exec.shellConfig.Type == ShellType_Script { 102 | result, err = execShellScript(exec.shellConfig.Script) 103 | } 104 | if exec.shellConfig.Type == ShellType_File { 105 | result, err = execShellFile(exec.shellConfig.Script) 106 | } 107 | 108 | logger.Runtime().DebugS(logTitle+"result= "+result, "error=", err) 109 | if err != nil { 110 | ctx.Error = err 111 | return nil 112 | } 113 | if result != CorrectResult { 114 | ctx.Error = errors.New("shell response not " + CorrectResult + ", is " + result) 115 | } 116 | return nil 117 | } 118 | 119 | func execShellScript(s string) (string, error) { 120 | if !config.CurrentProfile.Runtime.EnableShellScript { 121 | return "", ErrorNotEnabledShellScriptMode 122 | } 123 | cmd := exec.Command("/bin/bash", "-c", s) 124 | var out bytes.Buffer 125 | cmd.Stdout = &out 126 | err := cmd.Run() 127 | if err != nil { 128 | return "", err 129 | } 130 | str := strings.Replace(out.String(), " ", "", -1) 131 | str = strings.Replace(out.String(), "\n", "", -1) 132 | return str, err 133 | } 134 | 135 | func execShellFile(f string) (string, error) { 136 | cmd := exec.Command("/bin/sh", f) 137 | var out bytes.Buffer 138 | cmd.Stdout = &out 139 | err := cmd.Run() 140 | if err != nil { 141 | return "", err 142 | } 143 | str := strings.Replace(out.String(), " ", "", -1) 144 | str = strings.Replace(out.String(), "\n", "", -1) 145 | return str, err 146 | } 147 | 148 | func (c *ShellConfig) LoadFromJson(json string) error { 149 | return _json.Unmarshal(json, c) 150 | } 151 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "github.com/devfeel/rockman/config" 8 | "github.com/devfeel/rockman/logger" 9 | "github.com/devfeel/rockman/metrics/prometheus" 10 | "github.com/devfeel/rockman/node" 11 | "github.com/devfeel/rockman/rpc" 12 | "github.com/devfeel/rockman/util/exception" 13 | "github.com/devfeel/rockman/webui" 14 | "os" 15 | "time" 16 | ) 17 | 18 | var CurNode *node.Node 19 | var CurRpcServer *rpc.RpcServer 20 | var CurWebServer *webui.WebServer 21 | 22 | const ( 23 | ProjectName = "rockman" 24 | cmdNodeType = "nodetype" 25 | cmdOuterHost = "outerhost" 26 | cmdOuterPort = "outerport" 27 | cmdCluster = "cluster" 28 | cmdEnableTls = "enabletls" 29 | 30 | version = "2020.0606 For Birthday on 2020" 31 | confName = "app.conf" 32 | ) 33 | 34 | func main() { 35 | defer func() { 36 | if err := recover(); err != nil { 37 | exMsg := exception.CatchError(ProjectName+":main", err) 38 | logger.Default().Error(errors.New(exMsg), "") 39 | os.Stdout.Write([]byte(exMsg)) 40 | } 41 | }() 42 | 43 | startTime := time.Now() 44 | printLogo() 45 | var err error 46 | 47 | // load config file 48 | profile, err := config.LoadConfig(confName) 49 | if err != nil { 50 | logger.Default().Error(err, "load config error") 51 | return 52 | } 53 | 54 | parseFlag(profile) 55 | 56 | // start log service 57 | logger.StartLogService("config") 58 | 59 | shutdownChan := make(chan string) 60 | 61 | //start worker node 62 | CurNode, err = node.NewNode(profile, shutdownChan) 63 | if err != nil { 64 | logger.Default().Error(err, "new node error") 65 | return 66 | } 67 | 68 | //start rpc server 69 | CurRpcServer = rpc.NewRpcServer(profile, CurNode) 70 | //start web server 71 | if profile.Node.IsMaster { 72 | CurWebServer = webui.NewWebServer(profile.Logger.LogPath, CurNode) 73 | } 74 | 75 | go func() { 76 | err := CurRpcServer.Listen() 77 | if err != nil { 78 | logger.Default().Error(err, "RpcServer start error") 79 | panic(errors.New("RpcServer start error: " + err.Error())) 80 | } 81 | }() 82 | 83 | if profile.Node.IsMaster { 84 | go func() { 85 | err := CurWebServer.ListenAndServe(profile.WebUI.HttpHost + ":" + profile.WebUI.HttpPort) 86 | if err != nil { 87 | logger.Default().Error(err, "WebUI start error") 88 | panic(errors.New("WebUI start error: " + err.Error())) 89 | } 90 | }() 91 | } 92 | time.Sleep(time.Second) 93 | 94 | if profile.Prometheus.IsRun { 95 | go func() { 96 | err := prometheus.StartMetricsWeb(profile.Prometheus.HttpHost + ":" + profile.Prometheus.HttpPort) 97 | if err != nil { 98 | logger.Default().Error(err, "Prometheus metrics start error") 99 | panic(errors.New("Prometheus metrics start error: " + err.Error())) 100 | } 101 | }() 102 | } 103 | time.Sleep(time.Second) 104 | 105 | //start node 106 | err = CurNode.Start() 107 | if err != nil { 108 | logger.Default().Error(err, "Node start error") 109 | return 110 | } 111 | 112 | useTime := time.Now().Sub(startTime) 113 | logger.Default().Debug("Node start success in " + fmt.Sprint(int64(useTime/time.Second)) + "s, service running...") 114 | 115 | <-shutdownChan 116 | logger.Default().Debug("Node Shutdown.") 117 | logger.Default().Debug("Node Close.") 118 | } 119 | 120 | func parseFlag(profile *config.Profile) { 121 | var nodeType, outerHost, outerPort, cluster string 122 | var enableTls bool 123 | flag.StringVar(&nodeType, cmdNodeType, "", "node type, full or master or worker") 124 | flag.StringVar(&outerHost, cmdOuterHost, "", "node outer host") 125 | flag.StringVar(&outerPort, cmdOuterPort, "", "node outer port") 126 | flag.StringVar(&cluster, cmdCluster, "", "node cluster id") 127 | flag.BoolVar(&enableTls, cmdEnableTls, false, "enable tls for rpc") 128 | 129 | flag.Parse() 130 | if nodeType == "master" { 131 | profile.Node.IsWorker = false 132 | } 133 | 134 | if nodeType == "worker" { 135 | profile.Node.IsMaster = false 136 | } 137 | 138 | profile.Rpc.OuterHost = outerHost 139 | profile.Rpc.OuterPort = outerPort 140 | 141 | if cluster != "" { 142 | profile.Cluster.ClusterId = cluster 143 | } 144 | 145 | if enableTls { 146 | profile.Rpc.EnableTls = enableTls 147 | } 148 | } 149 | 150 | func printLogo() { 151 | fmt.Println(".______ ______ ______ __ ___ .___ ___. ___ .__ __. ") 152 | fmt.Println("| _ \\ / __ \\ / || |/ / | \\/ | / \\ | \\ | | ") 153 | fmt.Println("| |_) | | | | | | ,----'| ' / | \\ / | / ^ \\ | \\| | ") 154 | fmt.Println("| / | | | | | | | < | |\\/| | / /_\\ \\ | . ` | ") 155 | fmt.Println("| |\\ \\--.| `--' | | `----.| . \\ | | | | / _____ \\ | |\\ | ") 156 | fmt.Println("| _| `.___| \\______/ \\______||__|\\__\\ |__| |__| /__/ \\__\\ |__| \\__| ") 157 | fmt.Printf("%c[1m%s%c[0m\n", 0x1B, " Version: Beta."+version, 0x1B) 158 | } 159 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "github.com/devfeel/rockman/util/file" 6 | "gopkg.in/yaml.v2" 7 | "io/ioutil" 8 | ) 9 | 10 | const ConfigPath = "./conf/" 11 | 12 | type ( 13 | Profile struct { 14 | Global *GlobalSection 15 | Node *NodeSection 16 | Rpc *RpcSection 17 | WebUI *WebUISection 18 | Runtime *RuntimeSection 19 | Logger *LoggerSection 20 | Cluster *ClusterSection 21 | Prometheus *PrometheusSection 22 | } 23 | 24 | GlobalSection struct { 25 | RetryLimit int 26 | CheckNetInterval int //the interval time for check net, unit for second 27 | DataBaseConnectString string 28 | } 29 | 30 | LoggerSection struct { 31 | LogPath string 32 | } 33 | 34 | ClusterSection struct { 35 | ClusterId string //cluster Id 36 | RegistryServer string //Registry Server 37 | WatchLeaderRetryLimit int 38 | QueryResourceInterval int //the interval time for QueryResource, unit for second 39 | } 40 | 41 | NodeSection struct { 42 | NodeId string 43 | NodeName string 44 | IsMaster bool 45 | IsWorker bool 46 | LeaderCheckExecutorInterval int //the interval time for CheckExecutor from db, unit for second 47 | } 48 | 49 | RpcSection struct { 50 | OuterHost string 51 | OuterPort string 52 | RpcHost string 53 | RpcPort string 54 | RpcProtocol string //now is json-rpc 55 | EnableTls bool 56 | ServerCertFile string 57 | ServerKeyFile string 58 | ClientCertFile string 59 | ClientKeyFile string 60 | } 61 | 62 | PrometheusSection struct { 63 | IsRun bool 64 | HttpHost string 65 | HttpPort string 66 | } 67 | 68 | WebUISection struct { 69 | HttpHost string 70 | HttpPort string 71 | } 72 | 73 | RuntimeSection struct { 74 | IsRun bool 75 | LogPath string 76 | EnableShellScript bool 77 | } 78 | ) 79 | 80 | var CurrentProfile *Profile 81 | 82 | func GetConfigPath(file string) string { 83 | return ConfigPath + file 84 | } 85 | 86 | func GetProfile() *Profile { 87 | return CurrentProfile 88 | } 89 | 90 | // DefaultProfile return default profile used to full node role 91 | func DefaultProfile() *Profile { 92 | p := new(Profile) 93 | p.Global = &GlobalSection{RetryLimit: 5, DataBaseConnectString: "rock:rock@tcp(118.31.32.168:3306)/rockman?charset=utf8&allowOldPasswords=1&loc=Asia%2FShanghai&parseTime=true"} 94 | p.Node = &NodeSection{NodeId: "a1e97685392845f7b5bbd18f38a10461", IsMaster: true, IsWorker: true, LeaderCheckExecutorInterval: 60} 95 | p.Rpc = &RpcSection{RpcHost: "", RpcPort: "2398", EnableTls: false, ServerCertFile: "tls/server.crt", ServerKeyFile: "tls/server.key", ClientCertFile: "tls/client.crt", ClientKeyFile: "tls/client.key"} 96 | p.WebUI = &WebUISection{HttpHost: "", HttpPort: "8080"} 97 | p.Logger = &LoggerSection{LogPath: "./logs"} 98 | p.Runtime = &RuntimeSection{IsRun: true, LogPath: "./logs/runtime", EnableShellScript: false} 99 | p.Cluster = &ClusterSection{RegistryServer: "116.62.16.66:8500", ClusterId: "dev-rock", WatchLeaderRetryLimit: 10, QueryResourceInterval: 60} 100 | p.Prometheus = &PrometheusSection{IsRun: true, HttpHost: "", HttpPort: "8081"} 101 | 102 | CurrentProfile = p 103 | return p 104 | } 105 | 106 | // LoadConfig load config from yaml file 107 | func LoadConfig(configFile string) (*Profile, error) { 108 | // Validity check 109 | // 1. Try read as absolute path 110 | // 2. Try the current working directory 111 | // 3. Try $PWD/config 112 | // fixed for issue #15 config file path 113 | realFile := configFile 114 | if !_file.Exists(realFile) { 115 | realFile = _file.GetCurrentDirectory() + "/" + configFile 116 | if !_file.Exists(realFile) { 117 | realFile = _file.GetCurrentDirectory() + "/config/" + configFile 118 | if !_file.Exists(realFile) { 119 | return nil, errors.New("no exists config file => " + configFile) 120 | } 121 | } 122 | } 123 | 124 | profile, err := loadYamlConfig(realFile) 125 | if err == nil { 126 | err = validateProfile(profile) 127 | } 128 | CurrentProfile = profile 129 | return profile, err 130 | } 131 | 132 | // loadYamlConfig load config profile from yaml file 133 | func loadYamlConfig(configFile string) (*Profile, error) { 134 | content, err := ioutil.ReadFile(configFile) 135 | if err != nil { 136 | return nil, errors.New("RockMan:Config:initYamlConfig config file [" + configFile + "] cannot be parsed - " + err.Error()) 137 | } 138 | 139 | var profile *Profile 140 | err = yaml.Unmarshal(content, &profile) 141 | if err != nil { 142 | return nil, errors.New("RockMan:Config:initYamlConfig config file [" + configFile + "] cannot be parsed - " + err.Error()) 143 | } 144 | return profile, nil 145 | } 146 | 147 | func validateProfile(profile *Profile) error { 148 | if profile.Node.NodeId == "" { 149 | return errors.New("RockMan start error: " + "NodeId is null") 150 | } 151 | return nil 152 | } 153 | --------------------------------------------------------------------------------