manband 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/jobhandler.rb +7 -0
- data/bin/manband.rb +4 -0
- data/bin/remoteband.rb +5 -0
- data/bin/userband.rb +6 -0
- data/bin/workflowhandler.rb +10 -1
- data/lib/manband/bandmanager.rb +3 -3
- data/lib/manband/flowconfig.rb +15 -2
- data/lib/manband/job.rb +25 -11
- data/lib/manband/store.rb +14 -4
- data/lib/manband/user.rb +6 -0
- data/lib/manband/workflow.rb +14 -3
- metadata +3 -3
data/bin/jobhandler.rb
CHANGED
@@ -7,6 +7,13 @@ require 'mb-minion'
|
|
7
7
|
|
8
8
|
include Minion
|
9
9
|
|
10
|
+
# The program manages job commands. Master nodes (workflow handler) sends
|
11
|
+
# messages to jobhandler instances. Each instance manages one and
|
12
|
+
# only one message at a time. Jobhnadlers are also in charge of storage to S3.
|
13
|
+
# Author: Olivier Sallou <olivier.sallou@irisa.fr>
|
14
|
+
# Copyright: 2012, IRISA
|
15
|
+
|
16
|
+
|
10
17
|
if ENV["MYSQL_URL"]==nil
|
11
18
|
puts "MYSQL_URL environment variable is not set exiting..."
|
12
19
|
exit 1
|
data/bin/manband.rb
CHANGED
@@ -14,6 +14,10 @@ require 'manband/flowconfig.rb'
|
|
14
14
|
require 'manband/job.rb'
|
15
15
|
require 'manband/bandmanager.rb'
|
16
16
|
|
17
|
+
# The program sends a workflow execution request to manband orchestrator.
|
18
|
+
# Author: Olivier Sallou <olivier.sallou@irisa.fr>
|
19
|
+
# Copyright: 2012, IRISA
|
20
|
+
|
17
21
|
log = Logger.new(STDOUT)
|
18
22
|
log.level = Logger::INFO
|
19
23
|
|
data/bin/remoteband.rb
CHANGED
@@ -7,6 +7,11 @@ require 'mb-minion'
|
|
7
7
|
|
8
8
|
include Minion
|
9
9
|
|
10
|
+
# The program sends a workflow execution request to manband orchestrator.
|
11
|
+
# This program does not need database credentials but does not get workflow id in return
|
12
|
+
# Author: Olivier Sallou <olivier.sallou@irisa.fr>
|
13
|
+
# Copyright: 2012, IRISA
|
14
|
+
|
10
15
|
|
11
16
|
log = Logger.new(STDOUT)
|
12
17
|
log.level = Logger::INFO
|
data/bin/userband.rb
CHANGED
@@ -8,6 +8,12 @@ $:.push File.expand_path("../lib")
|
|
8
8
|
|
9
9
|
require 'manband/flowconfig.rb'
|
10
10
|
|
11
|
+
# The program is used to manage users (add, update) in the database.
|
12
|
+
# Users are used for web interface authentification and S3 credentials.
|
13
|
+
# Author: Olivier Sallou <olivier.sallou@irisa.fr>
|
14
|
+
# Copyright: 2012, IRISA
|
15
|
+
|
16
|
+
|
11
17
|
log = Logger.new(STDOUT)
|
12
18
|
log.level = Logger::INFO
|
13
19
|
|
data/bin/workflowhandler.rb
CHANGED
@@ -7,6 +7,15 @@ require 'mb-minion'
|
|
7
7
|
|
8
8
|
include Minion
|
9
9
|
|
10
|
+
# The program s the main orchestrator of the workflow.
|
11
|
+
# It starts with a workflow execution request and sends messages to job
|
12
|
+
# handlers according to the input workflow.
|
13
|
+
# It is possible to have multipe workflow handler, sharing messages for
|
14
|
+
# the same workflow, or to handler a high number of workflow requests.
|
15
|
+
# Author: Olivier Sallou <olivier.sallou@irisa.fr>
|
16
|
+
# Copyright: 2012, IRISA
|
17
|
+
|
18
|
+
|
10
19
|
if ENV["MYSQL_URL"]==nil
|
11
20
|
puts "MYSQL_URL environment variable is not set exiting..."
|
12
21
|
exit 1
|
@@ -52,7 +61,7 @@ optparse.parse!
|
|
52
61
|
|
53
62
|
userconf = File.expand_path("~")+"/.manband"
|
54
63
|
if @@options[:conf]==nil && File.exists?(userconf)
|
55
|
-
options[:conf]=userconf
|
64
|
+
@@options[:conf]=userconf
|
56
65
|
end
|
57
66
|
|
58
67
|
if @@options[:conf]!=nil
|
data/lib/manband/bandmanager.rb
CHANGED
@@ -11,14 +11,14 @@ include Minion
|
|
11
11
|
require 'manband/workflow.rb'
|
12
12
|
require 'manband/flowconfig.rb'
|
13
13
|
|
14
|
-
#
|
14
|
+
# This class is used to launch new workflows
|
15
15
|
class BandManager
|
16
16
|
|
17
17
|
@@log = Logger.new(STDOUT)
|
18
18
|
@@log.level = Logger::DEBUG
|
19
19
|
|
20
20
|
|
21
|
-
#
|
21
|
+
# Loads a workflow file
|
22
22
|
def self.load(wfile)
|
23
23
|
if !File.exists?(wfile)
|
24
24
|
@@log.error "Workflow file "+wfile+" does not exist!"
|
@@ -143,7 +143,7 @@ class BandManager
|
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
146
|
-
# Execute a workflow from an other one
|
146
|
+
# Execute a workflow from an other one (clone it)
|
147
147
|
def self.launchclone(id)
|
148
148
|
workflow = WorkFlow.get(id)
|
149
149
|
newworkflow = WorkFlow.new(:uid => workflow.uid , :name => workflow.name, :description => workflow.description, :created_at => Time.now, :file => workflow.file, :terminals => workflow.terminals, :status => STATUS_NEW, :workdir => FlowConfig.getjobdir(), :vars => workflow.vars, :bucket => workflow.bucket)
|
data/lib/manband/flowconfig.rb
CHANGED
@@ -2,6 +2,9 @@ require 'uuid'
|
|
2
2
|
require 'data_mapper'
|
3
3
|
require 'dm-migrations'
|
4
4
|
|
5
|
+
# Configuration library
|
6
|
+
|
7
|
+
# Determines the workflow and node status
|
5
8
|
STATUS_SKIP = -2
|
6
9
|
STATUS_FAKE = -1
|
7
10
|
STATUS_NEW = 0
|
@@ -10,12 +13,14 @@ STATUS_OVER = 2
|
|
10
13
|
STATUS_SUSPEND = 3 # SUSPEND mode for a job means a job is over and paused
|
11
14
|
STATUS_ERROR = 4
|
12
15
|
|
16
|
+
# Determines if a job must be stored to S3, and its current status
|
13
17
|
STORE_NO = -1
|
14
18
|
STORE_DO = 0
|
15
19
|
STORE_RUN = 1
|
16
20
|
STORE_OVER = 2
|
17
21
|
STORE_ERROR = 4
|
18
22
|
|
23
|
+
# List of message operations
|
19
24
|
OP_NEW = "new"
|
20
25
|
OP_START = "start"
|
21
26
|
OP_FINISH = "finish"
|
@@ -30,10 +35,10 @@ OP_CLEAN = "clean" # Delete work dirs of workflow
|
|
30
35
|
OP_DESTROY = "destroy" # Delete workflow and its work dirs
|
31
36
|
OP_STORE = "store" # Store result to S3
|
32
37
|
|
33
|
-
#
|
38
|
+
# This class holds the base config
|
34
39
|
class FlowConfig
|
35
40
|
@@workdir='/tmp'
|
36
|
-
@@s3host = '
|
41
|
+
@@s3host = 'localhost'
|
37
42
|
@@s3port = '8773'
|
38
43
|
@@s3path = '/services/Walrus'
|
39
44
|
|
@@ -47,6 +52,7 @@ class FlowConfig
|
|
47
52
|
return @@s3host
|
48
53
|
end
|
49
54
|
|
55
|
+
# Sets S3 storage parameters
|
50
56
|
def self.sets3(host,port= '8773',path='/services/Walrus')
|
51
57
|
@@s3host = host
|
52
58
|
@@s3port = port
|
@@ -65,14 +71,21 @@ class FlowConfig
|
|
65
71
|
return @@workdir
|
66
72
|
end
|
67
73
|
|
74
|
+
# defines upload directory for webband
|
75
|
+
# It must be accessible by the handlers
|
68
76
|
def self.setuploaddir(directory)
|
69
77
|
@@uploaddir = directory
|
70
78
|
end
|
71
79
|
|
80
|
+
# Defines the work directory. It must be shared between job
|
81
|
+
# and workflow handlers.
|
72
82
|
def self.setworkdir(directory)
|
73
83
|
@@workdir=directory
|
74
84
|
end
|
75
85
|
|
86
|
+
|
87
|
+
# Returns a work directory for a job. Directory
|
88
|
+
# is based on a unique identifier.
|
76
89
|
def self.getjobdir(workflowdir = nil)
|
77
90
|
uuid = UUID.new
|
78
91
|
if workflowdir == nil
|
data/lib/manband/job.rb
CHANGED
@@ -11,10 +11,7 @@ include Minion
|
|
11
11
|
#DataMapper.setup(:default, 'sqlite:///home/osallou/Desktop/genflow/project.db')
|
12
12
|
DataMapper.setup(:default, ENV['MYSQL_URL'])
|
13
13
|
|
14
|
-
#
|
15
|
-
# Exit code = 0 selects first node, other code select second node
|
16
|
-
# Should inform with a finish "code: exitcode" and manage it in workflowhandler to RUN selected node and SKIP other node
|
17
|
-
|
14
|
+
# This class defines the link between the nodes in the workflow
|
18
15
|
class JobLink
|
19
16
|
include DataMapper::Resource
|
20
17
|
|
@@ -25,11 +22,14 @@ class JobLink
|
|
25
22
|
|
26
23
|
end
|
27
24
|
|
25
|
+
# This class manages the job execution and its status on a job handler.
|
28
26
|
class Job
|
29
27
|
include DataMapper::Resource
|
30
28
|
|
31
29
|
@@debug = false
|
32
30
|
|
31
|
+
# Sets debug mode
|
32
|
+
# mode: boolean
|
33
33
|
def self.debug(mode)
|
34
34
|
@@debug = mode
|
35
35
|
end
|
@@ -50,7 +50,10 @@ class Job
|
|
50
50
|
property :error, Text, :default => "[]" # JSON Array of error, per instance
|
51
51
|
property :store, Integer, :default => STORE_NO # Storage status
|
52
52
|
|
53
|
-
# Update job status and launch the command
|
53
|
+
# Update job status and launch the command locally
|
54
|
+
# Sends a finish message or an error message according to the job status.
|
55
|
+
# if storage is needed, job will send a finish AND a store message. Storage
|
56
|
+
# will occur in parallel of the rest of the workflow.
|
54
57
|
def run(curhandler,instance=0)
|
55
58
|
# Send run command, possibly multiple ones according to pattern
|
56
59
|
# Command would send a message when over
|
@@ -81,7 +84,7 @@ class Job
|
|
81
84
|
end
|
82
85
|
end
|
83
86
|
|
84
|
-
# Skip treatment, just answer
|
87
|
+
# Skip treatment, just answer, for debug
|
85
88
|
def skip(curhandler)
|
86
89
|
curjob = Job.get(@id)
|
87
90
|
curjob.update(:handler => curhandler.to_s)
|
@@ -89,7 +92,10 @@ class Job
|
|
89
92
|
sendmessage(OP_FINISH,jobmsg)
|
90
93
|
end
|
91
94
|
|
92
|
-
# Execute locally the command
|
95
|
+
# Execute locally the command, creating directories and setting environment
|
96
|
+
# variables to empty string for security.
|
97
|
+
# wordir: job working directory
|
98
|
+
# instance: instance number in the list of commands
|
93
99
|
def runcommand(workdir,instance)
|
94
100
|
initcmd = "AMQP_URL="" && MYSQL_URL="" && mkdir -p "+workdir+" && cd "+workdir+" && WORKDIR="+workdir+" && "
|
95
101
|
curjob = Job.get(@id)
|
@@ -102,7 +108,8 @@ class Job
|
|
102
108
|
end
|
103
109
|
|
104
110
|
# Change instance counter
|
105
|
-
# If workflow is in suspend status, suspend the job at
|
111
|
+
# If workflow is in suspend status, suspend the job at
|
112
|
+
# the end of its treatment
|
106
113
|
def finish
|
107
114
|
if @status == STATUS_SKIP
|
108
115
|
return
|
@@ -133,7 +140,7 @@ class Job
|
|
133
140
|
end
|
134
141
|
end
|
135
142
|
|
136
|
-
#
|
143
|
+
# Compares intance counter to max instances to determine if job is over.
|
137
144
|
def isover?
|
138
145
|
if @status == STATUS_OVER
|
139
146
|
return true
|
@@ -179,7 +186,9 @@ class Job
|
|
179
186
|
end
|
180
187
|
end
|
181
188
|
end
|
182
|
-
|
189
|
+
|
190
|
+
# Sets a job in ERROR status.
|
191
|
+
# instance: job instance number in fault
|
183
192
|
def error!(instance = 0)
|
184
193
|
@status= STATUS_ERROR
|
185
194
|
curjob = Job.get(@id)
|
@@ -258,7 +267,12 @@ class Job
|
|
258
267
|
end
|
259
268
|
end
|
260
269
|
end
|
261
|
-
|
270
|
+
|
271
|
+
# Sends a message. According to the operation, message will be
|
272
|
+
# sent to master or node queues.
|
273
|
+
# operation: kind of message
|
274
|
+
# msg: message to send
|
275
|
+
# jobqueue: optional specific queue
|
262
276
|
def sendmessage(operation,msg,jobqueue='')
|
263
277
|
queue = "manband.master"
|
264
278
|
if operation == OP_RUN || operation == OP_SKIP || operation == OP_DESTROY || operation == OP_CLEAN|| operation == OP_STORE
|
data/lib/manband/store.rb
CHANGED
@@ -7,7 +7,7 @@ require 'fileutils'
|
|
7
7
|
require 'manband/flowconfig.rb'
|
8
8
|
require 'manband/user.rb'
|
9
9
|
|
10
|
-
|
10
|
+
# this class manage the storage of a job directory to S3
|
11
11
|
class Storeband
|
12
12
|
|
13
13
|
@@log = Logger.new(STDOUT)
|
@@ -29,6 +29,11 @@ class Storeband
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# Store a job workdir to S3
|
32
|
+
#
|
33
|
+
# job: current job
|
34
|
+
# uid: user identifier
|
35
|
+
# bucket: bucket name to store the zip file
|
36
|
+
# @return false in case of failure
|
32
37
|
def store(job,uid,bucket="manband")
|
33
38
|
user = User.get(uid)
|
34
39
|
if user == nil
|
@@ -50,16 +55,21 @@ class Storeband
|
|
50
55
|
compress(job.workdir, FlowConfig.workdir+"/"+zipfile)
|
51
56
|
# Send file
|
52
57
|
sends3(FlowConfig.workdir+"/"+zipfile,bucket,user.s3_access,user.s3_secret)
|
53
|
-
rescue
|
54
|
-
@@log.error "An error occured during S3 operation: "+job.id.to_s
|
58
|
+
rescue Exception => e
|
59
|
+
@@log.error "An error occured during S3 operation: "+job.id.to_s+" "+e.message
|
55
60
|
job.update(:store => STORE_ERROR)
|
56
61
|
return false
|
57
62
|
end
|
58
63
|
job.update(:store => STORE_OVER)
|
59
64
|
end
|
60
65
|
|
66
|
+
# Sends the file to the S3 bucket
|
67
|
+
# name: file name
|
68
|
+
# bucket: destination bucket name
|
69
|
+
# access: user credential access
|
70
|
+
# secret: user credential secret
|
61
71
|
def sends3(name,bucket,access,secret)
|
62
|
-
@@log.debug "connect to s3"
|
72
|
+
@@log.debug "connect to s3: "+FlowConfig.s3host+":"+FlowConfig.s3port.to_s+FlowConfig.s3path
|
63
73
|
AWS::S3::Base.establish_connection!(
|
64
74
|
:access_key_id => access,
|
65
75
|
:secret_access_key => secret,
|
data/lib/manband/user.rb
CHANGED
@@ -7,6 +7,7 @@ require 'manband/flowconfig.rb'
|
|
7
7
|
#DataMapper.setup(:default, 'sqlite:///tmp/project.db')
|
8
8
|
DataMapper.setup(:default, ENV['MYSQL_URL'])
|
9
9
|
|
10
|
+
# This class manages users in the database
|
10
11
|
class User
|
11
12
|
include DataMapper::Resource
|
12
13
|
|
@@ -17,6 +18,8 @@ class User
|
|
17
18
|
property :s3_access, String # User id key
|
18
19
|
property :s3_secret, String # Secret key
|
19
20
|
|
21
|
+
# Creates a default admin account if it does not exists.
|
22
|
+
# Default password is <b>admin</b>
|
20
23
|
def self.init
|
21
24
|
admin = User.get(@@admin)
|
22
25
|
if admin == nil
|
@@ -26,6 +29,9 @@ class User
|
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
32
|
+
# Check passwords
|
33
|
+
# password: user password
|
34
|
+
# @return: true if authentication is correct
|
29
35
|
def authenticate(password)
|
30
36
|
shapwd = Digest::SHA1.hexdigest(password)
|
31
37
|
if self.password == shapwd
|
data/lib/manband/workflow.rb
CHANGED
@@ -12,7 +12,9 @@ require 'manband/job.rb'
|
|
12
12
|
DataMapper.setup(:default, ENV['MYSQL_URL'])
|
13
13
|
|
14
14
|
|
15
|
-
|
15
|
+
# This class orchestrator the workflow status and the workflow file
|
16
|
+
# analysis. It determines if workflow is over, what are the next jobs
|
17
|
+
# to execute, ...
|
16
18
|
class WorkFlow
|
17
19
|
include DataMapper::Resource
|
18
20
|
|
@@ -34,6 +36,8 @@ class WorkFlow
|
|
34
36
|
property :parent, Integer, :default => 0 # Parent workflow id, none by default
|
35
37
|
property :instances, Integer, :default => 0 # Number of sub workflows, if any
|
36
38
|
|
39
|
+
# Checks if a workflow is over, e.g. we have reached all the
|
40
|
+
# terminal nodes (leafs).
|
37
41
|
def isover?
|
38
42
|
# decrement terminals
|
39
43
|
@terminals = @terminals - 1
|
@@ -54,7 +58,8 @@ class WorkFlow
|
|
54
58
|
return false
|
55
59
|
end
|
56
60
|
|
57
|
-
#
|
61
|
+
# Get the list of jobs to be run after current node
|
62
|
+
# @return an array of node names
|
58
63
|
def getnextjobs(curnode)
|
59
64
|
#fworkflow = YAML.load_file(@file)
|
60
65
|
fworkflow = BandManager.load(@file)
|
@@ -75,6 +80,9 @@ class WorkFlow
|
|
75
80
|
return nexts
|
76
81
|
end
|
77
82
|
|
83
|
+
# Parse workflow file and create jobs and links in the database
|
84
|
+
# curnode: current node
|
85
|
+
# id: id of the node as link originator
|
78
86
|
def parse(curnode, id = nil)
|
79
87
|
#fworkflow = YAML.load_file(@file)
|
80
88
|
fworkflow = BandManager.load(@file)
|
@@ -140,7 +148,9 @@ class WorkFlow
|
|
140
148
|
return newcommand
|
141
149
|
end
|
142
150
|
|
143
|
-
# Return
|
151
|
+
# Return a list of commands for the node in the workflow
|
152
|
+
# There is one command per input file matching regular expresssions,
|
153
|
+
# if any. Default is 1 command.
|
144
154
|
def getnodecommand(curnode)
|
145
155
|
#fworkflow = YAML.load_file(@file)
|
146
156
|
fworkflow = BandManager.load(@file)
|
@@ -247,6 +257,7 @@ class WorkFlow
|
|
247
257
|
return commands
|
248
258
|
end
|
249
259
|
|
260
|
+
# Clean a workflow directory
|
250
261
|
def clean
|
251
262
|
workflow = WorkFlow.get(@id)
|
252
263
|
if workflow.workdir == nil
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: manband
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Olivier Sallou
|