├── Configuration.md ├── notify ├── endpoint.go ├── factory.go ├── endpoint_smtp.go ├── endpoint_api.go ├── typedef.go ├── events.go ├── notify.go └── templates │ ├── location.html │ └── job.html ├── api ├── listener_windows.go ├── middleware │ └── logger.go ├── utils.go ├── listener_linux.go ├── request.go ├── response.go ├── router.go ├── resolve.go ├── server.go ├── context.go ├── messages.go └── handler.go ├── .gitignore ├── scheduler ├── pref.go ├── scheduler.go └── hash.go ├── Dockerfile ├── cache ├── driver │ ├── types │ │ └── types.go │ ├── driver.go │ ├── ngcloud │ │ ├── parse.go │ │ ├── ngcloud.go │ │ └── engine.go │ └── mongo │ │ ├── mongo.go │ │ └── engine.go ├── units.go ├── action.go ├── localstorage.go ├── nodes.go ├── alloc.go └── cache.go ├── main.go ├── .editorconfig ├── etc ├── config.yaml ├── lookupenv.go └── configuration.go ├── README.md ├── server ├── handler.go └── server.go ├── jobserver.go ├── APIs.md └── LICENSE /Configuration.md: -------------------------------------------------------------------------------- 1 | # Cloudtask Center Configuration 2 | -------------------------------------------------------------------------------- /notify/endpoint.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | // IEndPoint is exported 4 | // sender endPoint interface 5 | type IEndPoint interface { 6 | DoEvent(event *Event, data interface{}) 7 | } 8 | -------------------------------------------------------------------------------- /api/listener_windows.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | func newUnixListener(addr string, tlsConfig *tls.Config) (net.Listener, error) { 10 | 11 | return nil, fmt.Errorf("Windows platform does not support a unix socket") 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /scheduler/pref.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | func (s *Scheduler) PrefAlloc(location string, nodes *Nodes) { 4 | 5 | //目前配置主要使用hash分配算法,性能方式分配算法暂未实现,不影响项目使用,后期陆续完成. 6 | //思路: 7 | //1、worker心跳包中根据配置每(15m)带一次内存占用信息,job执行开销评估数据. 8 | //2、server结合心跳数据,定期检查worker的job数,按job数与执行开销均分job. 9 | //3、server评估下线率比较大的worker,加入黑名单,尽量不把job分配到该worker上. 10 | } 11 | 12 | func (s *Scheduler) PrefSingleJobAlloc(location string, jobid string) { 13 | } 14 | -------------------------------------------------------------------------------- /api/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "github.com/cloudtask/libtools/gounits/logger" 4 | 5 | import ( 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func Logger(inner http.Handler) http.Handler { 11 | 12 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | t := time.Now() 14 | inner.ServeHTTP(w, r) 15 | logger.INFO("[#api#] HTTP %s\t%s\t%s", r.Method, r.RequestURI, time.Since(t)) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /api/utils.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "net/http" 4 | 5 | func httpError(w http.ResponseWriter, err string, code int) { 6 | http.Error(w, err, code) 7 | } 8 | 9 | func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { 10 | w.Header().Add("Access-Control-Allow-Origin", "*") 11 | w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") 12 | w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS, HEAD") 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM frolvlad/alpine-glibc:alpine-3.6 2 | 3 | MAINTAINER bobliu bobliu0909@gmail.com 4 | 5 | RUN mkdir -p /opt/cloudtask/etc 6 | 7 | RUN mkdir -p /opt/cloudtask/logs 8 | 9 | RUN mkdir -p /opt/jobserver/etc/notify 10 | 11 | COPY etc /opt/cloudtask/etc 12 | 13 | COPY cloudtask-center /opt/cloudtask/cloudtask-center 14 | 15 | COPY notify /opt/jobserver/etc/notify 16 | 17 | WORKDIR /opt/cloudtask 18 | 19 | VOLUME ["/opt/cloudtask/etc"] 20 | 21 | VOLUME ["/opt/cloudtask/logs"] 22 | 23 | CMD ["./cloudtask-center"] 24 | 25 | EXPOSE 8985 26 | -------------------------------------------------------------------------------- /api/listener_linux.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | func newUnixListener(addr string, tlsConfig *tls.Config) (net.Listener, error) { 11 | 12 | if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) { 13 | return nil, err 14 | } 15 | 16 | mask := syscall.Umask(0777) 17 | defer syscall.Umask(mask) 18 | l, err := newListener("unix", addr, tlsConfig) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | if err := os.Chmod(addr, 0600); err != nil { 24 | return nil, err 25 | } 26 | return l, nil 27 | } 28 | -------------------------------------------------------------------------------- /cache/driver/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "errors" 4 | 5 | type Backend string 6 | 7 | const ( 8 | //MONGO, mongodb driver. 9 | MONGO Backend = "mongo" 10 | //NGCLOUD, newegg clouddata driver. 11 | NGCLOUD Backend = "ngcloud" 12 | ) 13 | 14 | var ( 15 | ErrDriverNotSupported = errors.New("storage not supported yet, please choose one of") 16 | ErrDriverResourceNotFound = errors.New("storage resource not found.") 17 | ) 18 | 19 | //Parameters is exported 20 | //config optation key-value pairs 21 | type Parameters map[string]interface{} 22 | 23 | //StorageDriverConfigs is exported 24 | //driver configs. 25 | type StorageDriverConfigs map[string]Parameters 26 | -------------------------------------------------------------------------------- /notify/factory.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | //INotifyEndPointFactory is exported 4 | type INotifyEndPointFactory interface { 5 | CreateAPIEndPoint(endpoint EndPoint) IEndPoint 6 | CreateSMTPEndPoint(endpoint EndPoint) IEndPoint 7 | } 8 | 9 | //NotifyEndPointFactory is exported 10 | type NotifyEndPointFactory struct { 11 | INotifyEndPointFactory 12 | } 13 | 14 | //CreateAPIEndPoint is exported 15 | func (factory *NotifyEndPointFactory) CreateAPIEndPoint(endpoint EndPoint) IEndPoint { 16 | return NewAPIEndPoint(endpoint) 17 | } 18 | 19 | //CreateSMTPEndPoint is exported 20 | func (factory *NotifyEndPointFactory) CreateSMTPEndPoint(endpoint EndPoint) IEndPoint { 21 | return NewSMTPEndpoint(endpoint) 22 | } 23 | -------------------------------------------------------------------------------- /cache/units.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/cloudtask/common/models" 4 | 5 | import ( 6 | "strings" 7 | ) 8 | 9 | func containsServer(it *models.Server, servers []*models.Server) bool { 10 | 11 | if it != nil { 12 | itIpAddr := strings.TrimSpace(it.IPAddr) 13 | itName := strings.TrimSpace(it.Name) 14 | for _, server := range servers { 15 | if ipAddr := strings.TrimSpace(server.IPAddr); ipAddr != "" { 16 | if itIpAddr == ipAddr { 17 | return true 18 | } 19 | } 20 | if hostName := strings.TrimSpace(server.Name); hostName != "" { 21 | if strings.ToUpper(hostName) == strings.ToUpper(itName) { 22 | return true 23 | } 24 | } 25 | } 26 | } 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/cloudtask/libtools/gounits/system" 4 | 5 | import ( 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | 12 | jobserver, err := NewJobServer() 13 | if err != nil { 14 | log.Printf("jobserver error, %s\n", err.Error()) 15 | os.Exit(system.ErrorExitCode(err)) 16 | } 17 | 18 | defer func() { 19 | exitCode := 0 20 | if err := jobserver.Stop(); err != nil { 21 | log.Printf("jobserver stop error, %s\n", err.Error()) 22 | exitCode = system.ErrorExitCode(err) 23 | } 24 | os.Exit(exitCode) 25 | }() 26 | 27 | if err = jobserver.Startup(); err != nil { 28 | log.Printf("jobserver startup error, %s\n", err.Error()) 29 | os.Exit(system.ErrorExitCode(err)) 30 | } 31 | system.InitSignal(nil) 32 | } 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig coding styles definitions. For more information about the 2 | # properties used in this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org/ 4 | 5 | # indicate this is the root of the project 6 | root = true 7 | 8 | [*] 9 | charset = utf-8 10 | 11 | end_of_line = CRLF 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [Makefile] 19 | indent_style = tab 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | 24 | [*.go] 25 | 26 | end_of_line = CRLF 27 | indent_style = tab 28 | 29 | [*.json] 30 | charset = utf-8 31 | 32 | end_of_line = CRLF 33 | insert_final_newline = true 34 | trim_trailing_whitespace = true 35 | 36 | indent_style = space 37 | indent_size = 2 38 | 39 | -------------------------------------------------------------------------------- /api/request.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/common/models" 4 | 5 | import ( 6 | "bytes" 7 | ) 8 | 9 | //MessageRequest is exported 10 | type MessageRequest struct { 11 | Header *models.MsgHeader 12 | Reader *bytes.Reader 13 | Context *Context 14 | } 15 | 16 | //JobLogRequest is exported 17 | type JobLogRequest struct { 18 | Context *Context 19 | models.JobLog 20 | } 21 | 22 | //JobActionRequest is exported 23 | type JobActionRequest struct { 24 | Context *Context 25 | Runtime string `json:"runtime"` 26 | JobId string `json:"jobid"` 27 | Action string `json:"action"` 28 | } 29 | 30 | //ServerJobsAllocDataRequest is exported 31 | type ServerJobsAllocDataRequest struct { 32 | Context *Context 33 | Runtime string `json:"runtime"` 34 | Server string `json:"server"` 35 | } 36 | -------------------------------------------------------------------------------- /cache/action.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/cloudtask/libtools/gounits/httpx" 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "net/http" 9 | ) 10 | 11 | func putJobAction(client *httpx.HttpClient, rawurl string, location string, jobid string, action string) error { 12 | 13 | jobAction := struct { 14 | Runtime string `json:"runtime"` 15 | JobId string `json:"jobid"` 16 | Action string `json:"action"` 17 | }{ 18 | Runtime: location, 19 | JobId: jobid, 20 | Action: action, 21 | } 22 | 23 | respData, err := client.PutJSON(context.Background(), rawurl+"/cloudtask/v2/jobs/action", nil, &jobAction, nil) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | defer respData.Close() 29 | statusCode := respData.StatusCode() 30 | if statusCode >= http.StatusBadRequest { 31 | return fmt.Errorf("HTTP PUT job %s action %s failure %d.", jobid, action, statusCode) 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /etc/config.yaml: -------------------------------------------------------------------------------- 1 | version: v.2.3.4 2 | pidfile: ./jobserver.pid 3 | retrystartup: true 4 | useserverconfig: true 5 | cluster: 6 | hosts: 192.168.2.80:2181,192.168.2.81:2181,192.168.2.82:2181 7 | root: /cloudtask 8 | pulse: 30s 9 | timeout: 60s 10 | threshold: 1 11 | api: 12 | hosts: [":8985"] 13 | enablecors: true 14 | scheduler: 15 | allocmode: hash 16 | allocrecovery: 320s 17 | cache: 18 | lrusize: 1024 19 | storagedriver: 20 | mongo: 21 | hosts: 192.168.2.80:27017,192.168.2.81:27017,192.168.2.82:27017 22 | database: cloudtask 23 | #auth: { 24 | # user: datastoreAdmin, 25 | # password: ds4dev 26 | #} 27 | options: [ 28 | "maxPoolSize=20", 29 | "replicaSet=mgoCluster" 30 | #"authSource=admin" 31 | ] 32 | notifications: 33 | endpoints: 34 | - name: smtp 35 | host: smtp.example.com 36 | port: 25 37 | user: 38 | password: 39 | sender: cloudtask@example.com 40 | enabled: true 41 | logger: 42 | logfile: ./logs/jobserver.log 43 | loglevel: error 44 | logsize: 20971520 45 | ... 46 | -------------------------------------------------------------------------------- /notify/endpoint_smtp.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import "github.com/cloudtask/libtools/gounits/logger" 4 | import "gopkg.in/gomail.v1" 5 | 6 | import ( 7 | "strings" 8 | ) 9 | 10 | // SMTPEndPoint is exported 11 | type SMTPEndPoint struct { 12 | IEndPoint 13 | EndPoint 14 | mailer *gomail.Mailer 15 | } 16 | 17 | // NewSMTPEndpoint is exported 18 | func NewSMTPEndpoint(endpoint EndPoint) IEndPoint { 19 | 20 | mailer := gomail.NewMailer(endpoint.Host, endpoint.User, endpoint.Password, endpoint.Port) 21 | return &SMTPEndPoint{ 22 | EndPoint: endpoint, 23 | mailer: mailer, 24 | } 25 | } 26 | 27 | // DoEvent is exported 28 | func (endpoint *SMTPEndPoint) DoEvent(event *Event, data interface{}) { 29 | 30 | if !endpoint.Enabled { 31 | return 32 | } 33 | 34 | msg := gomail.NewMessage() 35 | msg.SetHeader("From", endpoint.Sender) 36 | msg.SetHeader("To", strings.Join(event.ContactInfo, ";")) 37 | msg.SetHeader("Subject", event.makeSubjectText()) 38 | msg.SetBody("text/html", data.(string)) 39 | if err := endpoint.mailer.Send(msg); err != nil { 40 | logger.ERROR("[#notify#] smtp endpoint post error: %s", err.Error()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudtask-center 2 | The cloudtask platform center scheduler. 3 | 4 | 5 | Responsible for task scheduling distribution, processing task failover, cluster node discovery and status management. 6 | ### Documents 7 | * [APIs Manual](./APIs.md) 8 | * [Configuration Introduction](./Configuration.md) 9 | 10 | ### Architecture 11 | 图片名称 12 | 13 | ### Usage 14 | 15 | > binary 16 | 17 | ``` bash 18 | $ ./cloudtask-center -f etc/config.yaml 19 | ``` 20 | 21 | > docker image 22 | 23 | [![](https://images.microbadger.com/badges/image/cloudtask/cloudtask-center:2.0.0.svg)](https://microbadger.com/images/cloudtask/cloudtask-center:2.0.0 "Get your own image badge on microbadger.com") 24 | [![](https://images.microbadger.com/badges/version/cloudtask/cloudtask-center:2.0.0.svg)](https://microbadger.com/images/cloudtask/cloudtask-center:2.0.0 "Get your own version badge on microbadger.com") 25 | ``` bash 26 | $ docker run -d --net=host --restart=always \ 27 | -v /opt/app/cloudtask-center/etc/config.yaml:/opt/cloudtask/etc/config.yaml \ 28 | -v /opt/app/cloudtask-center/logs:/opt/cloudtask/logs \ 29 | -v /etc/localtime:/etc/localtime \ 30 | --name=cloudtask-center \ 31 | cloudtask/cloudtask-center:2.0.0 32 | ``` 33 | 34 | 35 | ## License 36 | cloudtask source code is licensed under the [Apache Licence 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 37 | -------------------------------------------------------------------------------- /api/response.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/common/models" 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | var ( 10 | ErrRequestSuccessed = errors.New("request successed.") 11 | ErrRequestAccepted = errors.New("request accepted.") 12 | ErrRequestResolveInvaild = errors.New("request resolve invaild.") 13 | ErrRequestNotFound = errors.New("request resource not found.") 14 | ErrRequestServerException = errors.New("request server exception.") 15 | ErrRequestAllocNotFound = errors.New("request resource not found in cache alloc.") 16 | ) 17 | 18 | //HandleResponse is exportyed 19 | type HandleResponse interface { 20 | SetContent(content string) 21 | SetData(data interface{}) 22 | } 23 | 24 | //ResponseImpl is exported 25 | type ResponseImpl struct { 26 | HandleResponse `json:"-,omitempty"` 27 | Content string `json:"content"` 28 | Data interface{} `json:"data,omitempty"` 29 | } 30 | 31 | //SetContent is exported 32 | func (response *ResponseImpl) SetContent(content string) { 33 | response.Content = content 34 | } 35 | 36 | //SetData is exported 37 | func (response *ResponseImpl) SetData(data interface{}) { 38 | response.Data = data 39 | } 40 | 41 | //GetServersResponse is exported 42 | type GetServersResponse struct { 43 | Servers []*models.Server `json:"servers"` 44 | } 45 | 46 | //GetJobBaseResponse is exported 47 | type GetJobBaseResponse struct { 48 | JobBase *models.JobBase `json:"jobbase"` 49 | } 50 | 51 | //GetJobsAllocDataResponse is exported 52 | type GetJobsAllocDataResponse struct { 53 | JobsAlloc *models.JobsAllocData `json:"alloc"` 54 | } 55 | -------------------------------------------------------------------------------- /cache/driver/driver.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 4 | import "github.com/cloudtask/common/models" 5 | 6 | import ( 7 | "fmt" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | type Initialize func(parameters types.Parameters) (StorageDriver, error) 13 | 14 | var ( 15 | initializes = make(map[types.Backend]Initialize) 16 | supportedBackend = func() string { 17 | keys := make([]string, 0, len(initializes)) 18 | for k := range initializes { 19 | keys = append(keys, string(k)) 20 | } 21 | sort.Strings(keys) 22 | return strings.Join(keys, ",") 23 | }() 24 | ) 25 | 26 | //StorageDriver is exported 27 | type StorageDriver interface { 28 | Open() error 29 | Close() 30 | SetConfigParameters(parameters types.Parameters) 31 | GetLocationsName() []string 32 | GetLocation(location string) *models.WorkLocation 33 | GetLocationSimpleJobs(location string) []*models.SimpleJob 34 | GetSimpleJob(jobid string) *models.SimpleJob 35 | GetJobs() []*models.Job 36 | GetStateJobs(state int) []*models.Job 37 | GetLocationJobs(location string) []*models.Job 38 | GetGroupJobs(groupid string) []*models.Job 39 | GetJob(jobid string) *models.Job 40 | SetJob(job *models.Job) 41 | SetJobLog(joblog *models.JobLog) 42 | } 43 | 44 | func NewDriver(backend types.Backend, parameters types.Parameters) (StorageDriver, error) { 45 | if init, exists := initializes[backend]; exists { 46 | return init(parameters) 47 | } 48 | return nil, fmt.Errorf("%s %s", types.ErrDriverNotSupported, supportedBackend) 49 | } 50 | 51 | func AddDriver(backend types.Backend, init Initialize) { 52 | initializes[backend] = init 53 | } 54 | -------------------------------------------------------------------------------- /notify/endpoint_api.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import "github.com/cloudtask/libtools/gounits/logger" 4 | import "github.com/cloudtask/libtools/gounits/httpx" 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "net/http" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // APIEndPoint is exported 15 | type APIEndPoint struct { 16 | IEndPoint 17 | EndPoint 18 | client *httpx.HttpClient 19 | } 20 | 21 | // NewAPIEndPoint is exported 22 | func NewAPIEndPoint(endpoint EndPoint) IEndPoint { 23 | 24 | client := httpx.NewClient(). 25 | SetTransport(&http.Transport{ 26 | Proxy: http.ProxyFromEnvironment, 27 | DialContext: (&net.Dialer{ 28 | Timeout: 30 * time.Second, 29 | KeepAlive: 60 * time.Second, 30 | }).DialContext, 31 | DisableKeepAlives: false, 32 | MaxIdleConns: 10, 33 | MaxIdleConnsPerHost: 10, 34 | IdleConnTimeout: 60 * time.Second, 35 | TLSHandshakeTimeout: http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout, 36 | ExpectContinueTimeout: http.DefaultTransport.(*http.Transport).ExpectContinueTimeout, 37 | }) 38 | 39 | return &APIEndPoint{ 40 | EndPoint: endpoint, 41 | client: client, 42 | } 43 | } 44 | 45 | // DoEvent is exported 46 | func (endpoint *APIEndPoint) DoEvent(event *Event, data interface{}) { 47 | 48 | if !endpoint.Enabled { 49 | return 50 | } 51 | 52 | value := map[string]interface{}{ 53 | "From": endpoint.Sender, 54 | "To": strings.Join(event.ContactInfo, ";"), 55 | "Subject": event.makeSubjectText(), 56 | "Body": data, 57 | "ContentType": "HTML", 58 | "MailType": "Smtp", 59 | "SmtpSetting": map[string]interface{}{}, 60 | } 61 | 62 | resp, err := endpoint.client.PostJSON(context.Background(), endpoint.URL, nil, value, nil) 63 | if err != nil { 64 | logger.ERROR("[#notify#] api endpoint error: %s", err.Error()) 65 | return 66 | } 67 | resp.Close() 68 | } 69 | -------------------------------------------------------------------------------- /notify/typedef.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | //EndPoint is exported 4 | type EndPoint struct { 5 | Name string `yaml:"name"` 6 | URL string `yaml:"url"` 7 | Enabled bool `yaml:"enabled"` 8 | Sender string `yaml:"sender"` 9 | Host string `yaml:"host"` 10 | Port int `yaml:"port"` 11 | User string `yaml:"user"` 12 | Password string `yaml:"password"` 13 | } 14 | 15 | //Notifications is exported 16 | type Notifications struct { 17 | EndPoints []EndPoint `yaml:"endpoints,omitempty"` 18 | } 19 | 20 | //Server is exported 21 | type Server struct { 22 | IP string 23 | Name string 24 | State string 25 | } 26 | 27 | //WatchLocation is exported 28 | type WatchLocation struct { 29 | Name string 30 | ContactInfo []string 31 | Servers []*Server 32 | } 33 | 34 | //WatchLocations is exported 35 | type WatchLocations map[string]*WatchLocation 36 | 37 | //AddServer is exported 38 | func (location *WatchLocation) AddServer(ip string, name string, state string) { 39 | 40 | for _, server := range location.Servers { 41 | if server.IP == ip || server.Name == name { 42 | return 43 | } 44 | } 45 | 46 | location.Servers = append(location.Servers, &Server{ 47 | IP: ip, 48 | Name: name, 49 | State: state, 50 | }) 51 | } 52 | 53 | //AddContactInfo is exported 54 | func (location *WatchLocation) AddContactInfo(value string) { 55 | 56 | for _, contactname := range location.ContactInfo { 57 | if contactname == value { 58 | return 59 | } 60 | } 61 | location.ContactInfo = append(location.ContactInfo, value) 62 | } 63 | 64 | //JobResult is exported 65 | type JobResult struct { 66 | JobName string 67 | Directory string 68 | Location string 69 | Server string 70 | Execat string 71 | IsSuccessd bool 72 | Content string 73 | Stdout string 74 | Errout string 75 | Execerr string 76 | } 77 | 78 | //WatchJobNotify is exported 79 | type WatchJobNotify struct { 80 | Name string 81 | ContactInfo []string 82 | JobResult 83 | } 84 | -------------------------------------------------------------------------------- /cache/driver/ngcloud/parse.go: -------------------------------------------------------------------------------- 1 | package ngcloud 2 | 3 | import "github.com/cloudtask/common/models" 4 | import "github.com/cloudtask/libtools/gounits/httpx" 5 | 6 | //PageData is exported 7 | type PageData struct { 8 | PageSize int `json:"pageSize"` 9 | PageIndex int `json:"pageIndex"` 10 | TotalRows int `json:"total_rows"` 11 | } 12 | 13 | //LocationsNameResponse is exported 14 | type LocationsNameResponse struct { 15 | PageData 16 | Names []string 17 | } 18 | 19 | func parseLocationsNameResponse(respData *httpx.HttpResponse) (*LocationsNameResponse, error) { 20 | 21 | data := struct { 22 | PageData 23 | Rows []struct { 24 | Location string `json:"location"` 25 | } `json:"rows"` 26 | }{} 27 | 28 | if err := respData.JSON(&data); err != nil { 29 | return nil, err 30 | } 31 | 32 | response := &LocationsNameResponse{ 33 | PageData: data.PageData, 34 | Names: []string{}, 35 | } 36 | 37 | for _, row := range data.Rows { 38 | response.Names = append(response.Names, row.Location) 39 | } 40 | return response, nil 41 | } 42 | 43 | //JobsResponse is exported 44 | type JobsResponse struct { 45 | PageData 46 | Jobs []*models.Job 47 | } 48 | 49 | func parseJobsResponse(respData *httpx.HttpResponse) (*JobsResponse, error) { 50 | 51 | data := struct { 52 | PageData 53 | Rows []*models.Job `json:"rows"` 54 | }{} 55 | 56 | if err := respData.JSON(&data); err != nil { 57 | return nil, err 58 | } 59 | 60 | return &JobsResponse{ 61 | PageData: data.PageData, 62 | Jobs: data.Rows, 63 | }, nil 64 | } 65 | 66 | //SimpleJobsResponse is exported 67 | type SimpleJobsResponse struct { 68 | PageData 69 | Jobs []*models.SimpleJob 70 | } 71 | 72 | func parseSimpleJobsResponse(respData *httpx.HttpResponse) (*SimpleJobsResponse, error) { 73 | 74 | data := struct { 75 | PageData 76 | Rows []*models.SimpleJob `json:"rows"` 77 | }{} 78 | 79 | if err := respData.JSON(&data); err != nil { 80 | return nil, err 81 | } 82 | 83 | return &SimpleJobsResponse{ 84 | PageData: data.PageData, 85 | Jobs: data.Rows, 86 | }, nil 87 | } 88 | -------------------------------------------------------------------------------- /notify/events.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import "github.com/cloudtask/libtools/gounits/rand" 4 | 5 | import ( 6 | "bytes" 7 | "html/template" 8 | "time" 9 | ) 10 | 11 | //EventType is exported 12 | type EventType int 13 | 14 | const ( 15 | //LocationServersEvent is exported 16 | //cluster discovery watch servers event 17 | LocationServersEvent EventType = 1000 18 | //JobNotifyEvent is exported 19 | //job executed notify event 20 | JobNotifyEvent EventType = 1001 21 | ) 22 | 23 | //eventsTextMap is exported 24 | var eventsTextMap = map[EventType]string{ 25 | LocationServersEvent: "LocationServersEvent", 26 | JobNotifyEvent: "JobNotifyEvent", 27 | } 28 | 29 | //Event is exported 30 | type Event struct { 31 | ID string 32 | Type EventType 33 | Name string 34 | Error error 35 | ContactInfo []string 36 | Endpoints []IEndPoint 37 | data map[string]interface{} 38 | } 39 | 40 | //NewEvent is exported 41 | func NewEvent(eventType EventType, description string, err error, contactInfo []string, endpoints []IEndPoint) *Event { 42 | 43 | seed := time.Now() 44 | event := &Event{ 45 | ID: rand.UUID(true), 46 | Type: eventType, 47 | Name: eventsTextMap[eventType], 48 | Error: err, 49 | ContactInfo: contactInfo, 50 | Endpoints: endpoints, 51 | } 52 | 53 | event.data = map[string]interface{}{ 54 | "ID": event.ID, 55 | "Event": event.Name, 56 | "Description": description, 57 | "Timestamp": seed.UnixNano(), 58 | "Datetime": seed, 59 | } 60 | 61 | if err != nil { 62 | event.data["Exception"] = err.Error() 63 | } 64 | return event 65 | } 66 | 67 | //Dispatch is exported 68 | func (event *Event) dispatch(templateBody string) { 69 | 70 | if len(templateBody) > 0 { 71 | var buf bytes.Buffer 72 | t := template.New("") 73 | t.Parse(templateBody) 74 | t.Execute(&buf, event.data) 75 | for _, endPoint := range event.Endpoints { 76 | endPoint.DoEvent(event, buf.String()) 77 | } 78 | } 79 | } 80 | 81 | //makeSubjectText is exported 82 | func (event *Event) makeSubjectText() string { 83 | 84 | subjectPrefix := "(info)" 85 | if event.Error != nil { 86 | subjectPrefix = "(error)" 87 | } 88 | return subjectPrefix + " CloudTask Notification" 89 | } 90 | -------------------------------------------------------------------------------- /api/router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/cloudtask-center/etc" 4 | import "github.com/cloudtask/libtools/gzkwrapper" 5 | import "github.com/gorilla/mux" 6 | 7 | import ( 8 | "net/http" 9 | ) 10 | 11 | type handler func(c *Context) error 12 | 13 | var routes = map[string]map[string]handler{ 14 | "GET": { 15 | "/cloudtask/v2/_ping": ping, 16 | "/cloudtask/v2/jobs/{jobid}/base": getJobBase, 17 | "/cloudtask/v2/runtimes/{runtime}/jobsalloc": getJobsAllocData, 18 | "/cloudtask/v2/runtimes/{runtime}/{server}/jobsalloc": getServerJobsAllocData, 19 | "/cloudtask/v2/runtimes/{runtime}/servers": getServers, 20 | }, 21 | "POST": { 22 | "/cloudtask/v2/messages": postMessages, 23 | "/cloudtask/v2/logs": postLogs, 24 | }, 25 | "PUT": { 26 | "/cloudtask/v2/jobs/action": putJobAction, 27 | }, 28 | } 29 | 30 | func NewRouter(enableCors bool, store Store) *mux.Router { 31 | 32 | router := mux.NewRouter() 33 | for method, mappings := range routes { 34 | for route, handler := range mappings { 35 | routemethod := method 36 | routepattern := route 37 | routehandler := handler 38 | wrap := func(w http.ResponseWriter, r *http.Request) { 39 | if enableCors { 40 | writeCorsHeaders(w, r) 41 | } 42 | c := NewContext(w, r, store) 43 | routehandler(c) 44 | } 45 | router.Path(routepattern).Methods(routemethod).HandlerFunc(wrap) 46 | if enableCors { 47 | optionsmethod := "OPTIONS" 48 | optionshandler := optionsHandler 49 | wrap := func(w http.ResponseWriter, r *http.Request) { 50 | if enableCors { 51 | writeCorsHeaders(w, r) 52 | } 53 | c := NewContext(w, r, store) 54 | optionshandler(c) 55 | } 56 | router.Path(routepattern).Methods(optionsmethod).HandlerFunc(wrap) 57 | } 58 | } 59 | } 60 | return router 61 | } 62 | 63 | func ping(c *Context) error { 64 | 65 | pangData := struct { 66 | AppCode string `json:"app"` 67 | ServerKey string `json:"key"` 68 | NodeData *gzkwrapper.NodeData `json:"node"` 69 | SystemConfig *etc.Configuration `json:"systemconfig"` 70 | }{ 71 | AppCode: c.Get("AppCode").(string), 72 | ServerKey: c.Get("ServerKey").(string), 73 | NodeData: c.Get("NodeData").(*gzkwrapper.NodeData), 74 | SystemConfig: c.Get("SystemConfig").(*etc.Configuration), 75 | } 76 | return c.JSON(http.StatusOK, pangData) 77 | } 78 | 79 | func optionsHandler(c *Context) error { 80 | 81 | c.WriteHeader(http.StatusOK) 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /api/resolve.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/common/models" 4 | import "github.com/gorilla/mux" 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "io/ioutil" 10 | "strings" 11 | ) 12 | 13 | //ResolveJobBaseRequest is exported 14 | func ResolveJobBaseRequest(c *Context) string { 15 | 16 | vars := mux.Vars(c.request) 17 | jobid := strings.TrimSpace(vars["jobid"]) 18 | if len(jobid) == 0 { 19 | return "" 20 | } 21 | return jobid 22 | } 23 | 24 | //ResolveJobsAllocDataRequest is exported 25 | func ResolveJobsAllocDataRequest(c *Context) string { 26 | 27 | vars := mux.Vars(c.request) 28 | runtime := strings.TrimSpace(vars["runtime"]) 29 | if len(runtime) == 0 { 30 | return "" 31 | } 32 | return runtime 33 | } 34 | 35 | //ResolveServerJobsAllocDataRequest is exported 36 | func ResolveServerJobsAllocDataRequest(c *Context) *ServerJobsAllocDataRequest { 37 | 38 | vars := mux.Vars(c.request) 39 | runtime := strings.TrimSpace(vars["runtime"]) 40 | if len(runtime) == 0 { 41 | return nil 42 | } 43 | 44 | server := strings.TrimSpace(vars["server"]) 45 | if len(server) == 0 { 46 | return nil 47 | } 48 | 49 | return &ServerJobsAllocDataRequest{ 50 | Context: c, 51 | Runtime: runtime, 52 | Server: server, 53 | } 54 | } 55 | 56 | //ResolveServersRequest is exported 57 | func ResolveServersRequest(c *Context) string { 58 | 59 | vars := mux.Vars(c.request) 60 | runtime := strings.TrimSpace(vars["runtime"]) 61 | if len(runtime) == 0 { 62 | return "" 63 | } 64 | return runtime 65 | } 66 | 67 | //ResolveJobActionRequest is exported 68 | func ResolveJobActionRequest(c *Context) *JobActionRequest { 69 | 70 | buf, err := ioutil.ReadAll(c.request.Body) 71 | if err != nil { 72 | return nil 73 | } 74 | 75 | request := &JobActionRequest{} 76 | if err := json.NewDecoder(bytes.NewReader(buf)).Decode(request); err != nil { 77 | return nil 78 | } 79 | 80 | request.Context = c 81 | return request 82 | } 83 | 84 | //ResolveMessageRequest is exported 85 | func ResolveMessageRequest(c *Context) *MessageRequest { 86 | 87 | buf, err := ioutil.ReadAll(c.Request().Body) 88 | if err != nil { 89 | return nil 90 | } 91 | 92 | msgHeader := &models.MsgHeader{} 93 | reader := bytes.NewReader(buf) 94 | if err := json.NewDecoder(reader).Decode(msgHeader); err != nil { 95 | return nil 96 | } 97 | 98 | if _, err := reader.Seek(0, 0); err != nil { 99 | return nil 100 | } 101 | 102 | return &MessageRequest{ 103 | Header: msgHeader, 104 | Reader: reader, 105 | Context: c, 106 | } 107 | } 108 | 109 | //ResloveJogRequest is exported 110 | func ResloveJogRequest(c *Context) *JobLogRequest { 111 | 112 | buf, err := ioutil.ReadAll(c.Request().Body) 113 | if err != nil { 114 | return nil 115 | } 116 | 117 | request := &JobLogRequest{} 118 | if err := json.NewDecoder(bytes.NewReader(buf)).Decode(request); err != nil { 119 | return nil 120 | } 121 | 122 | request.Context = c 123 | return request 124 | } 125 | -------------------------------------------------------------------------------- /api/server.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/cloudtask-center/api/middleware" 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | type Dispatcher struct { 14 | handler http.Handler 15 | } 16 | 17 | func (dispatcher *Dispatcher) SetHandler(handler http.Handler) { 18 | 19 | dispatcher.handler = handler 20 | } 21 | 22 | func (dispatcher *Dispatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) { 23 | 24 | if dispatcher.handler == nil { 25 | httpError(w, "API Dispatcher Invalid.", http.StatusInternalServerError) 26 | return 27 | } 28 | handler := middleware.Logger(dispatcher.handler) 29 | handler.ServeHTTP(w, r) 30 | } 31 | 32 | var store = make(Store) 33 | 34 | type Server struct { 35 | hosts []string 36 | tlsConfig *tls.Config 37 | dispatcher *Dispatcher 38 | } 39 | 40 | func NewServer(hosts []string, enableCors bool, tlsConfig *tls.Config) *Server { 41 | 42 | router := NewRouter(enableCors, store) 43 | return &Server{ 44 | hosts: hosts, 45 | tlsConfig: tlsConfig, 46 | dispatcher: &Dispatcher{ 47 | handler: router, 48 | }, 49 | } 50 | } 51 | 52 | func RegisterStore(name string, value interface{}) { 53 | 54 | store[name] = value 55 | } 56 | 57 | func (server *Server) ListenHosts() []string { 58 | 59 | return server.hosts 60 | } 61 | 62 | func (server *Server) SetHandler(handler http.Handler) { 63 | 64 | server.dispatcher.SetHandler(handler) 65 | } 66 | 67 | func (server *Server) Startup() error { 68 | 69 | errorsCh := make(chan error, len(server.hosts)) 70 | for _, host := range server.hosts { 71 | protoAddrParts := strings.SplitN(host, "://", 2) 72 | if len(protoAddrParts) == 1 { 73 | protoAddrParts = append([]string{"tcp"}, protoAddrParts...) 74 | } 75 | 76 | go func() { 77 | var ( 78 | err error 79 | l net.Listener 80 | s = http.Server{ 81 | Addr: protoAddrParts[1], 82 | Handler: server.dispatcher, 83 | } 84 | ) 85 | 86 | switch protoAddrParts[0] { 87 | case "unix": 88 | l, err = newUnixListener(protoAddrParts[1], server.tlsConfig) 89 | case "tcp": 90 | l, err = newListener("tcp", protoAddrParts[1], server.tlsConfig) 91 | default: 92 | err = fmt.Errorf("API UnSupported Protocol:%q", protoAddrParts[0]) 93 | } 94 | if err != nil { 95 | errorsCh <- err 96 | } else { 97 | errorsCh <- s.Serve(l) 98 | } 99 | }() 100 | } 101 | 102 | for i := 0; i < len(server.hosts); i++ { 103 | err := <-errorsCh 104 | if err != nil { 105 | return err 106 | } 107 | } 108 | return nil 109 | } 110 | 111 | func newListener(proto string, addr string, tlsConfig *tls.Config) (net.Listener, error) { 112 | 113 | l, err := net.Listen(proto, addr) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | if tlsConfig != nil { 119 | tlsConfig.NextProtos = []string{"http/1.1"} 120 | l = tls.NewListener(l, tlsConfig) 121 | } 122 | return l, nil 123 | } 124 | -------------------------------------------------------------------------------- /server/handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache" 4 | import "github.com/cloudtask/libtools/gounits/logger" 5 | import "github.com/cloudtask/libtools/gzkwrapper" 6 | 7 | func (server *CenterServer) OnZkWrapperNodeHandlerFunc(nodestore *gzkwrapper.NodeStore) { 8 | 9 | newTotalSize := nodestore.NewTotalSize() 10 | logger.INFO("[#server#] cluster discovery healthy nodes %d", newTotalSize) 11 | for key, nodedata := range nodestore.New { 12 | logger.INFO("[#server#] %s node %s(%s) healthy.", nodedata.Location, key, nodedata.IpAddr) 13 | server.CacheRepository.CreateWorker(key, nodedata) 14 | } 15 | 16 | deadTotalSize := nodestore.DeadTotalSize() 17 | logger.INFO("[#server#] cluster discovery deadly nodes %d", deadTotalSize) 18 | for key, nodedata := range nodestore.Dead { 19 | logger.INFO("[#server#] %s node %s(%s) deadly.", nodedata.Location, key, nodedata.IpAddr) 20 | server.CacheRepository.RemoveWorker(key, nodedata) 21 | } 22 | 23 | recoveryTotalSize := nodestore.RecoveryTotalSize() 24 | logger.INFO("[#server#] cluster discovery recovery nodes %d", recoveryTotalSize) 25 | for key, nodedata := range nodestore.Recovery { 26 | logger.INFO("[#server#] %s node %s(%s) recovery.", nodedata.Location, key, nodedata.IpAddr) 27 | server.CacheRepository.ChangeWorker(key, nodedata) 28 | } 29 | 30 | if newTotalSize > 0 || deadTotalSize > 0 { 31 | //worker上下线,开始调节重新分配任务到worker 32 | server.Scheduler.QuickAlloc(nodestore.New, nodestore.Dead) 33 | //worker上下线事件通知 34 | server.postNodesWatchNotifyEvent(nodestore.New, nodestore.Dead) 35 | } 36 | } 37 | 38 | func (server *CenterServer) OnZkWrapperPulseHandlerFunc(key string, nodedata *gzkwrapper.NodeData, err error) { 39 | //中心服务器不实现心跳 40 | } 41 | 42 | func (server *CenterServer) OnSeverConfigsWatchHandlerFunc(path string, data []byte, err error) { 43 | 44 | if err != nil { 45 | logger.ERROR("[#server#] watch server config handler error, %s", err) 46 | return 47 | } 48 | 49 | if err = server.RefreshServerConfig(data); err != nil { 50 | logger.ERROR("[#server#] watch server config save error, %s", err) 51 | return 52 | } 53 | logger.INFO("[#server#] watch server config changed.") 54 | } 55 | 56 | func (server *CenterServer) OnAllocCacheHandlerFunc(event cache.AllocEvent, location string, data []byte, err error) { 57 | 58 | if err != nil { 59 | logger.ERROR("[#server#] %s cache handler %s error, %s.", location, event.String(), err) 60 | return 61 | } 62 | 63 | if len(data) <= 0 { 64 | logger.ERROR("[#server#] %s cache handler %s error, alloc data invalid.", location, event.String()) 65 | return 66 | } 67 | 68 | logger.INFO("[#server#] %s cache handler %s.", location, event.String()) 69 | switch event { 70 | case cache.ALLOC_CREATED_EVENT: 71 | { 72 | server.postCacheAlloc(location, data) 73 | } 74 | case cache.ALLOC_CHANGED_EVENT: 75 | { 76 | server.putCacheAlloc(location, data) 77 | } 78 | case cache.ALLOC_REMOVED_EVENT: 79 | { 80 | server.putCacheAlloc(location, data) 81 | } 82 | } 83 | } 84 | 85 | func (server *CenterServer) OnNodeCacheHandlerFunc(event cache.NodeEvent, location string, worker *cache.Worker) { 86 | 87 | logger.INFO("[#server#] cache handler %s %s, worker %s, %s(%s).", location, event.String(), worker.Key, worker.Name, worker.IPAddr) 88 | } 89 | -------------------------------------------------------------------------------- /api/context.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/libtools/gounits/rand" 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | type ( 13 | Store map[string]interface{} 14 | 15 | Response struct { 16 | writer http.ResponseWriter 17 | status int 18 | size int64 19 | } 20 | 21 | Context struct { 22 | context.Context 23 | ID string 24 | request *http.Request 25 | response *Response 26 | query url.Values 27 | store Store 28 | } 29 | ) 30 | 31 | func NewResponse(w http.ResponseWriter) *Response { 32 | 33 | return &Response{ 34 | writer: w, 35 | } 36 | } 37 | 38 | func (r *Response) SetWriter(w http.ResponseWriter) { 39 | 40 | r.writer = w 41 | } 42 | 43 | func (r *Response) Header() http.Header { 44 | 45 | return r.writer.Header() 46 | } 47 | 48 | func (r *Response) Writer() http.ResponseWriter { 49 | 50 | return r.writer 51 | } 52 | 53 | func (r *Response) WriteHeader(code int) { 54 | 55 | r.status = code 56 | r.writer.WriteHeader(code) 57 | } 58 | 59 | func (r *Response) Write(b []byte) (int, error) { 60 | 61 | n, err := r.writer.Write(b) 62 | if err == nil { 63 | r.size += int64(n) 64 | } 65 | return n, err 66 | } 67 | 68 | func (r *Response) Flush() { 69 | 70 | r.writer.(http.Flusher).Flush() 71 | } 72 | 73 | func (r *Response) Size() int64 { 74 | 75 | return r.size 76 | } 77 | 78 | func (r *Response) Status() int { 79 | 80 | return r.status 81 | } 82 | 83 | func NewContext(w http.ResponseWriter, r *http.Request, store Store) *Context { 84 | 85 | return &Context{ 86 | ID: rand.UUID(true), 87 | request: r, 88 | response: NewResponse(w), 89 | store: store, 90 | } 91 | } 92 | 93 | func (c *Context) Request() *http.Request { 94 | 95 | return c.request 96 | } 97 | 98 | func (c *Context) Response() *Response { 99 | 100 | return c.response 101 | } 102 | 103 | func (c *Context) Get(key string) interface{} { 104 | 105 | return c.store[key] 106 | } 107 | 108 | func (c *Context) Set(key string, v interface{}) { 109 | 110 | if c.store == nil { 111 | c.store = make(Store) 112 | } 113 | c.store[key] = v 114 | } 115 | 116 | func (c *Context) WriteHeader(code int) { 117 | 118 | c.response.WriteHeader(code) 119 | } 120 | 121 | func (c *Context) Query(name string) string { 122 | 123 | if c.query == nil { 124 | c.query = c.request.URL.Query() 125 | } 126 | return c.query.Get(name) 127 | } 128 | 129 | func (c *Context) Form(name string) string { 130 | 131 | return c.request.FormValue(name) 132 | } 133 | 134 | func (c *Context) JSON(code int, v interface{}) error { 135 | 136 | data, err := json.Marshal(v) 137 | if err != nil { 138 | return err 139 | } 140 | c.response.Header().Set("Content-Type", "application/json; charset=utf-8") 141 | c.response.WriteHeader(code) 142 | if _, err := c.response.Write(data); err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | 148 | func (c *Context) JSONP(code int, callback string, v interface{}) error { 149 | 150 | b, err := json.Marshal(v) 151 | if err != nil { 152 | return err 153 | } 154 | c.response.Header().Set("Content-Type", "application/javascript; charset=utf-8") 155 | c.response.WriteHeader(code) 156 | data := []byte(callback + "(") 157 | data = append(data, b...) 158 | data = append(data, []byte(");")...) 159 | if _, err := c.response.Write(data); err != nil { 160 | return err 161 | } 162 | return nil 163 | } 164 | -------------------------------------------------------------------------------- /notify/notify.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | //notify template string 11 | var ( 12 | LocationServersNotifyBody string 13 | JobResultNotifyBody string 14 | ) 15 | 16 | //NotifySender is exported 17 | type NotifySender struct { 18 | sync.RWMutex 19 | initWatch bool 20 | endPoints []IEndPoint 21 | events map[string]*Event 22 | } 23 | 24 | func init() { 25 | 26 | var ( 27 | buf []byte 28 | err error 29 | ) 30 | 31 | if buf, err = ioutil.ReadFile("./notify/templates/location.html"); err == nil { 32 | LocationServersNotifyBody = string(buf) 33 | } 34 | 35 | if buf, err = ioutil.ReadFile("./notify/templates/job.html"); err == nil { 36 | JobResultNotifyBody = string(buf) 37 | } 38 | } 39 | 40 | //NewNotifySender is exported 41 | func NewNotifySender(endPoints []EndPoint) *NotifySender { 42 | 43 | sender := &NotifySender{ 44 | initWatch: true, 45 | endPoints: []IEndPoint{}, 46 | events: make(map[string]*Event), 47 | } 48 | 49 | factory := &NotifyEndPointFactory{} 50 | sender.Lock() 51 | for _, endPoint := range endPoints { 52 | switch strings.ToUpper(endPoint.Name) { 53 | case "API": 54 | apiEndPoint := factory.CreateAPIEndPoint(endPoint) 55 | sender.endPoints = append(sender.endPoints, apiEndPoint) 56 | case "SMTP": 57 | smtpEndPoint := factory.CreateSMTPEndPoint(endPoint) 58 | sender.endPoints = append(sender.endPoints, smtpEndPoint) 59 | } 60 | } 61 | sender.Unlock() 62 | 63 | go func() { 64 | time.Sleep(60 * time.Second) 65 | sender.initWatch = false 66 | }() 67 | return sender 68 | } 69 | 70 | //AddLocationServersEvent is exported 71 | func (sender *NotifySender) AddLocationServersEvent(description string, watchLocation *WatchLocation) { 72 | 73 | event := NewEvent(LocationServersEvent, description, nil, watchLocation.ContactInfo, sender.endPoints) 74 | event.data["WatchLocation"] = watchLocation 75 | sender.Lock() 76 | sender.events[event.ID] = event 77 | go sender.dispatchEvents() 78 | sender.Unlock() 79 | } 80 | 81 | //AddJobNotifyEvent is exported 82 | func (sender *NotifySender) AddJobNotifyEvent(description string, watchJobNotify *WatchJobNotify) { 83 | 84 | event := NewEvent(JobNotifyEvent, description, nil, watchJobNotify.ContactInfo, sender.endPoints) 85 | event.data["WatchJobNotify"] = watchJobNotify 86 | sender.Lock() 87 | sender.events[event.ID] = event 88 | go sender.dispatchEvents() 89 | sender.Unlock() 90 | } 91 | 92 | //dispatchEvents is exported 93 | //dispatch all events. 94 | func (sender *NotifySender) dispatchEvents() { 95 | 96 | sender.Lock() 97 | for { 98 | if len(sender.events) == 0 { 99 | break 100 | } 101 | if !sender.initWatch { 102 | wgroup := sync.WaitGroup{} 103 | for _, event := range sender.events { 104 | if len(event.ContactInfo) > 0 { 105 | wgroup.Add(1) 106 | go func(e *Event) { 107 | templateBody := getNotifyTemplateBody(e.Type) 108 | if templateBody != "" { 109 | e.dispatch(templateBody) 110 | } 111 | wgroup.Done() 112 | }(event) 113 | } 114 | } 115 | wgroup.Wait() 116 | } 117 | for _, event := range sender.events { 118 | delete(sender.events, event.ID) 119 | } 120 | } 121 | sender.Unlock() 122 | } 123 | 124 | func getNotifyTemplateBody(evt EventType) string { 125 | 126 | if evt == LocationServersEvent { 127 | return LocationServersNotifyBody 128 | } else if evt == JobNotifyEvent { 129 | return JobResultNotifyBody 130 | } 131 | return "" 132 | } 133 | -------------------------------------------------------------------------------- /notify/templates/location.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CloudTask Notify 7 | 30 | 31 | 32 | 33 | 34 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {{if .Exception}} 62 | 63 | 64 | 65 | 66 | {{end}} 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {{if .WatchLocation}} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 93 | 94 | {{end}} 95 | 96 | 97 | 98 |
35 | 36 |
37 | Cloud 38 | Task 39 |
40 |
41 |
 
ID{{.ID}}
Event{{.Event}}
Description{{.Description}}
Exception{{.Exception}}
Timestamp{{.Timestamp}}
Datetime{{.Datetime}}
Location{{.WatchLocation.Name}}
Servers 83 | 84 | {{range .WatchLocation.Servers}} 85 | {{if eq .State "Healthy"}} 86 |
{{.IP}} {{.Name}} {{.State}}
87 | {{else}} 88 |
{{.IP}} {{.Name}} {{.State}}
89 | {{end}} 90 | {{end}} 91 |
92 |
 
99 | 100 | 101 | -------------------------------------------------------------------------------- /jobserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/cloudtask/cloudtask-center/api" 4 | import "github.com/cloudtask/cloudtask-center/etc" 5 | import "github.com/cloudtask/cloudtask-center/server" 6 | import "github.com/cloudtask/libtools/gounits/flocker" 7 | import "github.com/cloudtask/libtools/gounits/logger" 8 | import "github.com/cloudtask/libtools/gounits/rand" 9 | import "github.com/cloudtask/libtools/gounits/system" 10 | 11 | import ( 12 | "flag" 13 | "fmt" 14 | "os" 15 | "os/exec" 16 | "time" 17 | ) 18 | 19 | //JobServer is exported 20 | type JobServer struct { 21 | RetryStartup bool 22 | Locker *flocker.FileLocker 23 | CenterServer *server.CenterServer 24 | APIServer *api.Server 25 | } 26 | 27 | //AppCode is exported 28 | var AppCode string 29 | 30 | func init() { 31 | 32 | if appFile, err := exec.LookPath(os.Args[0]); err == nil { 33 | AppCode, _ = system.ReadFileMD5Code(appFile) 34 | } 35 | } 36 | 37 | //NewJobServer is exported 38 | func NewJobServer() (*JobServer, error) { 39 | 40 | var filePath string 41 | flag.StringVar(&filePath, "f", "./etc/config.yaml", "jobserver etc file.") 42 | flag.Parse() 43 | if err := etc.New(filePath); err != nil { 44 | return nil, err 45 | } 46 | 47 | logConfigs := etc.LoggerConfigs() 48 | if logConfigs == nil { 49 | return nil, fmt.Errorf("logger configs invalid.") 50 | } 51 | logger.OPEN(logConfigs) 52 | 53 | key, err := rand.UUIDFile("./jobserver.key") //服务器唯一标识文件 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | var fLocker *flocker.FileLocker 59 | if pidFile := etc.PidFile(); pidFile != "" { 60 | fLocker = flocker.NewFileLocker(pidFile, 0) 61 | } 62 | 63 | centerServer, err := server.NewCenterServer(key) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | api.RegisterStore("AppCode", AppCode) 69 | api.RegisterStore("SystemConfig", etc.SystemConfig) 70 | api.RegisterStore("ServerKey", centerServer.Key) 71 | api.RegisterStore("NodeData", centerServer.Data) 72 | api.RegisterStore("CacheRepository", centerServer.CacheRepository) 73 | api.RegisterStore("Scheduler", centerServer.Scheduler) 74 | api.RegisterStore("MessageCache", centerServer.MessageCache) 75 | api.RegisterStore("NotifySender", centerServer.NotifySender) 76 | apiServer := api.NewServer(etc.SystemConfig.API.Hosts, etc.SystemConfig.API.EnableCors, nil) 77 | 78 | return &JobServer{ 79 | RetryStartup: etc.RetryStartup(), 80 | Locker: fLocker, 81 | CenterServer: centerServer, 82 | APIServer: apiServer, 83 | }, nil 84 | } 85 | 86 | //Startup is exported 87 | func (server *JobServer) Startup() error { 88 | 89 | var ( 90 | err error 91 | startCh chan bool = make(chan bool, 1) 92 | ) 93 | 94 | go func(c <-chan bool) { 95 | select { 96 | case <-c: 97 | logger.INFO("[#main] API listening: %s", server.APIServer.ListenHosts()) 98 | if err := server.APIServer.Startup(); err != nil { 99 | logger.ERROR("[#main#] API startup error, %s", err.Error()) 100 | return 101 | } 102 | } 103 | }(startCh) 104 | 105 | for { 106 | if err != nil { 107 | if server.RetryStartup == false { 108 | return err 109 | } 110 | time.Sleep(time.Second * 10) //retry, after sleep 10 seconds. 111 | } 112 | 113 | server.Locker.Unlock() 114 | if err = server.Locker.Lock(); err != nil { 115 | logger.ERROR("[#main#] pidfile lock error, %s", err) 116 | continue 117 | } 118 | 119 | if err = server.CenterServer.Startup(startCh); err != nil { 120 | logger.ERROR("[#main#] start server failure.") 121 | continue 122 | } 123 | break 124 | } 125 | logger.INFO("[#main#] jobserver started.") 126 | logger.INFO("[#main#] key:%s", server.CenterServer.Key) 127 | close(startCh) 128 | return nil 129 | } 130 | 131 | //Stop is exported 132 | func (server *JobServer) Stop() error { 133 | 134 | server.Locker.Unlock() 135 | if err := server.CenterServer.Stop(); err != nil { 136 | logger.ERROR("[#main#] stop server failure.") 137 | return err 138 | } 139 | logger.INFO("[#main#] jobserver stoped.") 140 | logger.CLOSE() 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /cache/localstorage.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/cloudtask/common/models" 4 | import lru "github.com/hashicorp/golang-lru" 5 | 6 | import ( 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | const ( 12 | MinLRUSize = 128 13 | MaxLRUSize = 1024 14 | DefaultLRUSize = 256 15 | ) 16 | 17 | //LocalStorage is exported 18 | type LocalStorage struct { 19 | sync.RWMutex 20 | lurSize int 21 | jobsCache *lru.Cache 22 | workLocations map[string]*models.WorkLocation 23 | } 24 | 25 | //NewLocalStorage is exported 26 | func NewLocalStorage(lurSize int) *LocalStorage { 27 | 28 | if lurSize < MinLRUSize || lurSize > MaxLRUSize { 29 | lurSize = DefaultLRUSize 30 | } 31 | 32 | jobsCache, _ := lru.New(lurSize) 33 | return &LocalStorage{ 34 | lurSize: lurSize, 35 | jobsCache: jobsCache, 36 | workLocations: make(map[string]*models.WorkLocation), 37 | } 38 | } 39 | 40 | //Clear is exported 41 | func (storage *LocalStorage) Clear() { 42 | 43 | storage.Lock() 44 | storage.jobsCache, _ = lru.New(storage.lurSize) 45 | storage.workLocations = make(map[string]*models.WorkLocation) 46 | storage.Unlock() 47 | } 48 | 49 | //ContainsLocationServer is exported 50 | func (storage *LocalStorage) ContainsLocationServer(location string, ipaddr string, hostname string) bool { 51 | 52 | servers := storage.GetLocationServers(location) 53 | for _, server := range servers { 54 | if server.IPAddr == ipaddr || strings.ToUpper(server.Name) == strings.ToUpper(hostname) { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | //GetLocation is exported 62 | func (storage *LocalStorage) GetLocation(location string) *models.WorkLocation { 63 | 64 | storage.RLock() 65 | defer storage.RUnlock() 66 | workLocation, ret := storage.workLocations[location] 67 | if ret { 68 | return workLocation 69 | } 70 | return nil 71 | } 72 | 73 | //GetLocationsName is exported 74 | func (storage *LocalStorage) GetLocationsName() []string { 75 | 76 | names := []string{} 77 | storage.RLock() 78 | for location := range storage.workLocations { 79 | names = append(names, location) 80 | } 81 | storage.RUnlock() 82 | return names 83 | } 84 | 85 | //GetLocationServers is exported 86 | func (storage *LocalStorage) GetLocationServers(location string) []*models.Server { 87 | 88 | servers := []*models.Server{} 89 | workLocation := storage.GetLocation(location) 90 | if workLocation != nil { 91 | servers = workLocation.Server 92 | } 93 | return servers 94 | } 95 | 96 | //GetLocationGroups is exported 97 | func (storage *LocalStorage) GetLocationGroups(location string) []*models.Group { 98 | 99 | groups := []*models.Group{} 100 | workLocation := storage.GetLocation(location) 101 | if workLocation != nil { 102 | groups = workLocation.Group 103 | } 104 | return groups 105 | } 106 | 107 | //GetLocationGroup is exported 108 | func (storage *LocalStorage) GetLocationGroup(location string, groupid string) *models.Group { 109 | 110 | workLocation := storage.GetLocation(location) 111 | if workLocation != nil { 112 | for _, group := range workLocation.Group { 113 | if group.Id == groupid { 114 | return group 115 | } 116 | } 117 | } 118 | return nil 119 | } 120 | 121 | //SetLocation is exported 122 | func (storage *LocalStorage) SetLocation(workLocation *models.WorkLocation) { 123 | 124 | if workLocation != nil { 125 | storage.Lock() 126 | storage.workLocations[workLocation.Location] = workLocation 127 | storage.Unlock() 128 | } 129 | } 130 | 131 | //RemoveLocation is exported 132 | func (storage *LocalStorage) RemoveLocation(location string) { 133 | 134 | storage.Lock() 135 | delete(storage.workLocations, location) 136 | storage.Unlock() 137 | } 138 | 139 | //GetJob is exported 140 | func (storage *LocalStorage) GetJob(jobid string) *models.Job { 141 | 142 | value, ret := storage.jobsCache.Get(jobid) 143 | if ret { 144 | return value.(*models.Job) 145 | } 146 | return nil 147 | } 148 | 149 | //AddJob is exported 150 | func (storage *LocalStorage) AddJob(job *models.Job) { 151 | 152 | if job != nil { 153 | storage.jobsCache.Add(job.JobId, job) 154 | } 155 | } 156 | 157 | //RemoveJob is exported 158 | func (storage *LocalStorage) RemoveJob(jobid string) { 159 | 160 | storage.jobsCache.Remove(jobid) 161 | } 162 | -------------------------------------------------------------------------------- /notify/templates/job.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cloud Task 8 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 110 | 111 | 112 | 113 | 114 |
39 | 40 |
41 | Cloud 42 | Task 43 |
44 |
45 |
 
ID{{.ID}}
Event{{.Event}}
Description{{.Description}}
Job name{{.WatchJobNotify.JobName}}
Directory{{.WatchJobNotify.Directory}}
Location{{.WatchJobNotify.Location}}
Server{{.WatchJobNotify.Server}}
Start time{{.WatchJobNotify.Execat}}
Result 88 | {{ if eq .WatchJobNotify.IsSuccessd true }} 89 | Successd {{ else }} 90 | Failed {{ end }} 91 |
Content{{.WatchJobNotify.Content}}
99 |
Output
100 |
102 | {{ if .WatchJobNotify.Stdout }} 103 |
Stdout
104 |
{{.WatchJobNotify.Stdout}}
{{ end }} {{ if .WatchJobNotify.Errout }} 105 |
Errout
106 |
{{.WatchJobNotify.Errout}}
{{ end }} {{ if .WatchJobNotify.Execerr }} 107 |
Exec Err
108 |
{{.WatchJobNotify.Execerr}}
{{ end }} 109 |
 
115 | 116 | 117 | -------------------------------------------------------------------------------- /etc/lookupenv.go: -------------------------------------------------------------------------------- 1 | package etc 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | //ParseEnv is exported 13 | func (conf *Configuration) parseEnv() error { 14 | 15 | pidFile := os.Getenv("CLOUDTASK_PIDFILE") 16 | if pidFile != "" { 17 | conf.PidFile = pidFile 18 | } 19 | 20 | retryStartup := os.Getenv("CLOUDTASK_RETRYSTARTUP") 21 | if retryStartup != "" { 22 | value, err := strconv.ParseBool(retryStartup) 23 | if err != nil { 24 | return fmt.Errorf("CLOUDTASK_RETRYSTARTUP invalid, %s", err.Error()) 25 | } 26 | conf.RetryStartup = value 27 | } 28 | 29 | useServerConfig := os.Getenv("CLOUDTASK_USESERVERCONFIG") 30 | if useServerConfig != "" { 31 | value, err := strconv.ParseBool(useServerConfig) 32 | if err != nil { 33 | return fmt.Errorf("CLOUDTASK_USESERVERCONFIG invalid, %s", err.Error()) 34 | } 35 | conf.UseServerConfig = value 36 | } 37 | 38 | var err error 39 | //parse cluster env 40 | if err = parseClusterEnv(conf); err != nil { 41 | return err 42 | } 43 | 44 | //parse API env 45 | if err = parseAPIEnv(conf); err != nil { 46 | return err 47 | } 48 | 49 | //parse scheduler env 50 | if err = parseSchedulerEnv(conf); err != nil { 51 | return err 52 | } 53 | 54 | //parse cache env 55 | if err = parseCacheEnv(conf); err != nil { 56 | return err 57 | } 58 | 59 | //parse logger env 60 | if err = parseLoggerEnv(conf); err != nil { 61 | return err 62 | } 63 | return nil 64 | } 65 | 66 | func parseClusterEnv(conf *Configuration) error { 67 | 68 | if clusterHosts := os.Getenv("CLOUDTASK_CLUSTER_HOSTS"); clusterHosts != "" { 69 | conf.Cluster.Hosts = clusterHosts 70 | } 71 | 72 | if clusterName := os.Getenv("CLOUDTASK_CLUSTER_NAME"); clusterName != "" { 73 | if ret := filepath.HasPrefix(clusterName, "/"); !ret { 74 | clusterName = "/" + clusterName 75 | } 76 | conf.Cluster.Root = clusterName 77 | } 78 | 79 | if clusterPulse := os.Getenv("CLOUDTASK_CLUSTER_PULSE"); clusterPulse != "" { 80 | if _, err := time.ParseDuration(clusterPulse); err != nil { 81 | return fmt.Errorf("CLOUDTASK_CLUSTER_PULSE invalid, %s", err.Error()) 82 | } 83 | conf.Cluster.Pulse = clusterPulse 84 | } 85 | 86 | if clusterTimeout := os.Getenv("CLOUDTASK_CLUSTER_TIMEOUT"); clusterTimeout != "" { 87 | if _, err := time.ParseDuration(clusterTimeout); err != nil { 88 | return fmt.Errorf("CLOUDTASK_CLUSTER_TIMEOUT invalid, %s", err.Error()) 89 | } 90 | conf.Cluster.Timeout = clusterTimeout 91 | } 92 | 93 | if clusterThreshold := os.Getenv("CLOUDTASK_CLUSTER_THRESHOLD"); clusterThreshold != "" { 94 | value, err := strconv.Atoi(clusterThreshold) 95 | if err != nil { 96 | return fmt.Errorf("CLOUDTASK_CLUSTER_THRESHOLD invalid, %s", err.Error()) 97 | } 98 | conf.Cluster.Threshold = value 99 | } 100 | return nil 101 | } 102 | 103 | func parseAPIEnv(conf *Configuration) error { 104 | 105 | if apiHost := os.Getenv("CLOUDTASK_API_HOST"); apiHost != "" { 106 | hostIP, hostPort, err := net.SplitHostPort(apiHost) 107 | if err != nil { 108 | return fmt.Errorf("CLOUDTASK_API_HOST invalid, %s", err.Error()) 109 | } 110 | if hostIP != "" { 111 | if _, err := net.LookupHost(hostIP); err != nil { 112 | return fmt.Errorf("CLOUDTASK_API_HOST invalid, %s", err.Error()) 113 | } 114 | } 115 | conf.API.Hosts = []string{net.JoinHostPort(hostIP, hostPort)} 116 | } 117 | 118 | if enableCors := os.Getenv("CLOUDTASK_API_ENABLECORS"); enableCors != "" { 119 | value, err := strconv.ParseBool(enableCors) 120 | if err != nil { 121 | return fmt.Errorf("CLOUDTASK_API_ENABLECORS invalid, %s", err.Error()) 122 | } 123 | conf.API.EnableCors = value 124 | } 125 | return nil 126 | } 127 | 128 | func parseSchedulerEnv(conf *Configuration) error { 129 | 130 | if schedulerAllocMode := os.Getenv("CLOUDTASK_SCHEDULER_ALLOCMODE"); schedulerAllocMode != "" { 131 | conf.Scheduler.AllocMode = schedulerAllocMode 132 | } 133 | 134 | if schedulerAllocRecovery := os.Getenv("CLOUDTASK_SCHEDULER_ALLOCRECOVERY"); schedulerAllocRecovery != "" { 135 | if _, err := time.ParseDuration(schedulerAllocRecovery); err != nil { 136 | return fmt.Errorf("CLOUDTASK_SCHEDULER_ALLOCRECOVERY invalid, %s", err.Error()) 137 | } 138 | conf.Scheduler.AllocRecovery = schedulerAllocRecovery 139 | } 140 | return nil 141 | } 142 | 143 | func parseCacheEnv(conf *Configuration) error { 144 | 145 | if cacheLRUSize := os.Getenv("CLOUDTASK_CACHE_LRUSIZE"); cacheLRUSize != "" { 146 | value, err := strconv.Atoi(cacheLRUSize) 147 | if err != nil { 148 | return fmt.Errorf("CLOUDTASK_CACHE_LRUSIZE invalid, %s", err.Error()) 149 | } 150 | conf.Cache.LRUSize = value 151 | } 152 | return nil 153 | } 154 | 155 | func parseLoggerEnv(conf *Configuration) error { 156 | 157 | if logFile := os.Getenv("CLOUDTASK_LOG_FILE"); logFile != "" { 158 | conf.Logger.LogFile = logFile 159 | } 160 | 161 | if logLevel := os.Getenv("CLOUDTASK_LOG_LEVEL"); logLevel != "" { 162 | conf.Logger.LogLevel = logLevel 163 | } 164 | 165 | if logSize := os.Getenv("CLOUDTASK_LOG_SIZE"); logSize != "" { 166 | value, err := strconv.ParseInt(logSize, 10, 64) 167 | if err != nil { 168 | return fmt.Errorf("CLOUDTASK_LOG_SIZE invalid, %s", err.Error()) 169 | } 170 | conf.Logger.LogSize = value 171 | } 172 | return nil 173 | } 174 | -------------------------------------------------------------------------------- /api/messages.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache" 4 | import "github.com/cloudtask/cloudtask-center/scheduler" 5 | import "github.com/cloudtask/common/models" 6 | import "github.com/cloudtask/libtools/gounits/logger" 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "strings" 12 | ) 13 | 14 | //ProcessSystemEventMessage is exported 15 | func ProcessSystemEventMessage(request *MessageRequest) error { 16 | 17 | logger.INFO("[#api#] process systemevent message %s", request.Header.MsgId) 18 | systemEvent := &models.SystemEvent{} 19 | if err := json.NewDecoder(request.Reader).Decode(systemEvent); err != nil { 20 | return fmt.Errorf("process systemevent message %s failure, %s", request.Header.MsgId, err.Error()) 21 | } 22 | 23 | cacheRepository := request.Context.Get("CacheRepository").(*cache.CacheRepository) 24 | scheduler := request.Context.Get("Scheduler").(*scheduler.Scheduler) 25 | switch systemEvent.Event { 26 | case models.RemoveGroupEvent: 27 | { //只考虑删除组情况,创建和修改组不会对分配表造成改变. 28 | logger.INFO("[#api#] ### %s, %+v", models.RemoveGroupEvent, systemEvent) 29 | cacheRepository.RemoveAllocJobs(systemEvent.Runtime, systemEvent.JobIds) 30 | cacheRepository.RemoveJobs(systemEvent.JobIds) 31 | } 32 | case models.CreateJobEvent: 33 | { //创建新任务事件 34 | logger.INFO("[#api#] ### %s, %+v", models.CreateJobEvent, systemEvent) 35 | if len(systemEvent.JobIds) > 0 { 36 | jobId := systemEvent.JobIds[0] 37 | if job := cacheRepository.GetRawJob(jobId); job != nil { 38 | scheduler.SingleJobAlloc(systemEvent.Runtime, jobId) 39 | } 40 | } 41 | } 42 | case models.RemoveJobEvent: 43 | { //删除一个任务事件 44 | logger.INFO("[#api#] ### %s, %+v", models.RemoveJobEvent, systemEvent) 45 | if len(systemEvent.JobIds) > 0 { 46 | jobId := systemEvent.JobIds[0] 47 | cacheRepository.RemoveAllocJob(systemEvent.Runtime, jobId) //从分配表删除 48 | cacheRepository.RemoveJob(jobId) 49 | } 50 | } 51 | case models.ChangeJobEvent: 52 | { //修改一个任务事件 53 | logger.INFO("[#api#] ### %s, %+v", models.ChangeJobEvent, systemEvent) 54 | if len(systemEvent.JobIds) > 0 { 55 | job := cacheRepository.GetRawJob(systemEvent.JobIds[0]) 56 | if job != nil { 57 | if job.Enabled == 1 { 58 | jobData := cacheRepository.GetAllocJob(job.Location, job.JobId) 59 | if jobData == nil { 60 | scheduler.SingleJobAlloc(job.Location, job.JobId) //重新加入分配表 61 | } else { 62 | if len(job.Servers) > 0 { 63 | scheduler.SingleJobAlloc(job.Location, job.JobId) //可能调整了servers, 需要重新分配一次. 64 | } 65 | cacheRepository.UpdateAllocJob(job.Location, job.JobId) 66 | } 67 | } else { //修改并关闭了任务 68 | cacheRepository.RemoveAllocJob(systemEvent.Runtime, job.JobId) 69 | cacheRepository.RemoveJob(job.JobId) 70 | } 71 | } 72 | } 73 | } 74 | case models.ChangeJobsFileEvent: 75 | { //批量修改job任务文件 76 | logger.INFO("[#api#] ### %s, %+v", models.ChangeJobsFileEvent, systemEvent) 77 | for _, jobId := range systemEvent.JobIds { 78 | cacheRepository.GetRawJob(jobId) 79 | } 80 | cacheRepository.UpdateAllocJobs(systemEvent.Runtime, systemEvent.JobIds) 81 | } 82 | case models.CreateRuntimeEvent: 83 | { 84 | if strings.TrimSpace(systemEvent.Runtime) != "" { 85 | logger.INFO("[#api#] ### %s, %+v", models.CreateRuntimeEvent, systemEvent) 86 | workLocation := cacheRepository.GetLocation(systemEvent.Runtime) 87 | if workLocation == nil { 88 | cacheRepository.CreateLocationAlloc(systemEvent.Runtime) 89 | } 90 | } 91 | } 92 | case models.ChangeRuntimeEvent: 93 | { 94 | if strings.TrimSpace(systemEvent.Runtime) != "" { 95 | logger.INFO("[#api#] ### %s, %+v", models.ChangeRuntimeEvent, systemEvent) 96 | workLocation := cacheRepository.GetLocation(systemEvent.Runtime) 97 | if workLocation != nil { 98 | cacheRepository.ChangeLocationAlloc(systemEvent.Runtime) 99 | } 100 | } 101 | } 102 | case models.RemoveRuntimeEvent: 103 | { 104 | if strings.TrimSpace(systemEvent.Runtime) != "" { 105 | logger.INFO("[#api#] ### %s, %+v", models.RemoveRuntimeEvent, systemEvent) 106 | workLocation := cacheRepository.GetLocation(systemEvent.Runtime) 107 | if workLocation != nil { 108 | cacheRepository.RemoveLocationAlloc(systemEvent.Runtime) 109 | } 110 | } 111 | } 112 | } 113 | return nil 114 | } 115 | 116 | //ProcessJobExecuteMessage is exported 117 | func ProcessJobExecuteMessage(request *MessageRequest) error { 118 | 119 | jobExecute := &models.JobExecute{} 120 | if err := json.NewDecoder(request.Reader).Decode(jobExecute); err != nil { 121 | return fmt.Errorf("process jobexecute message %s failure, %s", request.Header.MsgId, err.Error()) 122 | } 123 | 124 | logger.INFO("[#api#] process jobexecute message %s %s", jobExecute.JobId, jobExecute.Location) 125 | messageCache := request.Context.Get("MessageCache").(*models.MessageCache) 126 | if messageCache.ValidateMessage(jobExecute) { 127 | cacheRepository := request.Context.Get("CacheRepository").(*cache.CacheRepository) 128 | cacheRepository.SetJobExecute(jobExecute.JobId, jobExecute.State, jobExecute.ExecErr, jobExecute.ExecAt, jobExecute.NextAt) 129 | } 130 | return nil 131 | } 132 | 133 | //ProcessJobSelectMessage is exported 134 | func ProcessJobSelectMessage(request *MessageRequest) error { 135 | 136 | jobSelect := &models.JobSelect{} 137 | if err := json.NewDecoder(request.Reader).Decode(jobSelect); err != nil { 138 | return fmt.Errorf("process jobselect message %s failure, %s", request.Header.MsgId, err.Error()) 139 | } 140 | 141 | logger.INFO("[#api#] process jobselect message %s %s", jobSelect.JobId, jobSelect.Location) 142 | messageCache := request.Context.Get("MessageCache").(*models.MessageCache) 143 | if messageCache.ValidateMessage(jobSelect) { 144 | cacheRepository := request.Context.Get("CacheRepository").(*cache.CacheRepository) 145 | cacheRepository.SetJobNextAt(jobSelect.JobId, jobSelect.NextAt) 146 | } 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache" 4 | import "github.com/cloudtask/common/models" 5 | import "github.com/cloudtask/libtools/gzkwrapper" 6 | import "github.com/cloudtask/libtools/gounits/logger" 7 | 8 | /* 9 | 任务调配器 10 | 当worker注册、注销或异常掉线时,该模块负责根据配置调整job分配 11 | Alloc;hash和pref两种模式,见配置. 12 | hash,哈希调配模式,以jobid为一致性hash key 调整重新计算新的worker. 13 | worker上线,workercache环发生变化,此时将该location环中的所有job(状态为STATE_STARTED的job除外)重新调配. 14 | worker下线,workercache环发生变化,为避免该location环中的所有job调配抖动过大,只调配掉线节点的job,重新计算hash. 15 | pref, 性能调配模式,该模式为定时轮询检查worker的job负载,如果某worker的job数过多,那么尽量将该worker的job分担到其余相 16 | 对job较少的worker上,意味着该模式会定期调整,job执行worker随意度比较活跃. 17 | 注:当worker上下线,该模式会均匀按任务数调整(状态为STATE_STARTED的job除外). 18 | */ 19 | 20 | //Nodes is exported 21 | type Nodes struct { 22 | Online gzkwrapper.NodesPair 23 | Offline gzkwrapper.NodesPair 24 | } 25 | 26 | //NewNodes is exported 27 | func NewNodes() *Nodes { 28 | return &Nodes{ 29 | Online: make(gzkwrapper.NodesPair), 30 | Offline: make(gzkwrapper.NodesPair), 31 | } 32 | } 33 | 34 | //SchedulerConfigs is exported 35 | type SchedulerConfigs struct { 36 | AllocMode string 37 | AllocRecovery string 38 | } 39 | 40 | //Scheduler is exported 41 | type Scheduler struct { 42 | *SchedulerConfigs 43 | cacheRepository *cache.CacheRepository 44 | } 45 | 46 | //NewScheduler is exported 47 | func NewScheduler(configs *SchedulerConfigs, cacheRepository *cache.CacheRepository) *Scheduler { 48 | 49 | return &Scheduler{ 50 | SchedulerConfigs: configs, 51 | cacheRepository: cacheRepository, 52 | } 53 | } 54 | 55 | //QuickAlloc is exported 56 | //以节点状态改变为条件进行分配,调度器对上下线节点进行job调整. 57 | func (scheduler *Scheduler) QuickAlloc(online gzkwrapper.NodesPair, offline gzkwrapper.NodesPair) { 58 | 59 | logger.INFO("[#scheduler#] quick alloc. %s", scheduler.AllocMode) 60 | nodesMapper := make(map[string]*Nodes, 0) 61 | for key, nodedata := range online { 62 | location := nodedata.Location 63 | if _, ret := nodesMapper[location]; !ret { 64 | nodesMapper[location] = NewNodes() 65 | } 66 | nodesMapper[location].Online[key] = nodedata 67 | } 68 | 69 | for key, nodedata := range offline { 70 | location := nodedata.Location 71 | if _, ret := nodesMapper[location]; !ret { 72 | nodesMapper[location] = NewNodes() 73 | } 74 | nodesMapper[location].Offline[key] = nodedata 75 | } 76 | 77 | if scheduler.AllocMode == "hash" { 78 | for location, nodes := range nodesMapper { 79 | logger.INFO("[#scheduler#] hash alloc %s.", location) 80 | scheduler.HashAlloc(location, nodes) 81 | } 82 | return 83 | } 84 | 85 | if scheduler.AllocMode == "pref" { 86 | for location, nodes := range nodesMapper { 87 | logger.INFO("[#scheduler#] pref alloc %s.", location) 88 | scheduler.PrefAlloc(location, nodes) 89 | } 90 | return 91 | } 92 | logger.WARN("[#scheduler#] alloc mode invalid, jobs can't adjust. :-(") 93 | } 94 | 95 | //SingleJobAlloc is exported 96 | //以job为条件进行分配,当创建或删除Job时调度器会立即分配 97 | func (scheduler *Scheduler) SingleJobAlloc(location string, jobid string) { 98 | 99 | logger.INFO("[#scheduler#] single alloc. %s", scheduler.AllocMode) 100 | if scheduler.AllocMode == "hash" { 101 | scheduler.HashSingleJobAlloc(location, jobid) 102 | return 103 | } 104 | 105 | if scheduler.AllocMode == "pref" { 106 | scheduler.PrefSingleJobAlloc(location, jobid) 107 | return 108 | } 109 | logger.WARN("[#scheduler#] alloc mode invalid, jobs can't adjust. :-(") 110 | } 111 | 112 | //RecoveryLocationAlloc is exported 113 | //检查location下所有enabled已打开的job 114 | //1、若分配表中不存在,则重新分配一次. 115 | //2、已存在,则检测分配的节点是否已发生改变,若改变则重新再分配. 116 | func (scheduler *Scheduler) RecoveryLocationAlloc(location string, jobs []*models.SimpleJob) { 117 | 118 | //logger.INFO("[#scheduler#] recovery %s alloc.", location) 119 | jobKeys := make(map[string]string, 0) 120 | for _, job := range jobs { 121 | if job.Enabled == 1 { 122 | found := scheduler.cacheRepository.HasAllocJobId(job.Location, job.JobId) 123 | if !found { //不在分配表中,可能在创建job后通过HashSingleJobAlloc分配失败过1次,立即重新分配 124 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) //hash重新分配 125 | if worker != nil { 126 | scheduler.cacheRepository.CreateAllocJob(location, worker.Key, job.JobId) //加入到分配表 127 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_STOPED) 128 | logger.INFO("[#scheduler#] reset %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 129 | } else { 130 | if job.Stat != models.STATE_REALLOC { 131 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) 132 | logger.ERROR("[#scheduler#] reset %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 133 | } 134 | } 135 | } else { //分配表中存在,对比worker.Key检查是否需要重新分配. 136 | jobData := scheduler.cacheRepository.GetAllocJob(location, job.JobId) 137 | if jobData != nil { 138 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) 139 | if worker != nil { 140 | if worker.Key != jobData.Key { //节点发生变化了,需要调整 141 | jobKeys[job.JobId] = worker.Key 142 | logger.INFO("[#scheduler#] reset %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 143 | } 144 | } else { 145 | if job.Stat != models.STATE_REALLOC { 146 | scheduler.cacheRepository.RemoveAllocJob(location, job.JobId) //分配失败,从分配表删除. 147 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) 148 | logger.ERROR("[#scheduler#] reset %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | //分配表中存在同时分配节点发生过变化,将其批量修改分配表. 157 | if nSize := len(jobKeys); nSize > 0 { 158 | logger.INFO("[#scheduler#] reset %s jobs %d to joballoc successed.", location, nSize) 159 | scheduler.cacheRepository.SetAllocJobsKey(location, jobKeys) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /api/handler.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache" 4 | import "github.com/cloudtask/cloudtask-center/notify" 5 | import "github.com/cloudtask/common/models" 6 | 7 | import ( 8 | "crypto/md5" 9 | "encoding/hex" 10 | "net/http" 11 | ) 12 | 13 | func getJobBase(c *Context) error { 14 | 15 | response := &ResponseImpl{} 16 | jobid := ResolveJobBaseRequest(c) 17 | if jobid == "" { 18 | response.SetContent(ErrRequestResolveInvaild.Error()) 19 | return c.JSON(http.StatusBadRequest, response) 20 | } 21 | 22 | cacheRepository := c.Get("CacheRepository").(*cache.CacheRepository) 23 | job := cacheRepository.GetJob(jobid) 24 | if job == nil { 25 | response.SetContent(ErrRequestNotFound.Error()) 26 | return c.JSON(http.StatusNotFound, response) 27 | } 28 | 29 | version := 0 30 | if jobData := cacheRepository.GetAllocJob(job.Location, job.JobId); jobData != nil { 31 | version = jobData.Version 32 | } 33 | 34 | encoder := md5.New() //根据文件名计算filecode(md5) 35 | encoder.Write([]byte(job.FileName)) 36 | fileCode := hex.EncodeToString(encoder.Sum(nil)) 37 | jobBase := &models.JobBase{ 38 | JobId: job.JobId, 39 | JobName: job.Name, 40 | FileName: job.FileName, 41 | FileCode: fileCode, 42 | Cmd: job.Cmd, 43 | Env: job.Env, 44 | Timeout: job.Timeout, 45 | Version: version, 46 | Schedule: job.Schedule, 47 | } 48 | return c.JSON(http.StatusOK, jobBase) 49 | } 50 | 51 | func getJobsAllocData(c *Context) error { 52 | 53 | response := &ResponseImpl{} 54 | runtime := ResolveJobsAllocDataRequest(c) 55 | if runtime == "" { 56 | response.SetContent(ErrRequestResolveInvaild.Error()) 57 | return c.JSON(http.StatusBadRequest, response) 58 | } 59 | 60 | cacheRepository := c.Get("CacheRepository").(*cache.CacheRepository) 61 | jobsAllocData := cacheRepository.GetAllocData(runtime) 62 | if jobsAllocData == nil { 63 | response.SetContent(ErrRequestNotFound.Error()) 64 | return c.JSON(http.StatusNotFound, response) 65 | } 66 | 67 | respData := GetJobsAllocDataResponse{JobsAlloc: jobsAllocData} 68 | response.SetContent(ErrRequestSuccessed.Error()) 69 | response.SetData(respData) 70 | return c.JSON(http.StatusOK, response) 71 | } 72 | 73 | func getServerJobsAllocData(c *Context) error { 74 | 75 | response := &ResponseImpl{} 76 | request := ResolveServerJobsAllocDataRequest(c) 77 | if request == nil { 78 | response.SetContent(ErrRequestResolveInvaild.Error()) 79 | return c.JSON(http.StatusBadRequest, response) 80 | } 81 | 82 | cacheRepository := c.Get("CacheRepository").(*cache.CacheRepository) 83 | jobsAllocData := cacheRepository.GetServerAllocData(request.Runtime, request.Server) 84 | if jobsAllocData == nil { 85 | response.SetContent(ErrRequestNotFound.Error()) 86 | return c.JSON(http.StatusNotFound, response) 87 | } 88 | 89 | respData := GetJobsAllocDataResponse{JobsAlloc: jobsAllocData} 90 | response.SetContent(ErrRequestSuccessed.Error()) 91 | response.SetData(respData) 92 | return c.JSON(http.StatusOK, response) 93 | } 94 | 95 | func getServers(c *Context) error { 96 | 97 | response := &ResponseImpl{} 98 | runtime := ResolveServersRequest(c) 99 | if runtime == "" { 100 | response.SetContent(ErrRequestResolveInvaild.Error()) 101 | return c.JSON(http.StatusBadRequest, response) 102 | } 103 | 104 | cacheRepository := c.Get("CacheRepository").(*cache.CacheRepository) 105 | workers := cacheRepository.GetWorkers(runtime) 106 | respData := GetServersResponse{Servers: []*models.Server{}} 107 | for _, worker := range workers { 108 | respData.Servers = append(respData.Servers, worker.Server) 109 | } 110 | response.SetContent(ErrRequestSuccessed.Error()) 111 | response.SetData(respData) 112 | return c.JSON(http.StatusOK, response) 113 | } 114 | 115 | func postMessages(c *Context) error { 116 | 117 | response := &ResponseImpl{} 118 | request := ResolveMessageRequest(c) 119 | if request == nil { 120 | response.SetContent(ErrRequestResolveInvaild.Error()) 121 | return c.JSON(http.StatusBadRequest, response) 122 | } 123 | 124 | switch request.Header.MsgName { 125 | case models.MsgSystemEvent: 126 | { 127 | if err := ProcessSystemEventMessage(request); err != nil { 128 | response.SetContent(err.Error()) 129 | return c.JSON(http.StatusInternalServerError, response) 130 | } 131 | } 132 | case models.MsgJobExecute: 133 | { 134 | if err := ProcessJobExecuteMessage(request); err != nil { 135 | response.SetContent(err.Error()) 136 | return c.JSON(http.StatusInternalServerError, response) 137 | } 138 | } 139 | case models.MsgJobSelect: 140 | { 141 | if err := ProcessJobSelectMessage(request); err != nil { 142 | response.SetContent(err.Error()) 143 | return c.JSON(http.StatusInternalServerError, response) 144 | } 145 | } 146 | } 147 | response.SetContent(ErrRequestAccepted.Error()) 148 | return c.JSON(http.StatusAccepted, response) 149 | } 150 | 151 | func postLogs(c *Context) error { 152 | 153 | response := &ResponseImpl{} 154 | request := ResloveJogRequest(c) 155 | if request == nil { 156 | response.SetContent(ErrRequestResolveInvaild.Error()) 157 | return c.JSON(http.StatusBadRequest, response) 158 | } 159 | cacheRepository := c.Get("CacheRepository").(*cache.CacheRepository) 160 | cacheRepository.SetJobLog(&request.JobLog) 161 | job := cacheRepository.GetJob(request.JobId) 162 | if job != nil && job.NotifySetting != nil { 163 | var notifyOpt *models.Notify 164 | var isSuccessd bool 165 | switch request.JobLog.Stat { 166 | case models.STATE_STOPED: 167 | notifyOpt = &job.NotifySetting.Succeed 168 | isSuccessd = true 169 | case models.STATE_FAILED: 170 | notifyOpt = &job.NotifySetting.Failed 171 | isSuccessd = false 172 | } 173 | 174 | if notifyOpt != nil && notifyOpt.Enabled { 175 | var execAt string 176 | if !request.JobLog.ExecAt.IsZero() { 177 | execAt = request.JobLog.ExecAt.String() 178 | } 179 | watchJobNotify := ¬ify.WatchJobNotify{ 180 | ContactInfo: []string{notifyOpt.To}, 181 | JobResult: notify.JobResult{ 182 | JobName: job.Name, 183 | Directory: request.JobLog.WorkDir, 184 | Location: job.Location, 185 | Server: request.JobLog.IpAddr, 186 | Execat: execAt, 187 | IsSuccessd: isSuccessd, 188 | Content: notifyOpt.Content, 189 | Stdout: request.JobLog.StdOut, 190 | Errout: request.JobLog.ErrOut, 191 | Execerr: request.JobLog.ExecErr, 192 | }, 193 | } 194 | notifySender := c.Get("NotifySender").(*notify.NotifySender) 195 | notifySender.AddJobNotifyEvent("This Job Has Been Executed.", watchJobNotify) 196 | } 197 | } 198 | response.SetContent(ErrRequestAccepted.Error()) 199 | return c.JSON(http.StatusAccepted, response) 200 | } 201 | 202 | func putJobAction(c *Context) error { 203 | 204 | response := &ResponseImpl{} 205 | request := ResolveJobActionRequest(c) 206 | if request == nil { 207 | response.SetContent(ErrRequestResolveInvaild.Error()) 208 | return c.JSON(http.StatusBadRequest, response) 209 | } 210 | 211 | cacheRepository := c.Get("CacheRepository").(*cache.CacheRepository) 212 | cacheRepository.SetJobAction(request.Runtime, request.JobId, request.Action) 213 | response.SetContent(ErrRequestAccepted.Error()) 214 | return c.JSON(http.StatusAccepted, response) 215 | } 216 | -------------------------------------------------------------------------------- /cache/driver/ngcloud/ngcloud.go: -------------------------------------------------------------------------------- 1 | package ngcloud 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache/driver" 4 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 5 | import "github.com/cloudtask/common/models" 6 | import "github.com/cloudtask/libtools/gounits/logger" 7 | 8 | import ( 9 | "errors" 10 | "net/url" 11 | "path" 12 | "strconv" 13 | "sync" 14 | ) 15 | 16 | const ( 17 | defaultReadPageSize = 512 18 | ) 19 | 20 | var ( 21 | ErrNgCloudStorageDriverURLInvalid = errors.New("ngcloud storage driver apiURL invalid.") 22 | ) 23 | 24 | //NgCloudStorageDriver is exported 25 | type NgCloudStorageDriver struct { 26 | sync.RWMutex 27 | driver.StorageDriver 28 | engine *Engine 29 | } 30 | 31 | func init() { 32 | driver.AddDriver(types.NGCLOUD, New) 33 | } 34 | 35 | //New is exported 36 | func New(parameters types.Parameters) (driver.StorageDriver, error) { 37 | 38 | rawAPIURL, readPageSize, err := parseEngineConfigs(parameters) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &NgCloudStorageDriver{ 44 | engine: NewEngine(rawAPIURL, readPageSize), 45 | }, nil 46 | } 47 | 48 | func parseEngineConfigs(parameters types.Parameters) (string, int, error) { 49 | 50 | var ( 51 | value interface{} 52 | ret bool 53 | readPageSize int 54 | rawAPIURL string 55 | ) 56 | 57 | readPageSize = defaultReadPageSize 58 | value, ret = parameters["readpagesize"] 59 | if ret { 60 | if pValue, err := strconv.Atoi(value.(string)); err == nil { 61 | readPageSize = pValue 62 | } 63 | } 64 | 65 | value, ret = parameters["apiurl"] 66 | if !ret { 67 | return "", 0, ErrNgCloudStorageDriverURLInvalid 68 | } 69 | 70 | pRawURL, err := url.Parse(value.(string)) 71 | if err != nil { 72 | return "", 0, ErrNgCloudStorageDriverURLInvalid 73 | } 74 | 75 | scheme := pRawURL.Scheme 76 | if scheme == "" { 77 | scheme = "http" 78 | } 79 | 80 | rawAPIURL = scheme + "://" + pRawURL.Host + path.Clean(pRawURL.Path) 81 | if pRawURL.RawQuery != "" { 82 | rawAPIURL = rawAPIURL + "?" + pRawURL.RawQuery 83 | } 84 | return rawAPIURL, readPageSize, nil 85 | } 86 | 87 | //Open is exported 88 | func (driver *NgCloudStorageDriver) Open() error { 89 | 90 | return nil 91 | } 92 | 93 | //Close is exported 94 | func (driver *NgCloudStorageDriver) Close() { 95 | } 96 | 97 | //SetConfigParameters is exported 98 | func (driver *NgCloudStorageDriver) SetConfigParameters(parameters types.Parameters) { 99 | 100 | rawAPIURL, readPageSize, err := parseEngineConfigs(parameters) 101 | if err != nil { 102 | logger.ERROR("[#cache#] ngcloud driver parse configs error, %s", err.Error()) 103 | return 104 | } 105 | driver.engine.SetConfigParameters(rawAPIURL, readPageSize) 106 | logger.ERROR("[#cache#] ngcloud driver configs changed, %s %s", rawAPIURL, readPageSize) 107 | } 108 | 109 | //GetLocationsName is exported 110 | func (driver *NgCloudStorageDriver) GetLocationsName() []string { 111 | 112 | driver.RLock() 113 | defer driver.RUnlock() 114 | names, err := driver.engine.readLocationsName() 115 | if err != nil { 116 | logger.ERROR("[#cache#] engine read locations name error, %s", err.Error()) 117 | return []string{} 118 | } 119 | return names 120 | } 121 | 122 | //GetLocation is exported 123 | func (driver *NgCloudStorageDriver) GetLocation(location string) *models.WorkLocation { 124 | 125 | driver.RLock() 126 | defer driver.RUnlock() 127 | workLocation, err := driver.engine.getLocation(location) 128 | if err != nil { 129 | logger.ERROR("[#cache#] engine read location %s error, %s", location, err.Error()) 130 | return nil 131 | } 132 | return workLocation 133 | } 134 | 135 | //GetLocationSimpleJobs is exported 136 | func (driver *NgCloudStorageDriver) GetLocationSimpleJobs(location string) []*models.SimpleJob { 137 | 138 | driver.RLock() 139 | defer driver.RUnlock() 140 | query := map[string][]string{"f_location": []string{location}} 141 | jobs, err := driver.engine.readSimpleJobs(query) 142 | if err != nil { 143 | logger.ERROR("[#cache#] engine read simple jobs %+v error, %s", query, err.Error()) 144 | return []*models.SimpleJob{} 145 | } 146 | return jobs 147 | } 148 | 149 | //GetSimpleJob is exported 150 | func (driver *NgCloudStorageDriver) GetSimpleJob(jobid string) *models.SimpleJob { 151 | 152 | driver.RLock() 153 | defer driver.RUnlock() 154 | job, err := driver.engine.getSimpleJob(jobid) 155 | if err != nil { 156 | logger.ERROR("[#cache#] engine get simple job %s error, %s", jobid, err.Error()) 157 | return nil 158 | } 159 | return job 160 | } 161 | 162 | //GetJobs is exported 163 | func (driver *NgCloudStorageDriver) GetJobs() []*models.Job { 164 | 165 | driver.RLock() 166 | defer driver.RUnlock() 167 | query := map[string][]string{} 168 | jobs, err := driver.engine.readJobs(query) 169 | if err != nil { 170 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 171 | return []*models.Job{} 172 | } 173 | return jobs 174 | } 175 | 176 | //GetStateJobs is exported 177 | func (driver *NgCloudStorageDriver) GetStateJobs(state int) []*models.Job { 178 | 179 | driver.RLock() 180 | defer driver.RUnlock() 181 | query := map[string][]string{"f_stat": []string{strconv.Itoa(state)}} 182 | jobs, err := driver.engine.readJobs(query) 183 | if err != nil { 184 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 185 | return []*models.Job{} 186 | } 187 | return jobs 188 | } 189 | 190 | //GetLocationJobs is exported 191 | func (driver *NgCloudStorageDriver) GetLocationJobs(location string) []*models.Job { 192 | 193 | driver.RLock() 194 | defer driver.RUnlock() 195 | query := map[string][]string{"f_location": []string{location}} 196 | jobs, err := driver.engine.readJobs(query) 197 | if err != nil { 198 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 199 | return []*models.Job{} 200 | } 201 | return jobs 202 | } 203 | 204 | //GetGroupJobs is exported 205 | func (driver *NgCloudStorageDriver) GetGroupJobs(groupid string) []*models.Job { 206 | 207 | driver.RLock() 208 | defer driver.RUnlock() 209 | query := map[string][]string{"f_groupid": []string{groupid}} 210 | jobs, err := driver.engine.readJobs(query) 211 | if err != nil { 212 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 213 | return []*models.Job{} 214 | } 215 | return jobs 216 | } 217 | 218 | //GetJob is exported 219 | func (driver *NgCloudStorageDriver) GetJob(jobid string) *models.Job { 220 | 221 | driver.RLock() 222 | defer driver.RUnlock() 223 | job, err := driver.engine.getJob(jobid) 224 | if err != nil { 225 | logger.ERROR("[#cache#] engine get job %s error, %s", jobid, err.Error()) 226 | return nil 227 | } 228 | return job 229 | } 230 | 231 | //SetJob is exported 232 | func (driver *NgCloudStorageDriver) SetJob(job *models.Job) { 233 | 234 | driver.Lock() 235 | defer driver.Unlock() 236 | if err := driver.engine.putJob(job); err != nil { 237 | logger.ERROR("[#cache#] engine set job %s error, %s", job.JobId, err.Error()) 238 | } 239 | } 240 | 241 | //SetJobLog is exported 242 | func (driver *NgCloudStorageDriver) SetJobLog(joblog *models.JobLog) { 243 | 244 | driver.Lock() 245 | defer driver.Unlock() 246 | if err := driver.engine.postJobLog(joblog); err != nil { 247 | logger.ERROR("[#cache#] engine post job %s error, %s", joblog.JobId, err.Error()) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /cache/nodes.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/cloudtask/common/models" 4 | import "github.com/cloudtask/libtools/gounits/algorithm" 5 | 6 | import ( 7 | "hash/crc32" 8 | "sort" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | //NodeEvent is exported 14 | type NodeEvent int 15 | 16 | const ( 17 | NODE_CREATED_EVENT NodeEvent = iota + 1 18 | NODE_CHANGED_EVENT 19 | NODE_REMOVED_EVENT 20 | ) 21 | 22 | func (event NodeEvent) String() string { 23 | 24 | switch event { 25 | case NODE_CREATED_EVENT: 26 | return "NODE_CREATED_EVENT" 27 | case NODE_CHANGED_EVENT: 28 | return "NODE_CHANGED_EVENT" 29 | case NODE_REMOVED_EVENT: 30 | return "NODE_REMOVED_EVENT" 31 | } 32 | return "" 33 | } 34 | 35 | //NodeCacheEventHandlerFunc is exported 36 | type NodeCacheEventHandlerFunc func(event NodeEvent, location string, worker *Worker) 37 | 38 | //Worker is exported 39 | type Worker struct { 40 | Location string 41 | *models.AttachData 42 | *models.Server 43 | } 44 | 45 | //NodeStore is exported 46 | type NodeStore struct { 47 | sync.RWMutex 48 | nodesData map[string][]*Worker 49 | circle map[string]*algorithm.Consistent 50 | callback NodeCacheEventHandlerFunc 51 | } 52 | 53 | //NewNodeStore is exported 54 | func NewNodeStore(callback NodeCacheEventHandlerFunc) *NodeStore { 55 | 56 | return &NodeStore{ 57 | nodesData: make(map[string][]*Worker, 0), 58 | circle: make(map[string]*algorithm.Consistent, 0), 59 | callback: callback, 60 | } 61 | } 62 | 63 | //GetWorker is exported 64 | func (store *NodeStore) GetWorker(key string) *Worker { 65 | 66 | store.RLock() 67 | defer store.RUnlock() 68 | for _, workers := range store.nodesData { 69 | for _, worker := range workers { 70 | if worker.Key == key { 71 | return worker 72 | } 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | //ContainsLocationWorker is exported 79 | func (store *NodeStore) ContainsLocationWorker(location string, ipaddr string) bool { 80 | 81 | store.RLock() 82 | defer store.RUnlock() 83 | if workers, ret := store.nodesData[location]; ret { 84 | for _, worker := range workers { 85 | if itIPAddr := strings.TrimSpace(worker.IPAddr); itIPAddr != "" { 86 | if itIPAddr == ipaddr { 87 | return true 88 | } 89 | } 90 | } 91 | } 92 | return false 93 | } 94 | 95 | //GetWorkers is exported 96 | func (store *NodeStore) GetWorkers(location string) []*Worker { 97 | 98 | store.RLock() 99 | defer store.RUnlock() 100 | workers, ret := store.nodesData[location] 101 | if !ret { 102 | return []*Worker{} 103 | } 104 | return workers 105 | } 106 | 107 | //HashLocationWorker is exported 108 | func (store *NodeStore) HashLocationWorker(location string, key string) *Worker { 109 | 110 | var ( 111 | ret bool 112 | workers []*Worker 113 | ) 114 | 115 | store.RLock() 116 | defer store.RUnlock() 117 | if workers, ret = store.nodesData[location]; !ret { 118 | return nil 119 | } 120 | 121 | c := store.circle[location] 122 | if c == nil { 123 | return nil 124 | } 125 | 126 | workerKey := c.Get(key) 127 | for _, worker := range workers { 128 | if worker.Key == workerKey { 129 | return worker 130 | } 131 | } 132 | return nil 133 | } 134 | 135 | //HashLocationRangeWorker is exported 136 | func (store *NodeStore) HashLocationRangeWorker(location string, key string, servers []string) *Worker { 137 | 138 | store.RLock() 139 | defer store.RUnlock() 140 | workerKeys := []string{} 141 | selectWorkers := make(map[string]*Worker) 142 | workers := store.GetWorkers(location) 143 | for _, server := range servers { 144 | for _, worker := range workers { 145 | if server == worker.IPAddr || strings.ToUpper(server) == strings.ToUpper(worker.Name) { 146 | workerKeys = append(workerKeys, worker.Key) 147 | selectWorkers[worker.Key] = worker 148 | } 149 | } 150 | } 151 | 152 | size := len(workerKeys) 153 | if size > 0 { 154 | sort.Strings(workerKeys) 155 | index := crc32.ChecksumIEEE([]byte(key)) % (uint32)(size) 156 | workerKey := workerKeys[index] 157 | return selectWorkers[workerKey] 158 | } 159 | return nil 160 | } 161 | 162 | //ClearWorkers is exported 163 | func (store *NodeStore) ClearWorkers() { 164 | 165 | store.Lock() 166 | defer store.Unlock() 167 | for location := range store.nodesData { 168 | store.nodesData[location] = []*Worker{} 169 | delete(store.nodesData, location) 170 | delete(store.circle, location) 171 | } 172 | } 173 | 174 | //CreateWorker is exported 175 | func (store *NodeStore) CreateWorker(location string, attach *models.AttachData, server *models.Server) { 176 | 177 | var ( 178 | ret bool 179 | workers []*Worker 180 | w = &Worker{ 181 | Location: location, 182 | AttachData: attach, 183 | Server: server, 184 | } 185 | ) 186 | 187 | store.Lock() 188 | defer store.Unlock() 189 | added := false 190 | workers, ret = store.nodesData[location] 191 | if !ret { 192 | added = true 193 | store.nodesData[location] = []*Worker{w} 194 | c := algorithm.NewConsisten(50) 195 | c.Add(w.Key) 196 | store.circle[location] = c 197 | } else { 198 | found := false 199 | for _, worker := range workers { 200 | if worker.Key == w.Key { 201 | found = true 202 | break 203 | } 204 | } 205 | if !found { 206 | added = true 207 | store.nodesData[location] = append(store.nodesData[location], w) 208 | store.circle[location].Add(w.Key) 209 | } 210 | } 211 | 212 | if added { 213 | store.callback(NODE_CREATED_EVENT, location, w) 214 | } 215 | } 216 | 217 | //ChangeWorker is exported 218 | func (store *NodeStore) ChangeWorker(location string, attach *models.AttachData, server *models.Server) { 219 | 220 | var ( 221 | ret bool 222 | workers []*Worker 223 | ) 224 | 225 | store.Lock() 226 | defer store.Unlock() 227 | if workers, ret = store.nodesData[location]; !ret { 228 | return 229 | } 230 | 231 | for i := range workers { 232 | if workers[i].Key == server.Key { 233 | workers[i].Server = server 234 | workers[i].AttachData = attach 235 | store.nodesData[location] = workers 236 | store.callback(NODE_CHANGED_EVENT, location, workers[i]) 237 | break 238 | } 239 | } 240 | } 241 | 242 | //RemoveWorker is exported 243 | func (store *NodeStore) RemoveWorker(location string, key string) { 244 | 245 | var ( 246 | ret bool 247 | workers []*Worker 248 | ) 249 | 250 | store.Lock() 251 | defer store.Unlock() 252 | if workers, ret = store.nodesData[location]; !ret { 253 | return 254 | } 255 | 256 | for i, worker := range workers { 257 | if worker.Key == key { 258 | store.nodesData[location] = append(store.nodesData[location][:i], store.nodesData[location][i+1:]...) 259 | if len(store.nodesData[location]) == 0 { 260 | delete(store.nodesData, location) 261 | } 262 | store.circle[location].Remove(key) 263 | if len(store.circle[location].Members()) == 0 { 264 | delete(store.circle, location) 265 | } 266 | store.callback(NODE_REMOVED_EVENT, location, workers[i]) 267 | break 268 | } 269 | } 270 | } 271 | 272 | //RemoveLocation is exported 273 | func (store *NodeStore) RemoveLocation(location string) { 274 | 275 | store.Lock() 276 | if workers, ret := store.nodesData[location]; ret { 277 | delete(store.nodesData, location) 278 | delete(store.circle, location) 279 | for _, worker := range workers { 280 | store.callback(NODE_REMOVED_EVENT, location, worker) 281 | } 282 | } 283 | store.Unlock() 284 | } 285 | -------------------------------------------------------------------------------- /cache/driver/mongo/mongo.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache/driver" 4 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 5 | import "github.com/cloudtask/common/models" 6 | import "github.com/cloudtask/libtools/gounits/logger" 7 | 8 | import ( 9 | "errors" 10 | "sync" 11 | ) 12 | 13 | var ( 14 | //ErrMongoStorageDriverHostsInvalid is exported, parameters map not has 'hosts' key error. 15 | ErrMongoStorageDriverHostsInvalid = errors.New("mongo storage driver hosts invalid") 16 | //ErrMongoStorageDriverDataBaseInvalid is exported, parameters map not has 'database' key error. 17 | ErrMongoStorageDriverDataBaseInvalid = errors.New("mongo storage driver database invalid") 18 | ) 19 | 20 | //MongoStorageDriver is exported 21 | type MongoStorageDriver struct { 22 | sync.RWMutex 23 | driver.StorageDriver 24 | engine *Engine 25 | } 26 | 27 | func init() { 28 | driver.AddDriver(types.MONGO, New) 29 | } 30 | 31 | //New is exported 32 | func New(parameters types.Parameters) (driver.StorageDriver, error) { 33 | 34 | mgoConfigs, err := parseEngineConfigs(parameters) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return &MongoStorageDriver{ 40 | engine: NewEngine(mgoConfigs), 41 | }, nil 42 | } 43 | 44 | func parseEngineConfigs(parameters types.Parameters) (MgoConfigs, error) { 45 | 46 | var ( 47 | ret bool 48 | value interface{} 49 | ) 50 | 51 | mgoConfigs := MgoConfigs{ 52 | Auth: map[string]string{}, 53 | Options: []string{}, 54 | } 55 | 56 | value, ret = parameters["hosts"] 57 | if !ret { 58 | return mgoConfigs, ErrMongoStorageDriverHostsInvalid 59 | } 60 | mgoConfigs.Hosts = value.(string) 61 | 62 | value, ret = parameters["database"] 63 | if !ret { 64 | return mgoConfigs, ErrMongoStorageDriverDataBaseInvalid 65 | } 66 | mgoConfigs.DataBase = value.(string) 67 | 68 | if value, ret = parameters["auth"]; ret { 69 | if auth, ok := value.(map[string]interface{}); ok { 70 | if user, ok := auth["user"]; ok { 71 | mgoConfigs.Auth["user"] = user.(string) 72 | } 73 | if password, ok := auth["password"]; ok { 74 | mgoConfigs.Auth["password"] = password.(string) 75 | } 76 | } 77 | } 78 | 79 | if value, ret = parameters["options"]; ret { 80 | if options, ok := value.([]interface{}); ok { 81 | for _, option := range options { 82 | mgoConfigs.Options = append(mgoConfigs.Options, option.(string)) 83 | } 84 | } 85 | } 86 | return mgoConfigs, nil 87 | } 88 | 89 | //Open is exported 90 | func (driver *MongoStorageDriver) Open() error { 91 | 92 | return driver.engine.Open() 93 | } 94 | 95 | //Close is exported 96 | func (driver *MongoStorageDriver) Close() { 97 | 98 | driver.engine.Close() 99 | } 100 | 101 | //SetConfigParameters is exported 102 | func (driver *MongoStorageDriver) SetConfigParameters(parameters types.Parameters) { 103 | 104 | mgoConfigs, err := parseEngineConfigs(parameters) 105 | if err != nil { 106 | logger.ERROR("[#cache#] mongo driver parse configs error, %s", err.Error()) 107 | return 108 | } 109 | 110 | err = driver.engine.SetConfigParameters(mgoConfigs) 111 | if err != nil { 112 | logger.ERROR("[#cache#] mongo driver set configs error, %s", err.Error()) 113 | return 114 | } 115 | logger.ERROR("[#cache#] mongo driver configs changed, %+v", mgoConfigs) 116 | } 117 | 118 | //GetLocationsName is exported 119 | func (driver *MongoStorageDriver) GetLocationsName() []string { 120 | 121 | driver.RLock() 122 | defer driver.RUnlock() 123 | names, err := driver.engine.readLocationsName() 124 | if err != nil { 125 | logger.ERROR("[#cache#] engine read locations name error, %s", err.Error()) 126 | return []string{} 127 | } 128 | return names 129 | } 130 | 131 | //GetLocation is exported 132 | func (driver *MongoStorageDriver) GetLocation(location string) *models.WorkLocation { 133 | 134 | driver.RLock() 135 | defer driver.RUnlock() 136 | workLocation, err := driver.engine.getLocation(location) 137 | if err != nil { 138 | logger.ERROR("[#cache#] engine read location %s error, %s", location, err.Error()) 139 | return nil 140 | } 141 | return workLocation 142 | } 143 | 144 | //GetLocationSimpleJobs is exported 145 | func (driver *MongoStorageDriver) GetLocationSimpleJobs(location string) []*models.SimpleJob { 146 | 147 | driver.RLock() 148 | defer driver.RUnlock() 149 | query := M{"location": location} 150 | jobs, err := driver.engine.readSimpleJobs(query) 151 | if err != nil { 152 | logger.ERROR("[#cache#] engine read simple jobs %+v error, %s", query, err.Error()) 153 | return []*models.SimpleJob{} 154 | } 155 | return jobs 156 | } 157 | 158 | //GetSimpleJob is exported 159 | func (driver *MongoStorageDriver) GetSimpleJob(jobid string) *models.SimpleJob { 160 | 161 | driver.RLock() 162 | defer driver.RUnlock() 163 | job, err := driver.engine.getSimpleJob(jobid) 164 | if err != nil { 165 | logger.ERROR("[#cache#] engine get simple job %s error, %s", jobid, err.Error()) 166 | return nil 167 | } 168 | return job 169 | } 170 | 171 | //GetJobs is exported 172 | func (driver *MongoStorageDriver) GetJobs() []*models.Job { 173 | 174 | driver.RLock() 175 | defer driver.RUnlock() 176 | query := M{} 177 | jobs, err := driver.engine.readJobs(query) 178 | if err != nil { 179 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 180 | return []*models.Job{} 181 | } 182 | return jobs 183 | } 184 | 185 | //GetStateJobs is exported 186 | func (driver *MongoStorageDriver) GetStateJobs(state int) []*models.Job { 187 | 188 | driver.RLock() 189 | defer driver.RUnlock() 190 | query := M{"stat": state} 191 | jobs, err := driver.engine.readJobs(query) 192 | if err != nil { 193 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 194 | return []*models.Job{} 195 | } 196 | return jobs 197 | } 198 | 199 | //GetLocationJobs is exported 200 | func (driver *MongoStorageDriver) GetLocationJobs(location string) []*models.Job { 201 | 202 | driver.RLock() 203 | defer driver.RUnlock() 204 | query := M{"location": location} 205 | jobs, err := driver.engine.readJobs(query) 206 | if err != nil { 207 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 208 | return []*models.Job{} 209 | } 210 | return jobs 211 | } 212 | 213 | //GetGroupJobs is exported 214 | func (driver *MongoStorageDriver) GetGroupJobs(groupid string) []*models.Job { 215 | 216 | driver.RLock() 217 | defer driver.RUnlock() 218 | query := M{"groupid": groupid} 219 | jobs, err := driver.engine.readJobs(query) 220 | if err != nil { 221 | logger.ERROR("[#cache#] engine read jobs %+v error, %s", query, err.Error()) 222 | return []*models.Job{} 223 | } 224 | return jobs 225 | } 226 | 227 | //GetJob is exported 228 | func (driver *MongoStorageDriver) GetJob(jobid string) *models.Job { 229 | 230 | driver.RLock() 231 | defer driver.RUnlock() 232 | job, err := driver.engine.getJob(jobid) 233 | if err != nil { 234 | logger.ERROR("[#cache#] engine get job %s error, %s", jobid, err.Error()) 235 | return nil 236 | } 237 | return job 238 | } 239 | 240 | //SetJob is exported 241 | func (driver *MongoStorageDriver) SetJob(job *models.Job) { 242 | 243 | driver.Lock() 244 | defer driver.Unlock() 245 | if err := driver.engine.putJob(job); err != nil { 246 | logger.ERROR("[#cache#] engine set job %s error, %s", job.JobId, err.Error()) 247 | } 248 | } 249 | 250 | //SetJobLog is exported 251 | func (driver *MongoStorageDriver) SetJobLog(joblog *models.JobLog) { 252 | 253 | driver.Lock() 254 | defer driver.Unlock() 255 | if err := driver.engine.postJobLog(joblog); err != nil { 256 | logger.ERROR("[#cache#] engine post job %s error, %s", joblog.JobId, err.Error()) 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /cache/driver/ngcloud/engine.go: -------------------------------------------------------------------------------- 1 | package ngcloud 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 4 | import "github.com/cloudtask/common/models" 5 | import "github.com/cloudtask/libtools/gounits/httpx" 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "net" 11 | "net/http" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | //Engine is exported 17 | type Engine struct { 18 | rawAPIURL string 19 | readPageSize int 20 | client *httpx.HttpClient 21 | } 22 | 23 | //NewEngine is exported 24 | func NewEngine(rawAPIURL string, readPageSize int) *Engine { 25 | 26 | client := httpx.NewClient(). 27 | SetTransport(&http.Transport{ 28 | Proxy: http.ProxyFromEnvironment, 29 | DialContext: (&net.Dialer{ 30 | Timeout: 30 * time.Second, 31 | KeepAlive: 60 * time.Second, 32 | }).DialContext, 33 | DisableKeepAlives: false, 34 | MaxIdleConns: 25, 35 | MaxIdleConnsPerHost: 25, 36 | IdleConnTimeout: 90 * time.Second, 37 | TLSHandshakeTimeout: http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout, 38 | ExpectContinueTimeout: http.DefaultTransport.(*http.Transport).ExpectContinueTimeout, 39 | }) 40 | 41 | return &Engine{ 42 | rawAPIURL: rawAPIURL, 43 | readPageSize: readPageSize, 44 | client: client, 45 | } 46 | } 47 | 48 | func (engine *Engine) SetConfigParameters(rawAPIURL string, readPageSize int) { 49 | 50 | engine.rawAPIURL = rawAPIURL 51 | engine.readPageSize = readPageSize 52 | } 53 | 54 | func (engine *Engine) getLocation(location string) (*models.WorkLocation, error) { 55 | 56 | respData, err := engine.client.Get(context.Background(), engine.rawAPIURL+"/sys_locations/"+location, nil, nil) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | defer respData.Close() 62 | statusCode := respData.StatusCode() 63 | if statusCode >= http.StatusBadRequest { 64 | return nil, fmt.Errorf("HTTP GET sys_locations %s failure %d.", location, statusCode) 65 | } 66 | 67 | if statusCode == http.StatusNoContent { 68 | return nil, types.ErrDriverResourceNotFound 69 | } 70 | 71 | workLocation := &models.WorkLocation{} 72 | if err := respData.JSON(workLocation); err != nil { 73 | return nil, err 74 | } 75 | return workLocation, nil 76 | } 77 | 78 | func (engine *Engine) postLocation(workLocation *models.WorkLocation) error { 79 | 80 | respData, err := engine.client.PostJSON(context.Background(), engine.rawAPIURL+"/sys_locations", nil, workLocation, nil) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | defer respData.Close() 86 | statusCode := respData.StatusCode() 87 | if statusCode >= http.StatusBadRequest { 88 | return fmt.Errorf("HTTP POST sys_locations %s failure %d.", workLocation.Location, statusCode) 89 | } 90 | return nil 91 | } 92 | 93 | func (engine *Engine) readLocationsName() ([]string, error) { 94 | 95 | var ( 96 | pageIndex = 1 97 | names = []string{} 98 | ) 99 | 100 | query := map[string][]string{ 101 | "pageSize": []string{strconv.Itoa(engine.readPageSize)}, 102 | "fields": []string{`["location"]`}, 103 | } 104 | 105 | for { 106 | query["pageIndex"] = []string{strconv.Itoa(pageIndex)} 107 | respData, err := engine.client.Get(context.Background(), engine.rawAPIURL+"/sys_locations", query, nil) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | defer respData.Close() 113 | statusCode := respData.StatusCode() 114 | if statusCode >= http.StatusBadRequest { 115 | return nil, fmt.Errorf("HTTP GET sys_locations names failure %d.", statusCode) 116 | } 117 | 118 | respLocationsName, err := parseLocationsNameResponse(respData) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | names = append(names, respLocationsName.Names...) 124 | if len(names) != respLocationsName.TotalRows { 125 | pageIndex++ 126 | continue 127 | } 128 | break 129 | } 130 | return names, nil 131 | } 132 | 133 | func (engine *Engine) readSimpleJobs(query map[string][]string) ([]*models.SimpleJob, error) { 134 | 135 | var ( 136 | pageIndex = 1 137 | jobs = []*models.SimpleJob{} 138 | ) 139 | 140 | query["pageSize"] = []string{strconv.Itoa(engine.readPageSize)} 141 | query["fields"] = []string{`["jobid","name","location","groupid","servers","enabled","stat"]`} 142 | for { 143 | query["pageIndex"] = []string{strconv.Itoa(pageIndex)} 144 | respData, err := engine.client.Get(context.Background(), engine.rawAPIURL+"/sys_jobs", query, nil) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | defer respData.Close() 150 | statusCode := respData.StatusCode() 151 | if statusCode >= http.StatusBadRequest { 152 | return nil, fmt.Errorf("HTTP GET sys_jobs jobs failure %d.", statusCode) 153 | } 154 | 155 | respSimpleJobs, err := parseSimpleJobsResponse(respData) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | jobs = append(jobs, respSimpleJobs.Jobs...) 161 | if len(jobs) != respSimpleJobs.TotalRows { 162 | pageIndex++ 163 | continue 164 | } 165 | break 166 | } 167 | return jobs, nil 168 | } 169 | 170 | func (engine *Engine) readJobs(query map[string][]string) ([]*models.Job, error) { 171 | 172 | var ( 173 | pageIndex = 1 174 | jobs = []*models.Job{} 175 | ) 176 | 177 | query["pageSize"] = []string{strconv.Itoa(engine.readPageSize)} 178 | for { 179 | query["pageIndex"] = []string{strconv.Itoa(pageIndex)} 180 | respData, err := engine.client.Get(context.Background(), engine.rawAPIURL+"/sys_jobs", query, nil) 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | defer respData.Close() 186 | statusCode := respData.StatusCode() 187 | if statusCode >= http.StatusBadRequest { 188 | return nil, fmt.Errorf("HTTP GET sys_jobs jobs failure %d.", statusCode) 189 | } 190 | 191 | respJobs, err := parseJobsResponse(respData) 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | jobs = append(jobs, respJobs.Jobs...) 197 | if len(jobs) != respJobs.TotalRows { 198 | pageIndex++ 199 | continue 200 | } 201 | break 202 | } 203 | return jobs, nil 204 | } 205 | 206 | func (engine *Engine) getSimpleJob(jobid string) (*models.SimpleJob, error) { 207 | 208 | job, err := engine.getJob(jobid) 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | return &models.SimpleJob{ 214 | JobId: job.JobId, 215 | Name: job.Name, 216 | Location: job.Location, 217 | GroupId: job.GroupId, 218 | Servers: job.Servers, 219 | Enabled: job.Enabled, 220 | Stat: job.Stat, 221 | }, nil 222 | } 223 | 224 | func (engine *Engine) getJob(jobid string) (*models.Job, error) { 225 | 226 | respData, err := engine.client.Get(context.Background(), engine.rawAPIURL+"/sys_jobs/"+jobid, nil, nil) 227 | if err != nil { 228 | return nil, err 229 | } 230 | 231 | defer respData.Close() 232 | statusCode := respData.StatusCode() 233 | if statusCode >= http.StatusBadRequest { 234 | return nil, fmt.Errorf("HTTP GET sys_jobs %s failure %d.", jobid, statusCode) 235 | } 236 | 237 | if statusCode == http.StatusNoContent { 238 | return nil, types.ErrDriverResourceNotFound 239 | } 240 | 241 | job := &models.Job{} 242 | if err := respData.JSON(job); err != nil { 243 | return nil, err 244 | } 245 | return job, nil 246 | } 247 | 248 | func (engine *Engine) putJob(job *models.Job) error { 249 | 250 | respData, err := engine.client.PutJSON(context.Background(), engine.rawAPIURL+"/sys_jobs", nil, job, nil) 251 | if err != nil { 252 | return err 253 | } 254 | 255 | defer respData.Close() 256 | statusCode := respData.StatusCode() 257 | if statusCode >= http.StatusBadRequest { 258 | return fmt.Errorf("HTTP PUT sys_jobs %s failure %d.", job.JobId, statusCode) 259 | } 260 | return nil 261 | } 262 | 263 | func (engine *Engine) postJobLog(jobLog *models.JobLog) error { 264 | 265 | query := map[string][]string{ 266 | "strict": []string{"true"}, 267 | } 268 | 269 | resp, err := engine.client.PostJSON(context.Background(), engine.rawAPIURL+"/logs", query, jobLog, nil) 270 | if err != nil { 271 | return err 272 | } 273 | 274 | defer resp.Close() 275 | statusCode := resp.StatusCode() 276 | if statusCode >= http.StatusBadRequest { 277 | return fmt.Errorf("HTTP POST logs %s failure %d.", jobLog.JobId, statusCode) 278 | } 279 | return nil 280 | } 281 | -------------------------------------------------------------------------------- /cache/driver/mongo/engine.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 4 | import "github.com/cloudtask/common/models" 5 | import "github.com/cloudtask/libtools/gounits/system" 6 | import "gopkg.in/mgo.v2/bson" 7 | import mgo "gopkg.in/mgo.v2" 8 | 9 | import ( 10 | "strings" 11 | "time" 12 | ) 13 | 14 | const ( 15 | //mongo connect maxPoolSize default value. 16 | defaultMaxPoolSize = 20 17 | ) 18 | 19 | const ( 20 | //sysLocatinsCollection is exported, mongo sys_locations collection name define. 21 | sysLocatinsCollection = "sys_locations" 22 | //sysJobsCollection is exported, mongo sys_jobs collection name define. 23 | sysJobsCollection = "sys_jobs" 24 | //sysLogsCollection is exported, mongo logs collection name define. 25 | sysLogsCollection = "logs" 26 | ) 27 | 28 | //M is exported 29 | //mongo bson map type define. 30 | type M bson.M 31 | 32 | //D is exported 33 | type D bson.D 34 | 35 | //MgoConfigs is exported 36 | type MgoConfigs struct { 37 | Hosts string 38 | DataBase string 39 | Auth map[string]string 40 | Options []string 41 | } 42 | 43 | //Engine is exported 44 | type Engine struct { 45 | MgoConfigs 46 | globalSession *mgo.Session 47 | failPulseTimes int 48 | stopCh chan struct{} 49 | } 50 | 51 | //NewEngine is exported 52 | func NewEngine(configs MgoConfigs) *Engine { 53 | 54 | return &Engine{ 55 | MgoConfigs: configs, 56 | } 57 | } 58 | 59 | func generateHostURL(configs MgoConfigs) (string, error) { 60 | 61 | configs.Hosts = strings.TrimSpace(configs.Hosts) 62 | if len(configs.Hosts) == 0 { 63 | return "", ErrMongoStorageDriverHostsInvalid 64 | } 65 | 66 | configs.DataBase = strings.TrimSpace(configs.DataBase) 67 | if len(configs.DataBase) == 0 { 68 | return "", ErrMongoStorageDriverDataBaseInvalid 69 | } 70 | 71 | var authStr string 72 | if len(configs.Auth) > 0 { 73 | var ( 74 | user, password string 75 | ret bool 76 | ) 77 | if user, ret = configs.Auth["user"]; ret { 78 | authStr = user 79 | if password, ret = configs.Auth["password"]; ret { 80 | authStr = authStr + ":" + password 81 | } 82 | authStr = authStr + "@" 83 | } 84 | } 85 | 86 | var optsStr string 87 | if len(configs.Options) > 0 { 88 | for index, value := range configs.Options { 89 | optsStr = optsStr + value 90 | if index != len(configs.Options)-1 { 91 | optsStr = optsStr + "&" 92 | } 93 | } 94 | } 95 | 96 | mgoURL := "mongodb://" + authStr + configs.Hosts + "/" + configs.DataBase 97 | if optsStr != "" { 98 | mgoURL = mgoURL + "?" + optsStr 99 | } 100 | 101 | if _, err := mgo.ParseURL(mgoURL); err != nil { 102 | return "", err 103 | } 104 | return mgoURL, nil 105 | } 106 | 107 | //SetConfigParameters is exported 108 | func (engine *Engine) SetConfigParameters(configs MgoConfigs) error { 109 | 110 | engine.MgoConfigs = configs 111 | engine.Close() 112 | return engine.Open() 113 | } 114 | 115 | //Open is exported 116 | func (engine *Engine) Open() error { 117 | 118 | var maxPoolSize = defaultMaxPoolSize 119 | opts := system.DriverOpts(engine.Options) 120 | if value, ret := opts.Int("maxPoolSize", ""); ret { 121 | maxPoolSize = (int)(value) 122 | } 123 | 124 | mgoURL, err := generateHostURL(engine.MgoConfigs) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | session, err := mgo.Dial(mgoURL) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | session.SetMode(mgo.Strong, true) 135 | session.SetPoolLimit(maxPoolSize) 136 | engine.globalSession = session 137 | if engine.stopCh == nil { 138 | engine.stopCh = make(chan struct{}) 139 | go engine.pulseSessionLoop() 140 | } 141 | return nil 142 | } 143 | 144 | //Close is exported 145 | func (engine *Engine) Close() { 146 | 147 | if engine.stopCh != nil { 148 | close(engine.stopCh) 149 | engine.stopCh = nil 150 | } 151 | 152 | if engine.globalSession != nil { 153 | engine.globalSession.Close() 154 | } 155 | } 156 | 157 | func (engine *Engine) getLocation(location string) (*models.WorkLocation, error) { 158 | 159 | session := engine.getSession() 160 | defer session.Close() 161 | workLocation := &models.WorkLocation{} 162 | if err := session.DB(engine.DataBase).C(sysLocatinsCollection). 163 | Find(M{"location": location}). 164 | Select(M{"_id": 0}).One(workLocation); err != nil { 165 | if err == mgo.ErrNotFound { 166 | return nil, types.ErrDriverResourceNotFound 167 | } 168 | return nil, err 169 | } 170 | return workLocation, nil 171 | } 172 | 173 | func (engine *Engine) postLocation(workLocation *models.WorkLocation) error { 174 | 175 | session := engine.getSession() 176 | defer session.Close() 177 | return session.DB(engine.DataBase).C(sysLocatinsCollection). 178 | Insert(workLocation) 179 | } 180 | 181 | func (engine *Engine) putLocation(workLocation *models.WorkLocation) error { 182 | 183 | session := engine.getSession() 184 | defer session.Close() 185 | return session.DB(engine.DataBase).C(sysLocatinsCollection). 186 | Update(M{"location": workLocation.Location}, workLocation) 187 | } 188 | 189 | func (engine *Engine) readLocationsName() ([]string, error) { 190 | 191 | session := engine.getSession() 192 | defer session.Close() 193 | workLocations := []*models.WorkLocation{} 194 | if err := session.DB(engine.DataBase).C(sysLocatinsCollection). 195 | Find(M{}). 196 | Select(M{"_id": 0, "location": 1}). 197 | All(&workLocations); err != nil { 198 | return nil, err 199 | } 200 | 201 | names := []string{} 202 | for _, workLocation := range workLocations { 203 | names = append(names, workLocation.Location) 204 | } 205 | return names, nil 206 | } 207 | 208 | func (engine *Engine) readSimpleJobs(query M) ([]*models.SimpleJob, error) { 209 | 210 | session := engine.getSession() 211 | defer session.Close() 212 | jobs := []*models.SimpleJob{} 213 | if err := session.DB(engine.DataBase).C(sysJobsCollection). 214 | Find(query). 215 | Select(M{"_id": 0, "jobid": 1, "name": 1, "location": 1, "groupid": 1, "servers": 1, "enabled": 1, "stat": 1}). 216 | All(&jobs); err != nil { 217 | return nil, err 218 | } 219 | return jobs, nil 220 | } 221 | 222 | func (engine *Engine) readJobs(query M) ([]*models.Job, error) { 223 | 224 | session := engine.getSession() 225 | defer session.Close() 226 | jobs := []*models.Job{} 227 | if err := session.DB(engine.DataBase).C(sysJobsCollection). 228 | Find(query). 229 | Select(M{"_id": 0}). 230 | All(&jobs); err != nil { 231 | return nil, err 232 | } 233 | return jobs, nil 234 | } 235 | 236 | func (engine *Engine) getSimpleJob(jobid string) (*models.SimpleJob, error) { 237 | 238 | job, err := engine.getJob(jobid) 239 | if err != nil { 240 | return nil, err 241 | } 242 | 243 | return &models.SimpleJob{ 244 | JobId: job.JobId, 245 | Name: job.Name, 246 | Location: job.Location, 247 | GroupId: job.GroupId, 248 | Servers: job.Servers, 249 | Enabled: job.Enabled, 250 | Stat: job.Stat, 251 | }, nil 252 | } 253 | 254 | func (engine *Engine) getJob(jobid string) (*models.Job, error) { 255 | 256 | session := engine.getSession() 257 | defer session.Close() 258 | job := &models.Job{} 259 | if err := session.DB(engine.DataBase).C(sysJobsCollection). 260 | Find(M{"jobid": jobid}). 261 | Select(M{"_id": 0}).One(job); err != nil { 262 | if err == mgo.ErrNotFound { 263 | return nil, types.ErrDriverResourceNotFound 264 | } 265 | return nil, err 266 | } 267 | return job, nil 268 | } 269 | 270 | func (engine *Engine) putJob(job *models.Job) error { 271 | 272 | session := engine.getSession() 273 | defer session.Close() 274 | return session.DB(engine.DataBase).C(sysJobsCollection). 275 | Update(M{"jobid": job.JobId}, M{"$set": M{ 276 | "stat": job.Stat, 277 | "execerr": job.ExecErr, 278 | "execat": job.ExecAt, 279 | "nextat": job.NextAt}}) 280 | } 281 | 282 | func (engine *Engine) postJobLog(jobLog *models.JobLog) error { 283 | 284 | session := engine.getSession() 285 | defer session.Close() 286 | return session.DB(engine.DataBase).C(sysLogsCollection). 287 | Insert(jobLog) 288 | } 289 | 290 | func (engine *Engine) getSession() *mgo.Session { 291 | 292 | return engine.globalSession.Clone() 293 | } 294 | 295 | func (engine *Engine) pulseSessionLoop() { 296 | 297 | for { 298 | pulseTicker := time.NewTicker(time.Second * 90) 299 | select { 300 | case <-pulseTicker.C: 301 | { 302 | pulseTicker.Stop() 303 | session := engine.getSession() 304 | if err := session.Ping(); err != nil { 305 | if engine.failPulseTimes > 3 { 306 | engine.globalSession.Refresh() 307 | engine.failPulseTimes = 0 308 | } else { 309 | engine.failPulseTimes = engine.failPulseTimes + 1 310 | } 311 | } else { 312 | engine.failPulseTimes = 0 313 | } 314 | session.Close() 315 | } 316 | case <-engine.stopCh: 317 | { 318 | pulseTicker.Stop() 319 | return 320 | } 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /cache/alloc.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/cloudtask/common/models" 4 | 5 | import ( 6 | "bytes" 7 | "sync" 8 | ) 9 | 10 | //AllocEvent is exported 11 | type AllocEvent int 12 | 13 | const ( 14 | ALLOC_CREATED_EVENT AllocEvent = iota + 1 15 | ALLOC_CHANGED_EVENT 16 | ALLOC_REMOVED_EVENT 17 | ) 18 | 19 | func (event AllocEvent) String() string { 20 | 21 | switch event { 22 | case ALLOC_CREATED_EVENT: 23 | return "ALLOC_CREATED_EVENT" 24 | case ALLOC_CHANGED_EVENT: 25 | return "ALLOC_CHANGED_EVENT" 26 | case ALLOC_REMOVED_EVENT: 27 | return "ALLOC_REMOVED_EVENT" 28 | } 29 | return "" 30 | } 31 | 32 | //AllocCacheEventHandlerFunc is exported 33 | type AllocCacheEventHandlerFunc func(event AllocEvent, location string, data []byte, err error) 34 | 35 | //AllocStore is exported 36 | type AllocStore struct { 37 | sync.RWMutex 38 | allocPool *sync.Pool 39 | tableData models.AllocMapper 40 | callback AllocCacheEventHandlerFunc 41 | } 42 | 43 | //NewAllocStore is exported 44 | func NewAllocStore(callback AllocCacheEventHandlerFunc) *AllocStore { 45 | 46 | allocPool := &sync.Pool{ 47 | New: func() interface{} { //数据编码池,默认分配128K 48 | return bytes.NewBuffer(make([]byte, 0, 128<<10)) 49 | }, 50 | } 51 | 52 | return &AllocStore{ 53 | allocPool: allocPool, 54 | tableData: make(models.AllocMapper, 0), 55 | callback: callback, 56 | } 57 | } 58 | 59 | //ClearAlloc is exported 60 | func (store *AllocStore) ClearAlloc() { 61 | 62 | store.Lock() 63 | defer store.Unlock() 64 | for location := range store.tableData { 65 | store.tableData[location].Jobs = []*models.JobData{} 66 | delete(store.tableData, location) 67 | } 68 | } 69 | 70 | //GetAlloc is exported 71 | //return location jobsalloc. 72 | func (store *AllocStore) GetAlloc(location string) *models.JobsAlloc { 73 | 74 | store.RLock() 75 | defer store.RUnlock() 76 | if jobsAlloc, ret := store.tableData[location]; ret { 77 | return jobsAlloc 78 | } 79 | return nil 80 | } 81 | 82 | //HasAlloc is exported 83 | func (store *AllocStore) HasAlloc(location string) bool { 84 | 85 | store.RLock() 86 | defer store.RUnlock() 87 | _, ret := store.tableData[location] 88 | return ret 89 | } 90 | 91 | //HasAllocJobId is exported 92 | func (store *AllocStore) HasAllocJobId(location string, jobId string) bool { 93 | 94 | store.RLock() 95 | defer store.RUnlock() 96 | jobsAlloc, ret := store.tableData[location] 97 | if !ret { 98 | return false 99 | } 100 | 101 | for _, jobData := range jobsAlloc.Jobs { 102 | if jobData.JobId == jobId { 103 | return true 104 | } 105 | } 106 | return false 107 | } 108 | 109 | //GetAllocJobIds is exported 110 | //return all jobs ids contained in location key. 111 | func (store *AllocStore) GetAllocJobIds(location string, key string) []string { 112 | 113 | store.RLock() 114 | defer store.RUnlock() 115 | jobIds := []string{} 116 | if jobsAlloc, ret := store.tableData[location]; ret { 117 | for _, jobData := range jobsAlloc.Jobs { 118 | if jobData.Key == key { 119 | jobIds = append(jobIds, jobData.JobId) 120 | } 121 | } 122 | } 123 | return jobIds 124 | } 125 | 126 | //SetAllocJobsKey is exported 127 | //set location alloc's jobs key. 128 | func (store *AllocStore) SetAllocJobsKey(location string, jobs map[string]string) { 129 | 130 | var ( 131 | ret bool 132 | jobsAlloc *models.JobsAlloc 133 | ) 134 | 135 | store.Lock() 136 | defer store.Unlock() 137 | if jobsAlloc, ret = store.tableData[location]; !ret { 138 | return 139 | } 140 | 141 | for jobid, key := range jobs { 142 | for _, jobData := range jobsAlloc.Jobs { 143 | if jobData.JobId == jobid { 144 | jobData.Key = key 145 | break 146 | } 147 | } 148 | } 149 | 150 | jobsAlloc.Version = jobsAlloc.Version + 1 151 | data, err := models.JobsAllocEnCode(store.allocPool, jobsAlloc) 152 | store.callback(ALLOC_CHANGED_EVENT, location, data, err) 153 | } 154 | 155 | //GetAllocJob is exported 156 | //return location alloc's jobdata. 157 | func (store *AllocStore) GetAllocJob(location string, jobId string) *models.JobData { 158 | 159 | store.RLock() 160 | defer store.RUnlock() 161 | if jobsAlloc, ret := store.tableData[location]; ret { 162 | for _, jobData := range jobsAlloc.Jobs { 163 | if jobData.JobId == jobId { 164 | return jobData 165 | } 166 | } 167 | } 168 | return nil 169 | } 170 | 171 | //CreateAllocJob is exported 172 | //create jobdata to location alloc. 173 | func (store *AllocStore) CreateAllocJob(location string, key string, jobId string) { 174 | 175 | store.Lock() 176 | defer store.Unlock() 177 | var allocEvent AllocEvent 178 | jobsAlloc, ret := store.tableData[location] 179 | if !ret { 180 | jobsAlloc = &models.JobsAlloc{ 181 | Version: 1, 182 | Jobs: []*models.JobData{ 183 | &models.JobData{ 184 | JobId: jobId, 185 | Key: key, 186 | Version: 1, 187 | }, 188 | }, 189 | } 190 | store.tableData[location] = jobsAlloc 191 | allocEvent = ALLOC_CREATED_EVENT 192 | } else { 193 | found := false 194 | for _, jobData := range jobsAlloc.Jobs { 195 | if jobData.JobId == jobId { 196 | jobData.Version = jobData.Version + 1 197 | found = true 198 | break 199 | } 200 | } 201 | if !found { 202 | jobsAlloc.Jobs = append(jobsAlloc.Jobs, &models.JobData{ 203 | JobId: jobId, 204 | Key: key, 205 | Version: 1}) 206 | } 207 | jobsAlloc.Version = jobsAlloc.Version + 1 208 | allocEvent = ALLOC_CHANGED_EVENT 209 | } 210 | 211 | data, err := models.JobsAllocEnCode(store.allocPool, jobsAlloc) 212 | store.callback(allocEvent, location, data, err) 213 | } 214 | 215 | //UpdateAllocJobs is exported 216 | //update location's jobIds version & alloc version. 217 | func (store *AllocStore) UpdateAllocJobs(location string, jobIds []string) { 218 | 219 | var ( 220 | ret bool 221 | jobsAlloc *models.JobsAlloc 222 | ) 223 | 224 | store.Lock() 225 | defer store.Unlock() 226 | if jobsAlloc, ret = store.tableData[location]; !ret { 227 | return 228 | } 229 | 230 | for _, jobId := range jobIds { 231 | for _, jobData := range jobsAlloc.Jobs { 232 | if jobData.JobId == jobId { 233 | jobData.Version = jobData.Version + 1 234 | break 235 | } 236 | } 237 | } 238 | 239 | jobsAlloc.Version = jobsAlloc.Version + 1 240 | data, err := models.JobsAllocEnCode(store.allocPool, jobsAlloc) 241 | store.callback(ALLOC_CHANGED_EVENT, location, data, err) 242 | } 243 | 244 | //RemoveAllocJob is exported 245 | //remove jobdata to location alloc. 246 | func (store *AllocStore) RemoveAllocJob(location string, jobId string) { 247 | 248 | var ( 249 | ret bool 250 | jobsAlloc *models.JobsAlloc 251 | ) 252 | 253 | store.Lock() 254 | defer store.Unlock() 255 | if jobsAlloc, ret = store.tableData[location]; !ret { 256 | return 257 | } 258 | 259 | for i, jobData := range jobsAlloc.Jobs { 260 | if jobData.JobId == jobId { 261 | jobsAlloc.Jobs = append(jobsAlloc.Jobs[:i], jobsAlloc.Jobs[i+1:]...) 262 | jobsAlloc.Version = jobsAlloc.Version + 1 263 | data, err := models.JobsAllocEnCode(store.allocPool, jobsAlloc) 264 | store.callback(ALLOC_CHANGED_EVENT, location, data, err) 265 | break 266 | } 267 | } 268 | } 269 | 270 | //RemoveAllocJobs is exported 271 | func (store *AllocStore) RemoveAllocJobs(location string, jobIds []string) { 272 | 273 | var ( 274 | ret bool 275 | jobsAlloc *models.JobsAlloc 276 | ) 277 | 278 | store.Lock() 279 | defer store.Unlock() 280 | if jobsAlloc, ret = store.tableData[location]; !ret { 281 | return 282 | } 283 | 284 | found := false 285 | for _, jobId := range jobIds { 286 | for i, jobData := range jobsAlloc.Jobs { 287 | if jobData.JobId == jobId { 288 | jobsAlloc.Jobs = append(jobsAlloc.Jobs[:i], jobsAlloc.Jobs[i+1:]...) 289 | found = true 290 | break 291 | } 292 | } 293 | } 294 | 295 | if found { 296 | jobsAlloc.Version = jobsAlloc.Version + 1 297 | data, err := models.JobsAllocEnCode(store.allocPool, jobsAlloc) 298 | store.callback(ALLOC_CHANGED_EVENT, location, data, err) 299 | } 300 | } 301 | 302 | //CreateAlloc is exported 303 | func (store *AllocStore) CreateAlloc(location string, data []byte) { 304 | 305 | store.Lock() 306 | if _, ret := store.tableData[location]; !ret { 307 | jobsAlloc := &models.JobsAlloc{} 308 | var err error 309 | if err = models.JobsAllocDeCode(data, jobsAlloc); err == nil { 310 | store.tableData[location] = jobsAlloc 311 | } 312 | store.callback(ALLOC_CREATED_EVENT, location, data, err) 313 | } 314 | store.Unlock() 315 | } 316 | 317 | //RemoveAlloc is exported 318 | func (store *AllocStore) RemoveAlloc(location string) { 319 | 320 | store.Lock() 321 | if jobsAlloc, ret := store.tableData[location]; ret { 322 | delete(store.tableData, location) 323 | jobsAlloc.Jobs = []*models.JobData{} 324 | jobsAlloc.Version = jobsAlloc.Version + 1 325 | data, err := models.JobsAllocEnCode(store.allocPool, jobsAlloc) 326 | store.callback(ALLOC_REMOVED_EVENT, location, data, err) 327 | } 328 | store.Unlock() 329 | } 330 | -------------------------------------------------------------------------------- /APIs.md: -------------------------------------------------------------------------------- 1 | # Cloudtask Center APIs Manual 2 | 3 | > `GET` - http://localhost:8985/cloudtask/v2/_ping 4 | 5 |      center faq api, get center service config value and status info. 6 | 7 | ``` json 8 | /*Response*/ 9 | HTTP 200 OK 10 | { 11 | "app": "296721666eddf741016105ebe1bc3fb4", 12 | "key": "3a95a871-9dc4-4955-b213-5a040e324309", 13 | "node": { 14 | "type": 1, 15 | "hostname": "host.localdomain", 16 | "datacenter": "", 17 | "location": "", 18 | "os": "linux", 19 | "platform": "amd64", 20 | "ipaddr": "192.168.2.80", 21 | "apiaddr": "http://192.168.2.80:8985", 22 | "pid": 1, 23 | "singin": false, 24 | "timestamp": 0, 25 | "alivestamp": 1521268194, 26 | "attach": null 27 | }, 28 | "systemconfig": { 29 | "version": "v.2.0.0", 30 | "pidfile": "./jobserver.pid", 31 | "retrystartup": true, 32 | "useserverconfig": true, 33 | "cluster": { 34 | "hosts": "192.168.2.80:2181,192.168.2.81:2181,192.168.2.82:2181", 35 | "root": "/cloudtask", 36 | "device": "", 37 | "runtime": "", 38 | "os": "", 39 | "platform": "", 40 | "pulse": "30s", 41 | "timeout": "60s", 42 | "threshold": 1 43 | }, 44 | "api": { 45 | "hosts": [ 46 | ":8985" 47 | ], 48 | "enablecors": true 49 | }, 50 | "scheduler": { 51 | "allocmode": "hash", 52 | "allocrecovery": "320s" 53 | }, 54 | "cache": { 55 | "lrusize": 1024, 56 | "storagedriver": { 57 | "mongo": { 58 | "database": "cloudtask", 59 | "hosts": "192.168.2.80:27017,192.168.2.81:27017,192.168.2.82:27017", 60 | "options": [ 61 | "maxPoolSize=20", 62 | "replicaSet=mgoCluster" 63 | ] 64 | } 65 | } 66 | }, 67 | "notifications": { 68 | "EndPoints": [ 69 | { 70 | "Name": "smtp", 71 | "URL": "", 72 | "Enabled": true, 73 | "Sender": "cloudtask@example.com", 74 | "Host": "smtp.example.com", 75 | "Port": 25, 76 | "User": "", 77 | "Password": "" 78 | } 79 | ] 80 | }, 81 | "logger": { 82 | "logfile": "./logs/jobserver.log", 83 | "loglevel": "info", 84 | "logsize": 20971520 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | > `GET` - http://localhost:8985/cloudtask/v2/jobs/{jobid}/base 91 | 92 |      get a job base info. 93 | ``` json 94 | /*Response*/ 95 | HTTP 200 OK 96 | { 97 | "jobid": "399d4b159c65c9b34d2a3c41", 98 | "jobname": "ACCT.ShippingCost.job", 99 | "filename": "", 100 | "filecode": "d41d8cd98f00b204e9800998ecf8427e", 101 | "cmd": "ps aux", 102 | "env": [], 103 | "timeout": 0, 104 | "version": 1, 105 | "schedule": [ 106 | { 107 | "id": "1623e5f1a23", 108 | "enabled": 1, 109 | "turnmode": 2, 110 | "interval": 2, 111 | "startdate": "03/19/2018", 112 | "enddate": "", 113 | "starttime": "00:00", 114 | "endtime": "23:59", 115 | "selectat": "", 116 | "monthlyof": { 117 | "day": 1, 118 | "week": "" 119 | } 120 | }, 121 | { 122 | "id": "1623e5f1a23", 123 | "enabled": 1, 124 | "turnmode": 2, 125 | "interval": 4, 126 | "startdate": "03/19/2018", 127 | "enddate": "", 128 | "starttime": "00:00", 129 | "endtime": "23:59", 130 | "selectat": "", 131 | "monthlyof": { 132 | "day": 1, 133 | "week": "" 134 | } 135 | } 136 | ] 137 | } 138 | ``` 139 | 140 | > `GET` - http://localhost:8985/cloudtask/v2/runtimes/{runtime}/jobsalloc 141 | 142 |      get a runtime jobs alloc table info. 143 | ``` json 144 | /*Response*/ 145 | HTTP 200 OK 146 | { 147 | "content": "request successed.", 148 | "data": { 149 | "alloc": { 150 | "location": "myCluster", 151 | "version": 53, 152 | "data": [ 153 | { 154 | "jobid": "e7994ade7a32b13b4a51788d", 155 | "key": "517ae088-779c-4520-b23f-e5f0c341081d", 156 | "version": 3, 157 | "jobname": "MKPL-XML-Update", 158 | "ipaddr": "192.168.2.81", 159 | "hostname": "localhost.localdomain" 160 | }, 161 | { 162 | "jobid": "666e3b394cfd1ee1577e72b4", 163 | "key": "529ae1f4-d7ea-45e0-0f3f-32aefaba281c", 164 | "version": 2, 165 | "jobname": "ACCT.Accounting.job", 166 | "ipaddr": "192.168.2.80", 167 | "hostname": "localhost.localdomain" 168 | }, 169 | { 170 | "jobid": "399d4b159c65c9b34d2a3c41", 171 | "key": "517ae088-779c-4520-b23f-e5f0c341081d", 172 | "version": 1, 173 | "jobname": "ACCT.ShippingCost.job", 174 | "ipaddr": "192.168.2.81", 175 | "hostname": "host.localdomain" 176 | }, 177 | { 178 | "jobid": "33bd7b52592f4f2c45262e3b", 179 | "key": "509794cc-3539-4f58-a4d3-cc02a4f4848f", 180 | "version": 3, 181 | "jobname": "EDI.Portol", 182 | "ipaddr": "192.168.2.82", 183 | "hostname": "host.localdomain" 184 | }, 185 | { 186 | "jobid": "8fee1ea957b7b6b49bd4e75f", 187 | "key": "529ae1f4-d7ea-45e0-0f3f-32aefaba281c", 188 | "version": 2, 189 | "jobname": "EC.Flash", 190 | "ipaddr": "192.168.2.80", 191 | "hostname": "localhost.localdomain" 192 | }, 193 | { 194 | "jobid": "72ec7bb9decf1e8ea92ad3da", 195 | "key": "509794cc-3539-4f58-a4d3-cc02a4f4848f", 196 | "version": 1, 197 | "jobname": "MKPL-File-Update", 198 | "ipaddr": "192.168.2.82", 199 | "hostname": "host.localdomain" 200 | } 201 | ] 202 | } 203 | } 204 | } 205 | ``` 206 | 207 | > `GET` - http://localhost:8985/cloudtask/v2/runtimes/{runtime}/servers 208 | 209 |      get a runtime current all `healthy` servers. 210 | ``` json 211 | /*Response*/ 212 | HTTP 200 OK 213 | { 214 | "content": "request successed.", 215 | "data": { 216 | "servers": [ 217 | { 218 | "key": "529ae1f4-d7ea-45e0-0f3f-32aefaba281c", 219 | "name": "host.localdomain", 220 | "ipaddr": "192.168.2.80", 221 | "apiaddr": "http://192.168.2.80:8600", 222 | "os": "linux", 223 | "platform": "amd64", 224 | "status": 1, 225 | "alivestamp": 1521276530 226 | }, 227 | { 228 | "key": "517ae088-779c-4520-b23f-e5f0c341081d", 229 | "name": "localhost.localdomain", 230 | "ipaddr": "192.168.2.81", 231 | "apiaddr": "http://192.168.2.81:8600", 232 | "os": "linux", 233 | "platform": "amd64", 234 | "status": 1, 235 | "alivestamp": 1521277792 236 | }, 237 | { 238 | "key": "509794cc-3539-4f58-a4d3-cc02a4f4848f", 239 | "name": "localhost.localdomain", 240 | "ipaddr": "192.168.2.82", 241 | "apiaddr": "http://192.168.2.82:8600", 242 | "os": "linux", 243 | "platform": "amd64", 244 | "status": 1, 245 | "alivestamp": 1521237452 246 | } 247 | ] 248 | } 249 | } 250 | ``` 251 | 252 | > `PUT` - http://localhost:8985/cloudtask/v2/jobs/action 253 | 254 |      action a job, operation is `start` | `stop`. 255 | ``` json 256 | /*Request*/ 257 | { 258 | "runtime": "myCluster", 259 | "jobid": "8fee1ea957b7b6b49bd4e75f", 260 | "action": "start" 261 | } 262 | 263 | /*Response*/ 264 | HTTP 202 Accepted 265 | { 266 | "content": "request accepted." 267 | } 268 | ``` 269 | -------------------------------------------------------------------------------- /etc/configuration.go: -------------------------------------------------------------------------------- 1 | package etc 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache" 4 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 5 | import "github.com/cloudtask/cloudtask-center/notify" 6 | import "github.com/cloudtask/cloudtask-center/scheduler" 7 | import "github.com/cloudtask/libtools/gounits/logger" 8 | import "github.com/cloudtask/libtools/gounits/system" 9 | import "github.com/cloudtask/libtools/gzkwrapper" 10 | import "github.com/cloudtask/common/models" 11 | import "gopkg.in/yaml.v2" 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "io/ioutil" 17 | "log" 18 | "os" 19 | "path/filepath" 20 | "strconv" 21 | "sync" 22 | ) 23 | 24 | var ( 25 | SystemConfig *Configuration = nil 26 | ServerConfig *models.ServerConfig = nil 27 | ) 28 | 29 | var ( 30 | ErrConfigFileNotFound = errors.New("config file not found.") 31 | ErrConfigGenerateFailure = errors.New("config file generated failure.") 32 | ErrConfigFormatInvalid = errors.New("config file format invalid.") 33 | ErrConfigServerDataInvalid = errors.New("config server data invalid.") 34 | ) 35 | 36 | // Configuration is exported 37 | type Configuration struct { 38 | sync.RWMutex 39 | Version string `yaml:"version" json:"version"` 40 | PidFile string `yaml:"pidfile" json:"pidfile"` 41 | RetryStartup bool `yaml:"retrystartup" json:"retrystartup"` 42 | UseServerConfig bool `yaml:"useserverconfig" json:"useserverconfig"` 43 | 44 | Cluster struct { 45 | Hosts string `yaml:"hosts" json:"hosts"` 46 | Root string `yaml:"root" json:"root"` 47 | Device string `yaml:"device" json:"device"` 48 | Runtime string `yaml:"runtime" json:"runtime"` 49 | OS string `yaml:"os" json:"os"` 50 | Platform string `yaml:"platform" json:"platform"` 51 | Pulse string `yaml:"pulse" json:"pulse"` 52 | Timeout string `yaml:"timeout" json:"timeout"` 53 | Threshold int `yaml:"threshold" json:"threshold"` 54 | } `yaml:"cluster" json:"cluster"` 55 | 56 | API struct { 57 | Hosts []string `yaml:"hosts" json:"hosts"` 58 | EnableCors bool `yaml:"enablecors" json:"enablecors"` 59 | } `yaml:"api" json:"api"` 60 | 61 | Scheduler struct { 62 | AllocMode string `yaml:"allocmode" json:"allocmode"` 63 | AllocRecovery string `yaml:"allocrecovery" json:"allocrecovery"` 64 | } `yaml:"scheduler" json:"scheduler"` 65 | 66 | Cache struct { 67 | LRUSize int `yaml:"lrusize" json:"lrusize"` 68 | types.StorageDriverConfigs `yaml:"storagedriver" json:"storagedriver"` 69 | } `yaml:"cache" json:"cache"` 70 | 71 | Notifications notify.Notifications `yaml:"notifications" json:"notifications"` 72 | 73 | Logger struct { 74 | LogFile string `yaml:"logfile" json:"logfile"` 75 | LogLevel string `yaml:"loglevel" json:"loglevel"` 76 | LogSize int64 `yaml:"logsize" json:"logsize"` 77 | } `yaml:"logger" json:"logger"` 78 | } 79 | 80 | // New is exported 81 | func New(file string) error { 82 | 83 | if file != "" { 84 | if !system.FileExist(file) { 85 | cloudtaskENV, _ := os.LookupEnv("CLOUDTASK") 86 | if cloudtaskENV == "" { 87 | return ErrConfigFileNotFound 88 | } 89 | fileName := filepath.Base(file) 90 | if _, err := system.FileCopy("./etc/"+cloudtaskENV+"/"+fileName, file); err != nil { 91 | return ErrConfigGenerateFailure 92 | } 93 | log.Printf("[#etc#] ENV CLOUDTASK: %s\n", cloudtaskENV) 94 | } 95 | } 96 | 97 | buf, err := readConfigurationFile(file) 98 | if err != nil { 99 | return fmt.Errorf("config read %s", err.Error()) 100 | } 101 | 102 | conf := &Configuration{ 103 | RetryStartup: true, 104 | UseServerConfig: true, 105 | } 106 | if err := yaml.Unmarshal(buf, conf); err != nil { 107 | return ErrConfigFormatInvalid 108 | } 109 | 110 | if err = conf.parseEnv(); err != nil { 111 | return fmt.Errorf("config parse env %s", err.Error()) 112 | } 113 | 114 | parseDefaultParmeters(conf) 115 | SystemConfig = conf 116 | log.Printf("[#etc#] version: %s\n", SystemConfig.Version) 117 | log.Printf("[#etc#] pidfile: %s\n", SystemConfig.PidFile) 118 | log.Printf("[#etc#] retrystartup: %s\n", strconv.FormatBool(SystemConfig.RetryStartup)) 119 | log.Printf("[#etc#] useserverconfig: %s\n", strconv.FormatBool(SystemConfig.UseServerConfig)) 120 | log.Printf("[#etc#] cluster: %+v\n", SystemConfig.Cluster) 121 | log.Printf("[#etc#] APIlisten: %+v\n", SystemConfig.API) 122 | log.Printf("[#etc#] scheduler: %+v\n", SystemConfig.Scheduler) 123 | log.Printf("[#etc#] cache: %+v\n", SystemConfig.Cache) 124 | log.Printf("[#etc#] logger: %+v\n", SystemConfig.Logger) 125 | return nil 126 | } 127 | 128 | //SaveServerConfig is exported 129 | func SaveServerConfig(data []byte) error { 130 | 131 | if SystemConfig != nil { 132 | value, err := models.ParseServerConfigs(data) 133 | if err != nil { 134 | return err 135 | } 136 | SystemConfig.Lock() 137 | ServerConfig = value 138 | if value, ok := ServerConfig.StorageDriver.(map[string]interface{}); ok { 139 | SystemConfig.Cache.StorageDriverConfigs = make(types.StorageDriverConfigs) 140 | for backend, paramters := range value { 141 | SystemConfig.Cache.StorageDriverConfigs[backend] = paramters.(map[string]interface{}) 142 | break 143 | } 144 | } 145 | SystemConfig.Unlock() 146 | } 147 | return nil 148 | } 149 | 150 | //PidFile is exported 151 | func PidFile() string { 152 | 153 | if SystemConfig != nil { 154 | return SystemConfig.PidFile 155 | } 156 | return "" 157 | } 158 | 159 | //RetryStartup is exported 160 | func RetryStartup() bool { 161 | 162 | if SystemConfig != nil { 163 | return SystemConfig.RetryStartup 164 | } 165 | return false 166 | } 167 | 168 | //UseServerConfig is exported 169 | func UseServerConfig() bool { 170 | 171 | if SystemConfig != nil { 172 | return SystemConfig.UseServerConfig 173 | } 174 | return false 175 | } 176 | 177 | //SchedulerConfigs is exported 178 | func SchedulerConfigs() *scheduler.SchedulerConfigs { 179 | 180 | if SystemConfig != nil { 181 | return &scheduler.SchedulerConfigs{ 182 | AllocMode: SystemConfig.Scheduler.AllocMode, 183 | AllocRecovery: SystemConfig.Scheduler.AllocRecovery, 184 | } 185 | } 186 | return nil 187 | } 188 | 189 | //ClusterConfigs is exported 190 | func ClusterConfigs() *gzkwrapper.ServerArgs { 191 | 192 | if SystemConfig != nil { 193 | return &gzkwrapper.ServerArgs{ 194 | Hosts: SystemConfig.Cluster.Hosts, 195 | Root: SystemConfig.Cluster.Root, 196 | Device: SystemConfig.Cluster.Device, 197 | Location: SystemConfig.Cluster.Runtime, 198 | OS: SystemConfig.Cluster.OS, 199 | Platform: SystemConfig.Cluster.Platform, 200 | APIAddr: SystemConfig.API.Hosts[0], 201 | Pulse: SystemConfig.Cluster.Pulse, 202 | Timeout: SystemConfig.Cluster.Timeout, 203 | Threshold: SystemConfig.Cluster.Threshold, 204 | } 205 | } 206 | return nil 207 | } 208 | 209 | //Notifications is exported 210 | func Notifications() []notify.EndPoint { 211 | 212 | if SystemConfig != nil { 213 | return SystemConfig.Notifications.EndPoints 214 | } 215 | return []notify.EndPoint{} 216 | } 217 | 218 | //CacheConfigs is exported 219 | func CacheConfigs() *cache.CacheConfigs { 220 | 221 | var configs *cache.CacheConfigs 222 | if SystemConfig != nil { 223 | if len(SystemConfig.Cache.StorageDriverConfigs) > 0 { 224 | configs = &cache.CacheConfigs{ 225 | LRUSize: SystemConfig.Cache.LRUSize, 226 | } 227 | for backend, paramters := range SystemConfig.Cache.StorageDriverConfigs { 228 | configs.StorageBackend = types.Backend(backend) 229 | configs.StorageDriverParameters = paramters 230 | break 231 | } 232 | } 233 | } 234 | return configs 235 | } 236 | 237 | //LoggerConfigs is exported 238 | func LoggerConfigs() *logger.Args { 239 | 240 | if SystemConfig != nil { 241 | return &logger.Args{ 242 | FileName: SystemConfig.Logger.LogFile, 243 | Level: SystemConfig.Logger.LogLevel, 244 | MaxSize: SystemConfig.Logger.LogSize, 245 | } 246 | } 247 | return nil 248 | } 249 | 250 | func readConfigurationFile(file string) ([]byte, error) { 251 | 252 | fd, err := os.OpenFile(file, os.O_RDONLY, 0777) 253 | if err != nil { 254 | return nil, err 255 | } 256 | 257 | defer fd.Close() 258 | buf, err := ioutil.ReadAll(fd) 259 | if err != nil { 260 | return nil, err 261 | } 262 | return buf, nil 263 | } 264 | 265 | func parseDefaultParmeters(conf *Configuration) { 266 | 267 | if conf.Cache.LRUSize <= 0 { 268 | conf.Cache.LRUSize = 512 269 | } 270 | 271 | if conf.Cluster.Pulse == "" { 272 | conf.Cluster.Pulse = "30s" 273 | } 274 | 275 | if conf.Cluster.Timeout == "" { 276 | conf.Cluster.Timeout = "90" 277 | } 278 | 279 | if conf.Cluster.Threshold == 0 { 280 | conf.Cluster.Threshold = 1 281 | } 282 | 283 | if len(conf.API.Hosts) == 0 { 284 | conf.API.Hosts = []string{":8985"} 285 | } 286 | 287 | if conf.Scheduler.AllocMode == "" { 288 | conf.Scheduler.AllocMode = "hash" 289 | } 290 | 291 | if conf.Scheduler.AllocRecovery == "" { 292 | conf.Scheduler.AllocRecovery = "320s" 293 | } 294 | 295 | if conf.Logger.LogLevel == "" { 296 | conf.Logger.LogLevel = "info" 297 | } 298 | 299 | if conf.Logger.LogSize == 0 { 300 | conf.Logger.LogSize = 20971520 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /scheduler/hash.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import "github.com/cloudtask/common/models" 4 | import "github.com/cloudtask/libtools/gounits/logger" 5 | 6 | /* 7 | HashAlloc 8 | hash分配原则: 9 | 1、若location下只有worker上线情况(online > 0, offline == 0) 10 | location中worker环发生了变化, 将环中所有worker的jobs重新分配一次, 状态为STARTED的job排除在外. 11 | 将job的状态设置为REALLOC. 12 | 13 | 2、若location下只有worker下线情况(online == 0, offline > 0) 14 | location中worker环虽然发生了变化,但为了避免location下所有jobs全都调整(抖动过大),因此只处理下线worker的所有jobs. 15 | 将job的状态设置为REALLOC. 16 | 17 | 3、若location下既有worker上线,也有worker下线(online > 0, offline > 0) 18 | 记录下线worker的jobs到临时缓存(tempOffJobs) 19 | 将location中所有worker的job重新分配一次,但状态为STARTED同时在tempOffJobs中未查询到的job排除在外. 20 | 在tempOffJobs中的job虽然为启动状态,但有可能worker是异常关闭没有改变job状态,所以必须重新分配. 21 | 将job的状态设置为REALLOC. 22 | */ 23 | func (scheduler *Scheduler) HashAlloc(location string, nodes *Nodes) { 24 | 25 | logger.INFO("[#scheduler#] hash-alloc. %s", location) 26 | jobsAlloc := scheduler.cacheRepository.GetAlloc(location) 27 | if jobsAlloc == nil { 28 | logger.INFO("[#scheduler#] location %s not found, hash-alloc return.", location) 29 | return 30 | } 31 | 32 | onlineSize := len(nodes.Online) 33 | offlineSize := len(nodes.Offline) 34 | if onlineSize > 0 && offlineSize == 0 { 35 | scheduler.onlineJobsAlloc(location, nodes, jobsAlloc) 36 | jobs := scheduler.cacheRepository.GetLocationSimpleJobs(location) 37 | scheduler.RecoveryLocationAlloc(location, jobs) 38 | return 39 | } 40 | 41 | if onlineSize == 0 && offlineSize > 0 { 42 | scheduler.offlineJobsAlloc(location, nodes, jobsAlloc) 43 | jobs := scheduler.cacheRepository.GetLocationSimpleJobs(location) 44 | scheduler.RecoveryLocationAlloc(location, jobs) 45 | return 46 | } 47 | 48 | if onlineSize > 0 && offlineSize > 0 { 49 | scheduler.allJobsAlloc(location, nodes, jobsAlloc) 50 | jobs := scheduler.cacheRepository.GetLocationSimpleJobs(location) 51 | scheduler.RecoveryLocationAlloc(location, jobs) 52 | return 53 | } 54 | } 55 | 56 | /* 57 | HashSingleJobAlloc: 调整单一job 58 | 条件:当创建或修改job后调用该方法 59 | */ 60 | func (scheduler *Scheduler) HashSingleJobAlloc(location string, jobid string) { 61 | 62 | logger.INFO("[#scheduler#] hashsingle-joballoc %s job %s", location, jobid) 63 | job := scheduler.cacheRepository.GetSimpleJob(jobid) 64 | if job == nil { 65 | logger.INFO("[#scheduler#] %s job %s not found, return.", location, jobid) 66 | return 67 | } 68 | 69 | if job.Enabled == 0 { 70 | logger.INFO("[#scheduler#] %s job %s enabled is disabled, return.", location, job.JobId) 71 | return 72 | } 73 | 74 | jobData := scheduler.cacheRepository.GetAllocJob(location, job.JobId) 75 | if jobData == nil { 76 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) 77 | if worker != nil { 78 | scheduler.cacheRepository.CreateAllocJob(location, worker.Key, job.JobId) //创建到任务分配表 79 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_STOPED) //修改任务状态为:STATE_STOPED. 80 | logger.INFO("[#scheduler#] create %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 81 | } else { 82 | if job.Stat != models.STATE_REALLOC { 83 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) //无法选择worker分配,修改任务状态为:STATE_REALLOC. 84 | logger.ERROR("[#scheduler#] create %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 85 | } 86 | } 87 | } else { 88 | if job.Stat != models.STATE_STARTED { 89 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) 90 | if worker != nil { 91 | if worker.Key != jobData.Key { //节点发生变化了,需要调整 92 | jobs := make(map[string]string, 0) 93 | jobs[job.JobId] = worker.Key 94 | logger.INFO("[#scheduler#] reset %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 95 | scheduler.cacheRepository.SetAllocJobsKey(location, jobs) //修改任务分配表 96 | } 97 | } else { 98 | if job.Stat != models.STATE_REALLOC { 99 | scheduler.cacheRepository.RemoveAllocJob(location, job.JobId) //分配失败,从分配表删除. 100 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) 101 | logger.ERROR("[#scheduler#] reset %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | /* 109 | onlineJobsAlloc: 上线节点job分配算法 110 | location中状态为非STATE_STARTED的所有jobs重新分配 111 | 如果只有一个节点上线,workercache中的worker数为1,则忽略job状态,全部重新分配一次 112 | */ 113 | func (scheduler *Scheduler) onlineJobsAlloc(location string, nodes *Nodes, jobsAlloc *models.JobsAlloc) { 114 | 115 | logger.INFO("[#scheduler#] online-jobsalloc. %s", location) 116 | jobKeys := make(map[string]string, 0) 117 | for _, jobData := range jobsAlloc.Jobs { 118 | job := scheduler.cacheRepository.GetSimpleJob(jobData.JobId) 119 | if job != nil && job.Stat != models.STATE_STARTED { 120 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) //hash重新分配 121 | if worker != nil { 122 | if worker.Key != jobData.Key { 123 | jobKeys[job.JobId] = worker.Key 124 | logger.INFO("[#scheduler#] reset %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 125 | } 126 | if job.Stat == models.STATE_REALLOC { //之前是等待分配状态的任务全部改为停止,其余状态保持不变 127 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_STOPED) 128 | } 129 | } else { 130 | scheduler.cacheRepository.RemoveAllocJob(location, job.JobId) //分配失败,从分配表删除. 131 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) 132 | logger.ERROR("[#scheduler#] reset %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 133 | } 134 | } 135 | } 136 | 137 | if nSize := len(jobKeys); nSize > 0 { //提交分配表 138 | logger.INFO("[#scheduler#] reset %s jobs %d to joballoc successed.", location, nSize) 139 | scheduler.cacheRepository.SetAllocJobsKey(location, jobKeys) //修改任务分配表 140 | } 141 | } 142 | 143 | /* 144 | offlineJobsAlloc: 下线节点job分配算法 145 | 下线节点的所有job重新分配到新节点 146 | */ 147 | func (scheduler *Scheduler) offlineJobsAlloc(location string, nodes *Nodes, jobsAlloc *models.JobsAlloc) { 148 | 149 | logger.INFO("[#scheduler#] offline-jobsalloc. %s", location) 150 | jobKeys := make(map[string]string, 0) 151 | for key := range nodes.Offline { //只处理下线节点 152 | jobIds := scheduler.cacheRepository.GetAllocJobIds(location, key) //获取分配表中该节点的所有任务 153 | for _, jobId := range jobIds { 154 | job := scheduler.cacheRepository.GetSimpleJob(jobId) 155 | if job != nil { 156 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) //重新分配节点 157 | if worker != nil { 158 | jobKeys[job.JobId] = worker.Key 159 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_STOPED) 160 | logger.INFO("[#scheduler#] reset %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 161 | } else { 162 | scheduler.cacheRepository.RemoveAllocJob(location, job.JobId) 163 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) 164 | logger.ERROR("[#scheduler#] reset %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 165 | } 166 | } 167 | } 168 | } 169 | 170 | if nSize := len(jobKeys); nSize > 0 { //提交分配表 171 | logger.INFO("[#scheduler#] reset %s jobs %d to joballoc successed.", location, nSize) 172 | scheduler.cacheRepository.SetAllocJobsKey(location, jobKeys) 173 | } 174 | } 175 | 176 | /* 177 | allJobsAlloc: 即有上线也有下线节点job分配算法 178 | 记录下线节点的所有jobids 179 | 当状态为STATE_STARTED,但在tempOffJobs中找到后也要重新分配 180 | */ 181 | func (scheduler *Scheduler) allJobsAlloc(location string, nodes *Nodes, jobsAlloc *models.JobsAlloc) { 182 | 183 | logger.INFO("[#scheduler#] all-jobsalloc. %s", location) 184 | //找出离线节点所有的jobid 185 | tempOffJobs := []string{} 186 | for key := range nodes.Offline { 187 | jobIds := scheduler.cacheRepository.GetAllocJobIds(location, key) //获取分配表中离线节点的所有任务 188 | if len(jobIds) > 0 { 189 | tempOffJobs = append(tempOffJobs, jobIds...) 190 | } 191 | } 192 | 193 | jobKeys := make(map[string]string, 0) 194 | for _, jobData := range jobsAlloc.Jobs { 195 | job := scheduler.cacheRepository.GetSimpleJob(jobData.JobId) 196 | found := false 197 | for _, jobId := range tempOffJobs { 198 | if jobId == jobData.JobId { 199 | found = true 200 | break 201 | } 202 | } 203 | if job != nil && job.Stat != models.STATE_STARTED || (job.Stat == models.STATE_STARTED && found) { 204 | worker := scheduler.cacheRepository.ChoiceWorker(location, job.JobId, job.Servers) //hash重新分配 205 | if worker != nil { 206 | if worker.Key != jobData.Key { 207 | jobKeys[job.JobId] = worker.Key 208 | logger.INFO("[#scheduler#] reset %s job %s to joballoc successed, select worker %s.", location, job.JobId, worker.Key) 209 | } 210 | if job.Stat == models.STATE_REALLOC || (job.Stat == models.STATE_STARTED && found) { 211 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_STOPED) 212 | } 213 | } else { 214 | scheduler.cacheRepository.RemoveAllocJob(location, job.JobId) //分配失败,从分配表删除. 215 | scheduler.cacheRepository.SetJobState(job.JobId, models.STATE_REALLOC) 216 | logger.ERROR("[#scheduler#] reset %s job %s to joballoc failed, select worker invalid.", location, job.JobId) 217 | } 218 | } 219 | } 220 | 221 | if nSize := len(jobKeys); nSize > 0 { //提交分配表 222 | logger.INFO("[#scheduler#] reset %s jobs %d to joballoc successed.", location, nSize) 223 | scheduler.cacheRepository.SetAllocJobsKey(location, jobKeys) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "github.com/cloudtask/cloudtask-center/cache" 4 | import "github.com/cloudtask/cloudtask-center/etc" 5 | import "github.com/cloudtask/cloudtask-center/notify" 6 | import "github.com/cloudtask/cloudtask-center/scheduler" 7 | import "github.com/cloudtask/common/models" 8 | import "github.com/cloudtask/libtools/gzkwrapper" 9 | import "github.com/cloudtask/libtools/gounits/logger" 10 | 11 | import ( 12 | "fmt" 13 | "time" 14 | ) 15 | 16 | //CenterServer is exported 17 | type CenterServer struct { 18 | Key string 19 | ConfigPath string 20 | Data *gzkwrapper.NodeData 21 | Master *gzkwrapper.Server 22 | NotifySender *notify.NotifySender 23 | CacheRepository *cache.CacheRepository 24 | Scheduler *scheduler.Scheduler 25 | MessageCache *models.MessageCache 26 | stopCh chan struct{} 27 | gzkwrapper.INodeNotifyHandler 28 | } 29 | 30 | //NewCenterServer is exported 31 | func NewCenterServer(key string) (*CenterServer, error) { 32 | 33 | clusterConfigs := etc.ClusterConfigs() 34 | if clusterConfigs == nil { 35 | return nil, fmt.Errorf("cluster configs invalid.") 36 | } 37 | 38 | cacheConfigs := etc.CacheConfigs() 39 | if cacheConfigs == nil { 40 | return nil, fmt.Errorf("cache configs invalid.") 41 | } 42 | 43 | schedulerConfigs := etc.SchedulerConfigs() 44 | if schedulerConfigs == nil { 45 | return nil, fmt.Errorf("scheduler configs invalid.") 46 | } 47 | 48 | server := &CenterServer{ 49 | Key: key, 50 | ConfigPath: clusterConfigs.Root + "/ServerConfig", 51 | stopCh: make(chan struct{}), 52 | } 53 | 54 | master, err := gzkwrapper.NewServer(key, clusterConfigs, server) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | cacheConfigs.AllocHandlerFunc = server.OnAllocCacheHandlerFunc 60 | cacheConfigs.NodeHandlerFunc = server.OnNodeCacheHandlerFunc 61 | cacheConfigs.ReadLocationAllocFunc = server.readLocationAlloc 62 | cacheConfigs.ProcLocationAllocFunc = server.procLocationAlloc 63 | cacheRepository, err := cache.NewCacheRepository(cacheConfigs) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | server.Master = master 69 | server.Data = master.Data 70 | server.CacheRepository = cacheRepository 71 | server.NotifySender = notify.NewNotifySender(etc.Notifications()) 72 | server.Scheduler = scheduler.NewScheduler(schedulerConfigs, server.CacheRepository) 73 | server.MessageCache = models.NewMessageCache() 74 | return server, nil 75 | } 76 | 77 | //Startup is exported 78 | func (server *CenterServer) Startup(startCh chan<- bool) error { 79 | 80 | var err error 81 | defer func() { 82 | if err != nil { 83 | server.Master.Close() 84 | return 85 | } 86 | 87 | recoveryInterval, err := time.ParseDuration(server.Scheduler.AllocRecovery) 88 | if err != nil { 89 | recoveryInterval, _ = time.ParseDuration("320s") 90 | } 91 | 92 | if recoveryInterval != 0 { 93 | go server.monitorAllocLoop(recoveryInterval) 94 | } 95 | }() 96 | 97 | if err = server.Master.Open(); err != nil { 98 | logger.ERROR("[#server#] cluster zookeeper open failure, %s", err) 99 | return err 100 | } 101 | 102 | if etc.UseServerConfig() { 103 | server.initServerConfig() 104 | } 105 | 106 | if err = server.CacheRepository.Open(); err != nil { 107 | logger.ERROR("[#server] storage driver open failure, %s", err) 108 | return err 109 | } 110 | 111 | startCh <- true 112 | logger.INFO("[#server] server initialize......") 113 | server.CacheRepository.InitLocalStorageLocations() 114 | server.Master.RefreshCache() 115 | return nil 116 | } 117 | 118 | //Stop is exported 119 | func (server *CenterServer) Stop() error { 120 | 121 | close(server.stopCh) 122 | server.CacheRepository.Close() 123 | server.CacheRepository.Clear() 124 | server.closeServerConfig() 125 | server.Master.Clear() 126 | if err := server.Master.Close(); err != nil { 127 | logger.ERROR("[#server] cluster zookeeper close error, %s", err.Error()) 128 | return err 129 | } 130 | return nil 131 | } 132 | 133 | //initServerConfig is exported 134 | //initialize server congfig and watching zk config node path. 135 | func (server *CenterServer) initServerConfig() { 136 | 137 | //watch server config path. 138 | err := server.Master.WatchOpen(server.ConfigPath, server.OnSeverConfigsWatchHandlerFunc) 139 | if err != nil { 140 | logger.WARN("[#server] init serverConfig error %s, used local configs.", err) 141 | return 142 | } 143 | 144 | //read config data. 145 | data, err := server.Master.Get(server.ConfigPath) 146 | if err != nil { 147 | logger.WARN("[#server] get serverConfig error %s, used local configs.", err) 148 | return 149 | } 150 | //save data to etc serverConfig. 151 | server.RefreshServerConfig(data) 152 | logger.INFO("[#server#] inited server config.") 153 | } 154 | 155 | //closeServerConfig is exported 156 | func (server *CenterServer) closeServerConfig() { 157 | 158 | server.Master.WatchClose(server.ConfigPath) 159 | } 160 | 161 | //RefreshServerConfig is exported 162 | //save serverConfig, re-set to references objects. 163 | func (server *CenterServer) RefreshServerConfig(data []byte) error { 164 | 165 | if etc.UseServerConfig() { 166 | if err := etc.SaveServerConfig(data); err != nil { 167 | return err 168 | } 169 | 170 | if cacheConfigs := etc.CacheConfigs(); cacheConfigs != nil { 171 | server.CacheRepository.SetStorageDriverConfigParameters(cacheConfigs.StorageDriverParameters) 172 | } 173 | } 174 | return nil 175 | } 176 | 177 | //readLocationAlloc is exported 178 | //read location alloc data. 179 | func (server *CenterServer) readLocationAlloc(location string) []byte { 180 | 181 | var ( 182 | ret bool 183 | err error 184 | data []byte 185 | ) 186 | 187 | allocPath := server.Master.Root + "/JOBS-" + location 188 | ret, err = server.Master.Exists(allocPath) 189 | if err != nil { 190 | logger.ERROR("[#server#] %s alloc check exists %s error, %s.", location, allocPath, err) 191 | return nil 192 | } 193 | 194 | if ret { 195 | if data, err = server.Master.Get(allocPath); err != nil { 196 | logger.ERROR("[#server#] %s alloc get data %s error, %s.", location, allocPath, err) 197 | return nil 198 | } 199 | } 200 | return data 201 | } 202 | 203 | func (server *CenterServer) postCacheAlloc(location string, data []byte) { 204 | 205 | var ( 206 | ret bool 207 | err error 208 | ) 209 | 210 | allocPath := server.Master.Root + "/JOBS-" + location 211 | ret, err = server.Master.Exists(allocPath) 212 | if err != nil { 213 | logger.ERROR("[#server#] post %s cache alloc exists error, %s.", location, err) 214 | return 215 | } 216 | 217 | if !ret { 218 | err = server.Master.Create(allocPath, data) 219 | } else { 220 | err = server.Master.Set(allocPath, data) 221 | } 222 | 223 | if err != nil { 224 | logger.ERROR("[#server#] post %s cache alloc error, %s.", location, err) 225 | return 226 | } 227 | logger.INFO("[#server#] post %s cache alloc successed...", location) 228 | } 229 | 230 | func (server *CenterServer) putCacheAlloc(location string, data []byte) { 231 | 232 | var ( 233 | ret bool 234 | err error 235 | ) 236 | 237 | allocPath := server.Master.Root + "/JOBS-" + location 238 | ret, err = server.Master.Exists(allocPath) 239 | if err != nil { 240 | logger.ERROR("[#server#] put %s cache alloc exists error, %s.", location, err) 241 | return 242 | } 243 | 244 | if !ret { 245 | logger.ERROR("[#server#] put %s cache alloc not found.", location) 246 | return 247 | } 248 | 249 | if err = server.Master.Set(allocPath, data); err != nil { 250 | logger.ERROR("[#server#] put %s cache alloc error, %s.", location, err) 251 | return 252 | } 253 | logger.INFO("[#server#] put %s cache alloc successed...", location) 254 | } 255 | 256 | //monitorAllocLoop is exported 257 | //定期监视分配表,按每个location进行分配表任务检查和清理. 258 | func (server *CenterServer) monitorAllocLoop(recoveryInterval time.Duration) { 259 | 260 | for { 261 | runTicker := time.NewTicker(recoveryInterval) 262 | select { 263 | case <-runTicker.C: 264 | { 265 | runTicker.Stop() 266 | locations := server.CacheRepository.GetLocationsName() 267 | for _, location := range locations { 268 | jobsAlloc := server.CacheRepository.GetAlloc(location) 269 | if jobsAlloc != nil { 270 | jobs := server.CacheRepository.GetLocationSimpleJobs(location) 271 | server.Scheduler.RecoveryLocationAlloc(location, jobs) 272 | server.cleanDumpLocationAlloc(location, jobs, jobsAlloc) 273 | } 274 | } 275 | } 276 | case <-server.stopCh: 277 | { 278 | runTicker.Stop() 279 | logger.INFO("[#server] monitor alloc loop exited.") 280 | return 281 | } 282 | } 283 | } 284 | } 285 | 286 | //cleanDumpLocationAlloc is exported 287 | //清扫任务分配表,将分配表中存在,而数据库中不存在或已关闭的任务从分配表删除 288 | func (server *CenterServer) cleanDumpLocationAlloc(location string, jobs []*models.SimpleJob, jobsAlloc *models.JobsAlloc) { 289 | 290 | var ( 291 | found = false 292 | jobIds = []string{} 293 | ) 294 | 295 | for _, jobData := range jobsAlloc.Jobs { 296 | for _, job := range jobs { 297 | if job.JobId == jobData.JobId && job.Enabled == 1 { 298 | found = true 299 | break 300 | } 301 | } 302 | if !found { 303 | jobIds = append(jobIds, jobData.JobId) 304 | } 305 | found = false 306 | } 307 | 308 | if len(jobIds) > 0 { 309 | server.CacheRepository.RemoveAllocJobs(location, jobIds) 310 | } 311 | } 312 | 313 | func (server *CenterServer) procLocationAlloc(location string, addServers []*models.Server, delServers []*models.Server) { 314 | 315 | nodeStore := gzkwrapper.NewNodeStore() 316 | for _, addServer := range addServers { 317 | nodes := server.Master.GetNodes(location, addServer.IPAddr, addServer.Name) 318 | for key, node := range nodes { 319 | server.CacheRepository.CreateWorker(key, node) 320 | nodeStore.New[key] = node 321 | } 322 | } 323 | 324 | for _, delServer := range delServers { 325 | nodes := server.Master.GetNodes(location, delServer.IPAddr, delServer.Name) 326 | for key, node := range nodes { 327 | server.CacheRepository.RemoveWorker(key, node) 328 | nodeStore.Dead[key] = node 329 | } 330 | } 331 | 332 | if nodeStore.NewTotalSize() > 0 || nodeStore.DeadTotalSize() > 0 { 333 | server.Scheduler.QuickAlloc(nodeStore.New, nodeStore.Dead) 334 | } 335 | } 336 | 337 | func (server *CenterServer) postNodesWatchNotifyEvent(online gzkwrapper.NodesPair, offline gzkwrapper.NodesPair) { 338 | 339 | watchLocations := make(notify.WatchLocations) 340 | watchLocations = server.setWatchLocations(watchLocations, online, "Healthy") 341 | watchLocations = server.setWatchLocations(watchLocations, offline, "Disconnected") 342 | for _, watchLocation := range watchLocations { 343 | server.NotifySender.AddLocationServersEvent("cluster discovery servers state changed.", watchLocation) 344 | } 345 | } 346 | 347 | func (server *CenterServer) setWatchLocations(watchLocations notify.WatchLocations, nodes gzkwrapper.NodesPair, state string) notify.WatchLocations { 348 | 349 | for _, nodedata := range nodes { 350 | watchlocation, ret := watchLocations[nodedata.Location] 351 | if !ret { 352 | watchlocation = ¬ify.WatchLocation{ 353 | Name: nodedata.Location, 354 | ContactInfo: []string{}, 355 | Servers: []*notify.Server{}, 356 | } 357 | watchLocations[nodedata.Location] = watchlocation 358 | } 359 | watchlocation.AddServer(nodedata.IpAddr, nodedata.HostName, state) 360 | groups := server.CacheRepository.GetLocationGroups(watchlocation.Name) 361 | for _, group := range groups { 362 | for _, owner := range group.Owners { 363 | watchlocation.AddContactInfo(owner) 364 | } 365 | } 366 | } 367 | return watchLocations 368 | } 369 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import _ "github.com/cloudtask/cloudtask-center/cache/driver/mongo" 4 | import _ "github.com/cloudtask/cloudtask-center/cache/driver/ngcloud" 5 | import "github.com/cloudtask/cloudtask-center/cache/driver" 6 | import "github.com/cloudtask/cloudtask-center/cache/driver/types" 7 | import "github.com/cloudtask/common/models" 8 | import "github.com/cloudtask/libtools/gounits/httpx" 9 | import "github.com/cloudtask/libtools/gounits/logger" 10 | import "github.com/cloudtask/libtools/gzkwrapper" 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | "net" 16 | "net/http" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | //ReadLocationAllocFunc is exported 22 | //read jobs alloc path real data. 23 | type ReadLocationAllocFunc func(location string) []byte 24 | 25 | //ProcLocationAllocFunc is exported 26 | type ProcLocationAllocFunc func(location string, addServers []*models.Server, delServers []*models.Server) 27 | 28 | //CacheConfigs is exported 29 | type CacheConfigs struct { 30 | LRUSize int 31 | StorageBackend types.Backend 32 | StorageDriverParameters types.Parameters 33 | AllocHandlerFunc AllocCacheEventHandlerFunc 34 | NodeHandlerFunc NodeCacheEventHandlerFunc 35 | ReadLocationAllocFunc ReadLocationAllocFunc 36 | ProcLocationAllocFunc ProcLocationAllocFunc 37 | } 38 | 39 | //CacheRepository is expotred 40 | type CacheRepository struct { 41 | nodeStore *NodeStore 42 | allocStore *AllocStore 43 | localStorage *LocalStorage 44 | client *httpx.HttpClient 45 | storageDriver driver.StorageDriver 46 | readLocationAllocFunc ReadLocationAllocFunc 47 | procLocationAllocFunc ProcLocationAllocFunc 48 | } 49 | 50 | //NewCacheRepository is expotred 51 | func NewCacheRepository(configs *CacheConfigs) (*CacheRepository, error) { 52 | 53 | storageDriver, err := driver.NewDriver(configs.StorageBackend, configs.StorageDriverParameters) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if configs.LRUSize <= 0 { 59 | configs.LRUSize = DefaultLRUSize 60 | } 61 | 62 | client := httpx.NewClient(). 63 | SetTransport(&http.Transport{ 64 | Proxy: http.ProxyFromEnvironment, 65 | DialContext: (&net.Dialer{ 66 | Timeout: 30 * time.Second, 67 | KeepAlive: 60 * time.Second, 68 | }).DialContext, 69 | DisableKeepAlives: false, 70 | MaxIdleConns: 25, 71 | MaxIdleConnsPerHost: 25, 72 | IdleConnTimeout: 90 * time.Second, 73 | TLSHandshakeTimeout: http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout, 74 | ExpectContinueTimeout: http.DefaultTransport.(*http.Transport).ExpectContinueTimeout, 75 | }) 76 | 77 | return &CacheRepository{ 78 | nodeStore: NewNodeStore(configs.NodeHandlerFunc), 79 | allocStore: NewAllocStore(configs.AllocHandlerFunc), 80 | localStorage: NewLocalStorage(configs.LRUSize), 81 | client: client, 82 | storageDriver: storageDriver, 83 | readLocationAllocFunc: configs.ReadLocationAllocFunc, 84 | procLocationAllocFunc: configs.ProcLocationAllocFunc, 85 | }, nil 86 | } 87 | 88 | //Open is exported 89 | func (cacheRepository *CacheRepository) Open() error { 90 | 91 | return cacheRepository.storageDriver.Open() 92 | } 93 | 94 | //Close is exported 95 | func (cacheRepository *CacheRepository) Close() { 96 | 97 | cacheRepository.storageDriver.Close() 98 | } 99 | 100 | //SetStorageDriverConfigParameters is exported 101 | func (cacheRepository *CacheRepository) SetStorageDriverConfigParameters(parameters types.Parameters) { 102 | 103 | cacheRepository.storageDriver.SetConfigParameters(parameters) 104 | } 105 | 106 | //Clear is expotred 107 | func (cacheRepository *CacheRepository) Clear() { 108 | 109 | cacheRepository.nodeStore.ClearWorkers() 110 | cacheRepository.allocStore.ClearAlloc() 111 | cacheRepository.localStorage.Clear() 112 | } 113 | 114 | //SetLocalStorageLocation is exported 115 | func (cacheRepository *CacheRepository) setLocalStorageLocation(location string) { 116 | 117 | workLocation := cacheRepository.storageDriver.GetLocation(location) 118 | if workLocation != nil { 119 | cacheRepository.localStorage.SetLocation(workLocation) 120 | } 121 | } 122 | 123 | //RemoveLocalStorageLocation is exported 124 | func (cacheRepository *CacheRepository) removeLocalStorageLocation(location string) { 125 | 126 | cacheRepository.localStorage.RemoveLocation(location) 127 | } 128 | 129 | //InitLocalStorageLocations is exported 130 | func (cacheRepository *CacheRepository) InitLocalStorageLocations() { 131 | 132 | cacheRepository.localStorage.Clear() 133 | locationsName := cacheRepository.storageDriver.GetLocationsName() 134 | for _, location := range locationsName { 135 | if strings.TrimSpace(location) != "" { 136 | cacheRepository.CreateLocationAlloc(location) 137 | } 138 | } 139 | } 140 | 141 | //readLocatinAlloc is exported 142 | func (cacheRepository *CacheRepository) readLocatinAlloc(location string) []byte { 143 | 144 | data := cacheRepository.readLocationAllocFunc(location) 145 | if data == nil { 146 | jobsAlloc := &models.JobsAlloc{ 147 | Version: 1, 148 | Jobs: make([]*models.JobData, 0), 149 | } 150 | buffer := bytes.NewBuffer([]byte{}) 151 | if err := json.NewEncoder(buffer).Encode(jobsAlloc); err != nil { 152 | return nil 153 | } 154 | data = buffer.Bytes() 155 | } 156 | return data 157 | } 158 | 159 | //CreateLocationAlloc is exported 160 | func (cacheRepository *CacheRepository) CreateLocationAlloc(location string) { 161 | 162 | cacheRepository.setLocalStorageLocation(location) 163 | workLocation := cacheRepository.localStorage.GetLocation(location) 164 | if workLocation != nil { 165 | if data := cacheRepository.readLocatinAlloc(location); data != nil { 166 | cacheRepository.allocStore.CreateAlloc(location, data) 167 | if cacheRepository.HasAlloc(location) { 168 | cacheRepository.procLocationAllocFunc(location, workLocation.Server, []*models.Server{}) 169 | } 170 | } 171 | } 172 | } 173 | 174 | //ChangeLocationAlloc is exported 175 | func (cacheRepository *CacheRepository) ChangeLocationAlloc(location string) { 176 | 177 | originServers := cacheRepository.GetLocationServers(location) 178 | cacheRepository.setLocalStorageLocation(location) 179 | workLocation := cacheRepository.localStorage.GetLocation(location) 180 | if workLocation != nil { 181 | addServers := []*models.Server{} 182 | for _, server := range workLocation.Server { 183 | if ret := containsServer(server, originServers); !ret { 184 | addServers = append(addServers, server) 185 | } 186 | } 187 | delServers := []*models.Server{} 188 | for _, server := range originServers { 189 | if ret := containsServer(server, workLocation.Server); !ret { 190 | delServers = append(delServers, server) 191 | } 192 | } 193 | if cacheRepository.HasAlloc(location) { 194 | cacheRepository.procLocationAllocFunc(location, addServers, delServers) 195 | } 196 | } 197 | } 198 | 199 | //RemoveLocationAlloc is exported 200 | func (cacheRepository *CacheRepository) RemoveLocationAlloc(location string) { 201 | 202 | cacheRepository.nodeStore.RemoveLocation(location) 203 | cacheRepository.allocStore.RemoveAlloc(location) 204 | cacheRepository.removeLocalStorageLocation(location) 205 | } 206 | 207 | //GetAllocData is exported 208 | func (cacheRepository *CacheRepository) GetAllocData(location string) *models.JobsAllocData { 209 | 210 | jobsAlloc := cacheRepository.GetAlloc(location) 211 | if jobsAlloc == nil { 212 | return nil 213 | } 214 | 215 | jobsAllocData := &models.JobsAllocData{ 216 | Location: location, 217 | Version: jobsAlloc.Version, 218 | Data: []*models.JobDataInfo{}, 219 | } 220 | 221 | for _, jobData := range jobsAlloc.Jobs { 222 | jobDataInfo := &models.JobDataInfo{} 223 | jobDataInfo.JobId = jobData.JobId 224 | jobDataInfo.Key = jobData.Key 225 | jobDataInfo.Version = jobData.Version 226 | if worker := cacheRepository.GetWorker(jobData.Key); worker != nil { 227 | jobDataInfo.IpAddr = worker.IPAddr 228 | jobDataInfo.HostName = worker.Name 229 | } 230 | if job := cacheRepository.GetSimpleJob(jobData.JobId); job != nil { 231 | jobDataInfo.JobName = job.Name 232 | } 233 | jobsAllocData.Data = append(jobsAllocData.Data, jobDataInfo) 234 | } 235 | return jobsAllocData 236 | } 237 | 238 | //GetServerAllocData is exported 239 | func (cacheRepository *CacheRepository) GetServerAllocData(location string, server string) *models.JobsAllocData { 240 | 241 | var ( 242 | w *Worker 243 | found = false 244 | ) 245 | 246 | workers := cacheRepository.nodeStore.GetWorkers(location) 247 | for _, worker := range workers { 248 | if worker.IPAddr == server || strings.ToUpper(worker.Name) == strings.ToUpper(server) { 249 | w = worker 250 | found = true 251 | break 252 | } 253 | } 254 | 255 | if !found { //server not found. 256 | return nil 257 | } 258 | 259 | jobsAlloc := cacheRepository.GetAlloc(location) 260 | if jobsAlloc == nil { 261 | return nil 262 | } 263 | 264 | jobsAllocData := &models.JobsAllocData{ 265 | Location: location, 266 | Version: jobsAlloc.Version, 267 | Data: []*models.JobDataInfo{}, 268 | } 269 | 270 | for _, jobData := range jobsAlloc.Jobs { 271 | if jobData.Key == w.Key { 272 | jobDataInfo := &models.JobDataInfo{} 273 | jobDataInfo.JobId = jobData.JobId 274 | jobDataInfo.Key = jobData.Key 275 | jobDataInfo.Version = jobData.Version 276 | jobDataInfo.IpAddr = w.IPAddr 277 | jobDataInfo.HostName = w.Name 278 | if job := cacheRepository.GetSimpleJob(jobData.JobId); job != nil { 279 | jobDataInfo.JobName = job.Name 280 | } 281 | jobsAllocData.Data = append(jobsAllocData.Data, jobDataInfo) 282 | } 283 | } 284 | return jobsAllocData 285 | } 286 | 287 | //HasAlloc is exported 288 | func (cacheRepository *CacheRepository) HasAlloc(location string) bool { 289 | 290 | return cacheRepository.allocStore.HasAlloc(location) 291 | } 292 | 293 | //HasAllocJobId is exported 294 | func (cacheRepository *CacheRepository) HasAllocJobId(location string, jobId string) bool { 295 | 296 | return cacheRepository.allocStore.HasAllocJobId(location, jobId) 297 | } 298 | 299 | //GetAlloc is exported 300 | func (cacheRepository *CacheRepository) GetAlloc(location string) *models.JobsAlloc { 301 | 302 | return cacheRepository.allocStore.GetAlloc(location) 303 | } 304 | 305 | //GetAllocJobIds is exported 306 | func (cacheRepository *CacheRepository) GetAllocJobIds(location string, key string) []string { 307 | 308 | return cacheRepository.allocStore.GetAllocJobIds(location, key) 309 | } 310 | 311 | //GetAllocJob is exported 312 | func (cacheRepository *CacheRepository) GetAllocJob(location string, jobId string) *models.JobData { 313 | 314 | return cacheRepository.allocStore.GetAllocJob(location, jobId) 315 | } 316 | 317 | //CreateAllocJob is exported 318 | func (cacheRepository *CacheRepository) CreateAllocJob(location string, key string, jobId string) { 319 | 320 | cacheRepository.allocStore.CreateAllocJob(location, key, jobId) 321 | } 322 | 323 | //RemoveAllocJob is exported 324 | func (cacheRepository *CacheRepository) RemoveAllocJob(location string, jobId string) { 325 | 326 | cacheRepository.allocStore.RemoveAllocJob(location, jobId) 327 | } 328 | 329 | //RemoveAllocJobs is exported 330 | func (cacheRepository *CacheRepository) RemoveAllocJobs(location string, jobIds []string) { 331 | 332 | cacheRepository.allocStore.RemoveAllocJobs(location, jobIds) 333 | } 334 | 335 | //SetAllocJobsKey is exported 336 | func (cacheRepository *CacheRepository) SetAllocJobsKey(location string, jobs map[string]string) { 337 | 338 | cacheRepository.allocStore.SetAllocJobsKey(location, jobs) 339 | } 340 | 341 | //UpdateAllocJob is exported 342 | func (cacheRepository *CacheRepository) UpdateAllocJob(location string, jobId string) { 343 | 344 | cacheRepository.UpdateAllocJobs(location, []string{jobId}) 345 | } 346 | 347 | //UpdateAllocJobs is exported 348 | func (cacheRepository *CacheRepository) UpdateAllocJobs(location string, jobIds []string) { 349 | 350 | cacheRepository.allocStore.UpdateAllocJobs(location, jobIds) 351 | } 352 | 353 | //GetLocationsName is exported 354 | func (cacheRepository *CacheRepository) GetLocationsName() []string { 355 | 356 | return cacheRepository.localStorage.GetLocationsName() 357 | } 358 | 359 | //GetLocation is exported 360 | func (cacheRepository *CacheRepository) GetLocation(location string) *models.WorkLocation { 361 | 362 | return cacheRepository.localStorage.GetLocation(location) 363 | } 364 | 365 | //GetLocationServers is exported 366 | func (cacheRepository *CacheRepository) GetLocationServers(location string) []*models.Server { 367 | 368 | return cacheRepository.localStorage.GetLocationServers(location) 369 | } 370 | 371 | //GetLocationGroups is exported 372 | func (cacheRepository *CacheRepository) GetLocationGroups(location string) []*models.Group { 373 | 374 | return cacheRepository.localStorage.GetLocationGroups(location) 375 | } 376 | 377 | //GetLocationGroup is exported 378 | func (cacheRepository *CacheRepository) GetLocationGroup(location string, groupid string) *models.Group { 379 | 380 | return cacheRepository.localStorage.GetLocationGroup(location, groupid) 381 | } 382 | 383 | //GetLocationSimpleJobs is exported 384 | func (cacheRepository *CacheRepository) GetLocationSimpleJobs(location string) []*models.SimpleJob { 385 | 386 | return cacheRepository.storageDriver.GetLocationSimpleJobs(location) 387 | } 388 | 389 | //GetSimpleJob is exported 390 | func (cacheRepository *CacheRepository) GetSimpleJob(jobid string) *models.SimpleJob { 391 | 392 | return cacheRepository.storageDriver.GetSimpleJob(jobid) 393 | } 394 | 395 | //GetRawJob is exported 396 | func (cacheRepository *CacheRepository) GetRawJob(jobid string) *models.Job { 397 | 398 | job := cacheRepository.storageDriver.GetJob(jobid) 399 | if job != nil { 400 | cacheRepository.localStorage.AddJob(job) 401 | } 402 | return job 403 | } 404 | 405 | //GetJob is exported 406 | func (cacheRepository *CacheRepository) GetJob(jobid string) *models.Job { 407 | 408 | job := cacheRepository.localStorage.GetJob(jobid) 409 | if job == nil { 410 | job = cacheRepository.GetRawJob(jobid) 411 | } 412 | return job 413 | } 414 | 415 | //RemoveJob is exported 416 | func (cacheRepository *CacheRepository) RemoveJob(jobid string) { 417 | 418 | cacheRepository.localStorage.RemoveJob(jobid) 419 | } 420 | 421 | //RemoveJobs is exported 422 | func (cacheRepository *CacheRepository) RemoveJobs(jobIds []string) { 423 | 424 | for _, jobid := range jobIds { 425 | cacheRepository.RemoveJob(jobid) 426 | } 427 | } 428 | 429 | //SetJobAction is exported 430 | func (cacheRepository *CacheRepository) SetJobAction(location string, jobid string, action string) { 431 | 432 | logger.INFO("[#cache#] set job action, %s %s", jobid, action) 433 | job := cacheRepository.GetSimpleJob(jobid) 434 | if job == nil { 435 | return 436 | } 437 | 438 | if job.Enabled == 0 || job.Stat == models.STATE_REALLOC { //任务状态已关闭或未分配 439 | logger.INFO("[#cache#] job %s set action to %s return, job enable is %d, state %d", jobid, action, job.Enabled, job.Stat) 440 | return 441 | } 442 | 443 | if action == "start" && job.Stat == models.STATE_STARTED { //任务已在运行中 444 | logger.INFO("[#cache#] job %s set action to %s return, job is started, state %d", jobid, action, job.Stat) 445 | return 446 | } 447 | 448 | jobData := cacheRepository.GetAllocJob(location, jobid) 449 | if jobData != nil { 450 | if worker := cacheRepository.GetWorker(jobData.Key); worker != nil { 451 | if err := putJobAction(cacheRepository.client, worker.APIAddr, location, jobid, action); err != nil { 452 | logger.ERROR("[#cache#] set worker %s job action failure, %s", worker.IPAddr, err) 453 | return 454 | } 455 | logger.INFO("[#cache#] set worker %s job %s action to %s", worker.IPAddr, jobid, action) 456 | } 457 | } 458 | } 459 | 460 | //SetJobState is exported 461 | func (cacheRepository *CacheRepository) SetJobState(jobid string, state int) { 462 | 463 | logger.INFO("[#cache#] set job state, %s %d", jobid, state) 464 | job := cacheRepository.GetJob(jobid) 465 | if job != nil { 466 | job.Stat = state 467 | if state == models.STATE_STARTED || state == models.STATE_REALLOC { 468 | job.NextAt = time.Time{} 469 | } 470 | logger.INFO("[#cache#] set job %s state to %d, nextat %s, execat %s", jobid, job.Stat, job.NextAt, job.ExecAt) 471 | cacheRepository.storageDriver.SetJob(job) 472 | } 473 | } 474 | 475 | //SetJobNextAt is exported 476 | func (cacheRepository *CacheRepository) SetJobNextAt(jobid string, nextat time.Time) { 477 | 478 | logger.INFO("[#cache#] set job nextat, %s %s", jobid, nextat) 479 | job := cacheRepository.GetJob(jobid) 480 | if job != nil { 481 | if len(job.Schedule) > 0 { 482 | job.Stat = models.STATE_STOPED 483 | job.NextAt = nextat 484 | logger.INFO("[#cache#] set job %s nextat to %s, execat %s, state %d", jobid, job.NextAt, job.ExecAt, job.Stat) 485 | cacheRepository.storageDriver.SetJob(job) 486 | } 487 | } 488 | } 489 | 490 | //SetJobExecute is exported 491 | func (cacheRepository *CacheRepository) SetJobExecute(jobid string, state int, execerr string, execat time.Time, nextat time.Time) { 492 | 493 | logger.INFO("[#cache#] set job execute, %s %d execat:%s nextat:%s", jobid, state, execat, nextat) 494 | job := cacheRepository.GetJob(jobid) 495 | if job != nil { 496 | job.Stat = state 497 | job.ExecAt = execat 498 | if state == models.STATE_STARTED { 499 | job.NextAt = time.Time{} 500 | if job.Enabled == 0 { 501 | job.Stat = models.STATE_REALLOC 502 | } 503 | } else if state == models.STATE_STOPED || state == models.STATE_FAILED { 504 | job.ExecErr = execerr 505 | job.NextAt = nextat 506 | } 507 | logger.INFO("[#cache#] set job %s execute to %d, nextat %s, execat %s", jobid, job.Stat, job.NextAt, job.ExecAt) 508 | cacheRepository.storageDriver.SetJob(job) 509 | } 510 | } 511 | 512 | //SetJobLog is exported 513 | func (cacheRepository *CacheRepository) SetJobLog(joblog *models.JobLog) { 514 | 515 | logger.INFO("[#cache#] set job log, %s %d", joblog.JobId, joblog.Stat) 516 | cacheRepository.storageDriver.SetJobLog(joblog) 517 | } 518 | 519 | //GetWorker is exported 520 | func (cacheRepository *CacheRepository) GetWorker(key string) *Worker { 521 | 522 | return cacheRepository.nodeStore.GetWorker(key) 523 | } 524 | 525 | //GetWorkers is exported 526 | func (cacheRepository *CacheRepository) GetWorkers(location string) []*Worker { 527 | 528 | return cacheRepository.nodeStore.GetWorkers(location) 529 | } 530 | 531 | //ChoiceWorker is exported 532 | func (cacheRepository *CacheRepository) ChoiceWorker(location string, key string, servers []string) *Worker { 533 | 534 | if len(servers) > 0 { 535 | return cacheRepository.nodeStore.HashLocationRangeWorker(location, key, servers) 536 | } 537 | return cacheRepository.nodeStore.HashLocationWorker(location, key) 538 | } 539 | 540 | //CreateWorker is exported 541 | func (cacheRepository *CacheRepository) CreateWorker(key string, node *gzkwrapper.NodeData) { 542 | 543 | worker := cacheRepository.nodeStore.GetWorker(key) 544 | if worker != nil { 545 | logger.WARN("[#cache#] %s create worker %s is already, %s(%s).", node.Location, key, node.HostName, node.IpAddr) 546 | return 547 | } 548 | 549 | ret := cacheRepository.nodeStore.ContainsLocationWorker(node.Location, node.IpAddr) 550 | if ret { 551 | logger.WARN("[#cache#] %s create worker %s ipaddr is already, %s(%s).", node.Location, key, node.HostName, node.IpAddr) 552 | return 553 | } 554 | 555 | ret = cacheRepository.localStorage.ContainsLocationServer(node.Location, node.IpAddr, node.HostName) 556 | if ret { 557 | server := models.CreateServer(key, node, 1) 558 | attach := models.AttachDecode(node.Attach) 559 | cacheRepository.nodeStore.CreateWorker(node.Location, attach, server) 560 | logger.INFO("[#cache#] %s create worker %s, %s(%s).", node.Location, key, node.HostName, node.IpAddr) 561 | } 562 | } 563 | 564 | //ChangeWorker is exported 565 | func (cacheRepository *CacheRepository) ChangeWorker(key string, node *gzkwrapper.NodeData) { 566 | 567 | worker := cacheRepository.nodeStore.GetWorker(key) 568 | if worker == nil { 569 | logger.WARN("[#cache#] %s change worker %s key is not found, %s(%s).", node.Location, node.HostName, node.IpAddr) 570 | return 571 | } 572 | 573 | ret := cacheRepository.localStorage.ContainsLocationServer(node.Location, node.IpAddr, node.HostName) 574 | if ret { 575 | server := models.CreateServer(key, node, 1) 576 | attach := models.AttachDecode(node.Attach) 577 | cacheRepository.nodeStore.ChangeWorker(node.Location, attach, server) 578 | logger.INFO("[#cache#] %s change worker %s, %s(%s).", node.Location, key, node.HostName, node.IpAddr) 579 | } 580 | } 581 | 582 | //RemoveWorker is exported 583 | func (cacheRepository *CacheRepository) RemoveWorker(key string, node *gzkwrapper.NodeData) { 584 | 585 | worker := cacheRepository.nodeStore.GetWorker(key) 586 | if worker != nil && worker.Alivestamp == node.Alivestamp { 587 | cacheRepository.nodeStore.RemoveWorker(node.Location, key) 588 | logger.INFO("[#cache#] %s remove worker %s, %s(%s).", node.Location, key, node.HostName, node.IpAddr) 589 | } 590 | } 591 | --------------------------------------------------------------------------------