manband 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +10 -0
- data/README +108 -0
- data/bin/jobhandler.rb +135 -0
- data/bin/manband.rb +108 -0
- data/bin/remoteband.rb +76 -0
- data/bin/userband.rb +75 -0
- data/bin/workflowhandler.rb +182 -0
- data/lib/manband/bandmanager.rb +176 -0
- data/lib/manband/flowconfig.rb +87 -0
- data/lib/manband/job.rb +282 -0
- data/lib/manband/store.rb +77 -0
- data/lib/manband/user.rb +47 -0
- data/lib/manband/workflow.rb +274 -0
- metadata +175 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'data_mapper'
|
2
|
+
require 'dm-migrations'
|
3
|
+
require 'yaml'
|
4
|
+
require 'logger'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'json'
|
7
|
+
require 'manband/flowconfig.rb'
|
8
|
+
require 'manband/bandmanager.rb'
|
9
|
+
require 'manband/job.rb'
|
10
|
+
|
11
|
+
#DataMapper.setup(:default, 'sqlite:///home/osallou/Desktop/genflow/project.db')
|
12
|
+
DataMapper.setup(:default, ENV['MYSQL_URL'])
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
class WorkFlow
|
17
|
+
include DataMapper::Resource
|
18
|
+
|
19
|
+
@@log = Logger.new(STDOUT)
|
20
|
+
@@log.level = Logger::DEBUG
|
21
|
+
|
22
|
+
property :id, Serial # An auto-increment integer key
|
23
|
+
property :uid, Text # user id
|
24
|
+
property :name, String # A varchar type string, for short strings
|
25
|
+
property :description, Text # A text block, for longer string data.
|
26
|
+
property :file, String # path to the worflow file definition
|
27
|
+
property :created_at, DateTime # A DateTime, for any date you might like.
|
28
|
+
property :terminated_at, DateTime # Termination date
|
29
|
+
property :terminals, Integer
|
30
|
+
property :status, Integer # Running, Suspended, Error, Finished
|
31
|
+
property :workdir, Text # Workflow work dir
|
32
|
+
property :vars, Text, :default => "{}" # Runtime vars
|
33
|
+
property :bucket, String, :default => "manband" # Bucket name for S3 storage
|
34
|
+
property :parent, Integer, :default => 0 # Parent workflow id, none by default
|
35
|
+
property :instances, Integer, :default => 0 # Number of sub workflows, if any
|
36
|
+
|
37
|
+
def isover?
|
38
|
+
# decrement terminals
|
39
|
+
@terminals = @terminals - 1
|
40
|
+
curw = nil
|
41
|
+
# Use lock if MYSQL
|
42
|
+
if ENV['MYSQL_URL'].include?("mysql")
|
43
|
+
DataMapper.repository(:default).adapter.execute("UPDATE work_flows SET terminals = terminals - 1 WHERE id="+@id.to_s);
|
44
|
+
curw = WorkFlow.get(@id)
|
45
|
+
else
|
46
|
+
curw = WorkFlow.get(@id)
|
47
|
+
curw.update(:terminals => @terminals)
|
48
|
+
end
|
49
|
+
if curw.terminals <=0
|
50
|
+
@@log.info "Workflow "+@id.to_s+" is over"
|
51
|
+
curw.update(:terminated_at => Time.now, :status => STATUS_OVER)
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return an array of node names
|
58
|
+
def getnextjobs(curnode)
|
59
|
+
#fworkflow = YAML.load_file(@file)
|
60
|
+
fworkflow = BandManager.load(@file)
|
61
|
+
if fworkflow==nil
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
if fworkflow["workflow"][curnode]["next"] == nil
|
65
|
+
@@log.debug "no next node, this branch is over"
|
66
|
+
#isover?
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
nexts = fworkflow["workflow"][curnode]["next"].split(',')
|
70
|
+
if nexts[0].empty?
|
71
|
+
@@log.debug "no next node, this branch is over"
|
72
|
+
#isover?
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
return nexts
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse(curnode, id = nil)
|
79
|
+
#fworkflow = YAML.load_file(@file)
|
80
|
+
fworkflow = BandManager.load(@file)
|
81
|
+
if fworkflow==nil
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
jobs = getnextjobs(curnode)
|
85
|
+
if jobs == nil
|
86
|
+
return
|
87
|
+
end
|
88
|
+
jobs.each do |job|
|
89
|
+
if Job.count(:wid => @id, :node => job) == 0
|
90
|
+
queue = ""
|
91
|
+
if fworkflow["workflow"][job]["queue"]!=nil
|
92
|
+
queue = fworkflow["workflow"][job]["queue"]
|
93
|
+
end
|
94
|
+
status = STATUS_NEW
|
95
|
+
if fworkflow["workflow"][job]["breakpoint"]!=nil
|
96
|
+
@@log.debug "Node "+job+" has a breakpoint set"
|
97
|
+
status = STATUS_SUSPEND
|
98
|
+
end
|
99
|
+
store = STORE_NO
|
100
|
+
if (!fworkflow["options"].nil? && fworkflow["options"]["store"] == "all") || fworkflow["workflow"][job]["store"] == true
|
101
|
+
@@log.debug "Add store option for job "+job
|
102
|
+
store = STORE_DO
|
103
|
+
end
|
104
|
+
workdir = FlowConfig.getjobdir(@workdir)
|
105
|
+
if curnode == "root"
|
106
|
+
workdir = self.workdir + "/root";
|
107
|
+
end
|
108
|
+
newjob = Job.new(:wid => @id, :node => job, :command => "", :status => status, :instances => 0, :maxinstances => 0, :queue => queue, :workdir => FlowConfig.getjobdir(@workdir), :store => store)
|
109
|
+
newjob.save
|
110
|
+
if id != nil
|
111
|
+
@@log.debug "Add link "+id.to_s+"->"+newjob.id.to_s+","+newjob.node
|
112
|
+
link = JobLink.new(:wid => @id, :from => id, :to => newjob.id)
|
113
|
+
link.save
|
114
|
+
end
|
115
|
+
parse(job,newjob.id)
|
116
|
+
else
|
117
|
+
if id!=nil
|
118
|
+
# Already declared, just add link
|
119
|
+
linkedjob = Job.first(:wid => @id, :node => job)
|
120
|
+
@@log.debug "Add link "+id.to_s+"->"+linkedjob.id.to_s+","+linkedjob.node
|
121
|
+
link = JobLink.new(:wid => @id, :from => id, :to => linkedjob.id)
|
122
|
+
link.save
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Update in command the runtime vars
|
129
|
+
# return modified command
|
130
|
+
def setruntimevars(varexpr,command)
|
131
|
+
newcommand = String.new(command)
|
132
|
+
runtimevars = JSON.parse(self.vars)
|
133
|
+
if runtimevars[varexpr]!=nil
|
134
|
+
@@log.debug "Replace in command #var."+varexpr+"# by "+runtimevars[varexpr]
|
135
|
+
newcommand["#var."+varexpr+"#"]= runtimevars[varexpr]
|
136
|
+
else
|
137
|
+
# runtime var is node defined
|
138
|
+
return nil
|
139
|
+
end
|
140
|
+
return newcommand
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return command for the node in the workflow
|
144
|
+
def getnodecommand(curnode)
|
145
|
+
#fworkflow = YAML.load_file(@file)
|
146
|
+
fworkflow = BandManager.load(@file)
|
147
|
+
if fworkflow==nil
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
maincommand = fworkflow["workflow"][curnode]["command"]
|
151
|
+
# Manage node regexp
|
152
|
+
exprs = maincommand.scan(/#(node|var)\.(.*?)#/)
|
153
|
+
if exprs.length == 0
|
154
|
+
return [ fworkflow["workflow"][curnode]["command"] ]
|
155
|
+
end
|
156
|
+
subnodefilelist = Hash.new
|
157
|
+
multinode=nil
|
158
|
+
for reg in 0..exprs.length-1
|
159
|
+
@@log.debug "Expr "+": "+exprs[reg][0]+" "+exprs[reg][1]
|
160
|
+
if exprs[reg][0] == "var"
|
161
|
+
maincommand = setruntimevars(exprs[reg][1],maincommand)
|
162
|
+
if maincommand == nil
|
163
|
+
@@log.error "Runtime var "+exprs[reg][1]+" is not defined for workflow "+@id.to_s
|
164
|
+
return nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
if exprs[reg][0] == "node"
|
168
|
+
# Get regexp for this node
|
169
|
+
subnode = exprs[reg][1]
|
170
|
+
# If regexp is empty, we jsut want the directory
|
171
|
+
if fworkflow["workflow"][curnode][subnode]==nil
|
172
|
+
return nil
|
173
|
+
end
|
174
|
+
if fworkflow["workflow"][curnode][subnode]['regexp'].strip == ''
|
175
|
+
subnoderegexp = nil
|
176
|
+
else
|
177
|
+
subnoderegexp = Regexp.new(fworkflow["workflow"][curnode][subnode]['regexp'])
|
178
|
+
end
|
179
|
+
# List all files for this node regexp
|
180
|
+
if subnode.match(/local/)
|
181
|
+
# Local files reference
|
182
|
+
nodepath = fworkflow["workflow"][curnode][subnode]['url']
|
183
|
+
else
|
184
|
+
# An other node reference
|
185
|
+
if subnode == 'root'
|
186
|
+
nodepath = self.workdir+'/root';
|
187
|
+
else
|
188
|
+
subjob = Job.first(:wid => @id, :node => subnode)
|
189
|
+
nodepath = subjob.workdir
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
filelist = Array.new
|
194
|
+
if subnoderegexp == nil
|
195
|
+
filelist.push(nodepath+"/")
|
196
|
+
else
|
197
|
+
if !File.exists?(nodepath)
|
198
|
+
@@log.error("path does not exists!")
|
199
|
+
return nil
|
200
|
+
end
|
201
|
+
Dir.new(nodepath).entries.each do |n|
|
202
|
+
if subnoderegexp.match(n)
|
203
|
+
filelist.push(nodepath+"/"+n)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
@@log.debug "File list "+subnode+": "+filelist.to_s
|
208
|
+
# update file list per node regexp in the command
|
209
|
+
subnodefilelist[subnode]=filelist
|
210
|
+
if filelist.length>1
|
211
|
+
if multinode!=nil
|
212
|
+
# We do not support multiple lists in same command (N*N*N*....)
|
213
|
+
return nil
|
214
|
+
else
|
215
|
+
multinode = subnode
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# TODO manage local URI regexp (or remote)
|
222
|
+
# If remote: list then download
|
223
|
+
|
224
|
+
# Now create an array of command with file lists substitution
|
225
|
+
commands = Array.new
|
226
|
+
subnodefilelist.each do |key,slist|
|
227
|
+
if slist[0] == nil
|
228
|
+
return nil
|
229
|
+
end
|
230
|
+
if key != multinode
|
231
|
+
# replace, one match only allowed here
|
232
|
+
maincommand["#node."+key+"#"]= slist[0]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
if multinode!=nil
|
236
|
+
@@log.debug "Multinode: "+multinode
|
237
|
+
subnodefilelist[multinode].each do |file|
|
238
|
+
newcommand = String.new(maincommand)
|
239
|
+
@@log.debug "Command: "+newcommand
|
240
|
+
newcommand["#node."+multinode+"#"]= file
|
241
|
+
commands.push(newcommand)
|
242
|
+
end
|
243
|
+
else
|
244
|
+
commands.push(maincommand)
|
245
|
+
end
|
246
|
+
@@log.debug "Commands: "+commands.to_s
|
247
|
+
return commands
|
248
|
+
end
|
249
|
+
|
250
|
+
def clean
|
251
|
+
workflow = WorkFlow.get(@id)
|
252
|
+
if workflow.workdir == nil
|
253
|
+
return
|
254
|
+
end
|
255
|
+
if File.directory? workflow.workdir
|
256
|
+
FileUtils.rm_rf(workflow.workdir)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
# Table hosting workflow handler messages
|
263
|
+
class BandMessage
|
264
|
+
include DataMapper::Resource
|
265
|
+
|
266
|
+
property :id, Serial # An auto-increment integer key
|
267
|
+
property :wid, Integer # Workflow id
|
268
|
+
property :message, Text # Message text
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
DataMapper.finalize
|
273
|
+
DataMapper.auto_upgrade!
|
274
|
+
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: manband
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Olivier Sallou
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-05-10 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: amqp
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: json
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: data_mapper
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: dm-mysql-adapter
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: zip
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
type: :runtime
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: mb-minion
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
type: :runtime
|
103
|
+
version_requirements: *id006
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: mb-aws-s3
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
type: :runtime
|
117
|
+
version_requirements: *id007
|
118
|
+
description:
|
119
|
+
email: olivier.sallou@irisa.fr
|
120
|
+
executables: []
|
121
|
+
|
122
|
+
extensions: []
|
123
|
+
|
124
|
+
extra_rdoc_files:
|
125
|
+
- README
|
126
|
+
- LICENSE
|
127
|
+
files:
|
128
|
+
- bin/manband.rb
|
129
|
+
- bin/remoteband.rb
|
130
|
+
- bin/jobhandler.rb
|
131
|
+
- bin/workflowhandler.rb
|
132
|
+
- bin/userband.rb
|
133
|
+
- lib/manband/store.rb
|
134
|
+
- lib/manband/bandmanager.rb
|
135
|
+
- lib/manband/user.rb
|
136
|
+
- lib/manband/workflow.rb
|
137
|
+
- lib/manband/job.rb
|
138
|
+
- lib/manband/flowconfig.rb
|
139
|
+
- README
|
140
|
+
- LICENSE
|
141
|
+
homepage: http://gforge.inria.fr/projects/manband
|
142
|
+
licenses:
|
143
|
+
- CeCILL-C
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
hash: 3
|
155
|
+
segments:
|
156
|
+
- 0
|
157
|
+
version: "0"
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
none: false
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
hash: 3
|
164
|
+
segments:
|
165
|
+
- 0
|
166
|
+
version: "0"
|
167
|
+
requirements: []
|
168
|
+
|
169
|
+
rubyforge_project:
|
170
|
+
rubygems_version: 1.8.15
|
171
|
+
signing_key:
|
172
|
+
specification_version: 3
|
173
|
+
summary: Workflow orchestrator based on RabbitMQ
|
174
|
+
test_files: []
|
175
|
+
|