scout-gear 7.3.0 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +44 -16
  3. data/Rakefile +6 -1
  4. data/VERSION +1 -1
  5. data/bin/scout +21 -7
  6. data/doc/lib/scout/path.md +35 -0
  7. data/doc/lib/scout/workflow/task.md +13 -0
  8. data/lib/rbbt-scout.rb +1 -0
  9. data/lib/scout/cmd.rb +24 -25
  10. data/lib/scout/concurrent_stream.rb +59 -39
  11. data/lib/scout/config.rb +1 -1
  12. data/lib/scout/exceptions.rb +10 -0
  13. data/lib/scout/log/color.rb +15 -12
  14. data/lib/scout/log/progress/report.rb +8 -6
  15. data/lib/scout/log/progress/util.rb +61 -54
  16. data/lib/scout/log/progress.rb +1 -1
  17. data/lib/scout/log/trap.rb +107 -0
  18. data/lib/scout/log.rb +115 -52
  19. data/lib/scout/meta_extension.rb +47 -6
  20. data/lib/scout/misc/digest.rb +12 -3
  21. data/lib/scout/misc/format.rb +24 -7
  22. data/lib/scout/misc/insist.rb +1 -1
  23. data/lib/scout/misc/monitor.rb +22 -0
  24. data/lib/scout/misc/system.rb +58 -0
  25. data/lib/scout/named_array.rb +73 -3
  26. data/lib/scout/offsite/ssh.rb +171 -0
  27. data/lib/scout/offsite/step.rb +83 -0
  28. data/lib/scout/offsite/sync.rb +55 -0
  29. data/lib/scout/offsite.rb +3 -0
  30. data/lib/scout/open/lock/lockfile.rb +587 -0
  31. data/lib/scout/open/lock.rb +9 -2
  32. data/lib/scout/open/remote.rb +16 -1
  33. data/lib/scout/open/stream.rb +146 -83
  34. data/lib/scout/open/util.rb +22 -3
  35. data/lib/scout/open.rb +5 -4
  36. data/lib/scout/path/find.rb +24 -11
  37. data/lib/scout/path/util.rb +40 -0
  38. data/lib/scout/persist/serialize.rb +19 -6
  39. data/lib/scout/persist.rb +29 -13
  40. data/lib/scout/resource/path.rb +57 -0
  41. data/lib/scout/resource/produce.rb +0 -8
  42. data/lib/scout/resource/util.rb +12 -5
  43. data/lib/scout/tmpfile.rb +7 -8
  44. data/lib/scout/tsv/attach.rb +177 -0
  45. data/lib/scout/tsv/change_id.rb +40 -0
  46. data/lib/scout/tsv/dumper.rb +74 -46
  47. data/lib/scout/tsv/index.rb +85 -87
  48. data/lib/scout/tsv/open.rb +160 -85
  49. data/lib/scout/tsv/parser.rb +142 -80
  50. data/lib/scout/tsv/path.rb +1 -2
  51. data/lib/scout/tsv/persist/adapter.rb +15 -45
  52. data/lib/scout/tsv/persist/fix_width_table.rb +3 -0
  53. data/lib/scout/tsv/persist/tokyocabinet.rb +6 -1
  54. data/lib/scout/tsv/persist.rb +4 -0
  55. data/lib/scout/tsv/stream.rb +204 -0
  56. data/lib/scout/tsv/transformer.rb +152 -0
  57. data/lib/scout/tsv/traverse.rb +96 -92
  58. data/lib/scout/tsv/util/filter.rb +9 -0
  59. data/lib/scout/tsv/util/reorder.rb +81 -0
  60. data/lib/scout/tsv/util/select.rb +78 -33
  61. data/lib/scout/tsv/util/unzip.rb +86 -0
  62. data/lib/scout/tsv/util.rb +60 -11
  63. data/lib/scout/tsv.rb +34 -4
  64. data/lib/scout/work_queue/socket.rb +6 -1
  65. data/lib/scout/work_queue/worker.rb +5 -2
  66. data/lib/scout/work_queue.rb +51 -20
  67. data/lib/scout/workflow/definition.rb +23 -3
  68. data/lib/scout/workflow/deployment/orchestrator.rb +245 -0
  69. data/lib/scout/workflow/deployment.rb +1 -0
  70. data/lib/scout/workflow/step/dependencies.rb +56 -10
  71. data/lib/scout/workflow/step/file.rb +5 -0
  72. data/lib/scout/workflow/step/info.rb +40 -7
  73. data/lib/scout/workflow/step/load.rb +1 -1
  74. data/lib/scout/workflow/step/provenance.rb +9 -7
  75. data/lib/scout/workflow/step/status.rb +43 -0
  76. data/lib/scout/workflow/step.rb +160 -49
  77. data/lib/scout/workflow/task/dependencies.rb +114 -0
  78. data/lib/scout/workflow/task/inputs.rb +40 -32
  79. data/lib/scout/workflow/task.rb +38 -102
  80. data/lib/scout/workflow/usage.rb +48 -18
  81. data/lib/scout/workflow.rb +4 -2
  82. data/lib/scout-gear.rb +2 -0
  83. data/lib/scout.rb +6 -0
  84. data/scout-gear.gemspec +52 -23
  85. data/scout_commands/doc +37 -0
  86. data/scout_commands/find +1 -0
  87. data/scout_commands/offsite +30 -0
  88. data/scout_commands/update +29 -0
  89. data/scout_commands/workflow/info +15 -3
  90. data/scout_commands/workflow/install +102 -0
  91. data/scout_commands/workflow/task +57 -9
  92. data/test/scout/offsite/test_ssh.rb +15 -0
  93. data/test/scout/offsite/test_step.rb +33 -0
  94. data/test/scout/offsite/test_sync.rb +36 -0
  95. data/test/scout/offsite/test_task.rb +0 -0
  96. data/test/scout/open/test_stream.rb +60 -58
  97. data/test/scout/path/test_find.rb +10 -1
  98. data/test/scout/resource/test_path.rb +6 -0
  99. data/test/scout/resource/test_produce.rb +15 -0
  100. data/test/scout/test_meta_extension.rb +25 -0
  101. data/test/scout/test_named_array.rb +24 -0
  102. data/test/scout/test_persist.rb +9 -2
  103. data/test/scout/test_tsv.rb +229 -2
  104. data/test/scout/test_work_queue.rb +65 -41
  105. data/test/scout/tsv/persist/test_tokyocabinet.rb +29 -1
  106. data/test/scout/tsv/test_attach.rb +227 -0
  107. data/test/scout/tsv/test_change_id.rb +98 -0
  108. data/test/scout/tsv/test_dumper.rb +1 -1
  109. data/test/scout/tsv/test_index.rb +49 -3
  110. data/test/scout/tsv/test_open.rb +160 -2
  111. data/test/scout/tsv/test_parser.rb +33 -2
  112. data/test/scout/tsv/test_persist.rb +2 -0
  113. data/test/scout/tsv/test_stream.rb +200 -0
  114. data/test/scout/tsv/test_transformer.rb +120 -0
  115. data/test/scout/tsv/test_traverse.rb +88 -3
  116. data/test/scout/tsv/test_util.rb +1 -0
  117. data/test/scout/tsv/util/test_reorder.rb +94 -0
  118. data/test/scout/tsv/util/test_select.rb +25 -11
  119. data/test/scout/tsv/util/test_unzip.rb +112 -0
  120. data/test/scout/work_queue/test_socket.rb +0 -1
  121. data/test/scout/workflow/deployment/test_orchestrator.rb +272 -0
  122. data/test/scout/workflow/step/test_dependencies.rb +68 -0
  123. data/test/scout/workflow/step/test_info.rb +18 -0
  124. data/test/scout/workflow/step/test_status.rb +30 -0
  125. data/test/scout/workflow/task/test_dependencies.rb +355 -0
  126. data/test/scout/workflow/task/test_inputs.rb +67 -14
  127. data/test/scout/workflow/test_definition.rb +18 -0
  128. data/test/scout/workflow/test_documentation.rb +24 -0
  129. data/test/scout/workflow/test_step.rb +112 -3
  130. data/test/scout/workflow/test_task.rb +0 -151
  131. data/test/scout/workflow/test_usage.rb +33 -6
  132. data/test/test_scout.rb +9 -0
  133. metadata +100 -8
  134. data/scout_commands/workflow/task_old +0 -706
@@ -0,0 +1,55 @@
1
+ class SSHLine
2
+ def self.locate(server, paths, map: :user)
3
+ SSHLine.scout server, <<-EOF
4
+ map = :#{map}
5
+ paths = [#{paths.collect{|p| "'" + p + "'" } * ", " }]
6
+ located = paths.collect{|p| Path.setup(p).find(map) }
7
+ identified = paths.collect{|p| Resource.identify(p) }
8
+ [located, identified]
9
+ EOF
10
+ end
11
+
12
+ def self.rsync(source_path, target_path, directory: false, source: nil, target: nil, dry_run: false, hard_link: false)
13
+ rsync_args = "-avztHP --copy-unsafe-links --omit-dir-times "
14
+
15
+ rsync_args << "--link-dest '#{source_path}' " if hard_link && ! source
16
+
17
+ source_path = source_path + "/" if directory && ! source_path.end_with?("/")
18
+ target_path = target_path + "/" if directory && ! target_path.end_with?("/")
19
+ if target
20
+ SSHLine.mkdir target, File.dirname(target_path)
21
+ else
22
+ Open.mkdir(File.dirname(target_path))
23
+ end
24
+
25
+ cmd = 'rsync '
26
+ cmd << rsync_args
27
+ cmd << '-nv ' if dry_run
28
+ cmd << (source ? [source, source_path] * ":" : source_path) << " "
29
+ cmd << (target ? [target, target_path] * ":" : target_path) << " "
30
+
31
+ CMD.cmd_log(cmd, :log => Log::HIGH)
32
+ end
33
+
34
+ def self.sync(paths, source: nil, target: nil, map: :user, **kwargs)
35
+ source = nil if source == 'localhost'
36
+ target = nil if target == 'localhost'
37
+
38
+ if source
39
+ source_paths, identified_paths = SSHLine.locate(source, paths)
40
+ else
41
+ source_paths = paths.collect{|p| Path === p ? p.find : p }
42
+ identified_paths = paths.collect{|p| Resource.identify(p) }
43
+ end
44
+
45
+ if target
46
+ target_paths = SSHLine.locate(target, identified_paths, map: map)
47
+ else
48
+ target_paths = identified_paths.collect{|p| p.find(map) }
49
+ end
50
+
51
+ source_paths.zip(target_paths).each do |source_path,target_path|
52
+ rsync(source_path, target_path, source: source, target: target, **kwargs)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'offsite/ssh'
2
+ require_relative 'offsite/step'
3
+ require_relative 'offsite/sync'
@@ -0,0 +1,587 @@
1
+ # Originaly from here https://github.com/ahoward/lockfile
2
+ # Author: Ara T. Howard (Ara T. Howard)
3
+ # Licence: Ruby (as listed [here](https://github.com/ahoward/lockfile/blob/20ab06f29bda69b9773d799510d00585b6a27e3b/lockfile.gemspec#L10))
4
+ # with modifications for scout-gear and rbbt by Miguel Vazquez
5
+ unless(defined?($__lockfile__) or defined?(Lockfile))
6
+
7
+ require 'socket'
8
+ require 'timeout'
9
+ require 'fileutils'
10
+
11
+ class Lockfile
12
+
13
+ VERSION = '2.1.8'
14
+ def Lockfile.version() Lockfile::VERSION end
15
+ def version() Lockfile::VERSION end
16
+
17
+ def Lockfile.description
18
+ 'a ruby library for creating perfect and NFS safe lockfiles'
19
+ end
20
+
21
+ class LockError < StandardError; end
22
+ class StolenLockError < LockError; end
23
+ class StackingLockError < LockError; end
24
+ class StatLockError < LockError; end
25
+ class MaxTriesLockError < LockError; end
26
+ class TimeoutLockError < LockError; end
27
+ class NFSLockError < LockError; end
28
+ class UnLockError < LockError; end
29
+
30
+ class SleepCycle < Array
31
+ attr_reader :min
32
+ attr_reader :max
33
+ attr_reader :range
34
+ attr_reader :inc
35
+
36
+ def initialize(min, max, inc)
37
+ @min, @max, @inc = Float(min), Float(max), Float(inc)
38
+ @range = @max - @min
39
+ raise RangeError, "max(#{ @max }) <= min(#{ @min })" if @max <= @min
40
+ raise RangeError, "inc(#{ @inc }) > range(#{ @range })" if @inc > @range
41
+ raise RangeError, "inc(#{ @inc }) <= 0" if @inc <= 0
42
+ raise RangeError, "range(#{ @range }) <= 0" if @range <= 0
43
+ s = @min
44
+ push(s) and s += @inc while(s <= @max)
45
+ self[-1] = @max if self[-1] < @max
46
+ reset
47
+ end
48
+
49
+ def next
50
+ ret = self[@idx]
51
+ @idx = ((@idx + 1) % self.size)
52
+ ret
53
+ end
54
+
55
+ def reset
56
+ @idx = 0
57
+ end
58
+ end
59
+
60
+ HOSTNAME = Socket.gethostname
61
+
62
+ DEFAULT_RETRIES = nil # maximum number of attempts
63
+ DEFAULT_TIMEOUT = nil # the longest we will try
64
+ DEFAULT_MAX_AGE = 3600 # lockfiles older than this are stale
65
+ DEFAULT_SLEEP_INC = 2 # sleep cycle is this much longer each time
66
+ DEFAULT_MIN_SLEEP = 2 # shortest sleep time
67
+ DEFAULT_MAX_SLEEP = 32 # longest sleep time
68
+ DEFAULT_SUSPEND = 1800 # iff we steal a lock wait this long before we go on
69
+ DEFAULT_REFRESH = 8 # how often we touch/validate the lock
70
+ DEFAULT_DONT_CLEAN = false # iff we leave lock files lying around
71
+ DEFAULT_POLL_RETRIES = 16 # this many polls makes one 'try'
72
+ DEFAULT_POLL_MAX_SLEEP = 0.08 # the longest we'll sleep between polls
73
+ DEFAULT_DONT_SWEEP = false # if we cleanup after other process on our host
74
+ DEFAULT_DONT_USE_LOCK_ID = false # if we dump lock info into lockfile
75
+
76
+ DEFAULT_DEBUG = ENV['LOCKFILE_DEBUG'] || false
77
+
78
+ class << Lockfile
79
+ attr_accessor :retries
80
+ attr_accessor :max_age
81
+ attr_accessor :sleep_inc
82
+ attr_accessor :min_sleep
83
+ attr_accessor :max_sleep
84
+ attr_accessor :suspend
85
+ attr_accessor :timeout
86
+ attr_accessor :refresh
87
+ attr_accessor :debug
88
+ attr_accessor :dont_clean
89
+ attr_accessor :poll_retries
90
+ attr_accessor :poll_max_sleep
91
+ attr_accessor :dont_sweep
92
+ attr_accessor :dont_use_lock_id
93
+
94
+ def init
95
+ @retries = DEFAULT_RETRIES
96
+ @max_age = DEFAULT_MAX_AGE
97
+ @sleep_inc = DEFAULT_SLEEP_INC
98
+ @min_sleep = DEFAULT_MIN_SLEEP
99
+ @max_sleep = DEFAULT_MAX_SLEEP
100
+ @suspend = DEFAULT_SUSPEND
101
+ @timeout = DEFAULT_TIMEOUT
102
+ @refresh = DEFAULT_REFRESH
103
+ @dont_clean = DEFAULT_DONT_CLEAN
104
+ @poll_retries = DEFAULT_POLL_RETRIES
105
+ @poll_max_sleep = DEFAULT_POLL_MAX_SLEEP
106
+ @dont_sweep = DEFAULT_DONT_SWEEP
107
+ @dont_use_lock_id = DEFAULT_DONT_USE_LOCK_ID
108
+
109
+ @debug = DEFAULT_DEBUG
110
+
111
+ STDOUT.sync = true if @debug
112
+ STDERR.sync = true if @debug
113
+ end
114
+ end
115
+
116
+ Lockfile.init
117
+
118
+ attr_reader :klass
119
+ attr_reader :path
120
+ attr_reader :opts
121
+ attr_reader :locked
122
+ attr_reader :thief
123
+ attr_reader :refresher
124
+ attr_reader :dirname
125
+ attr_reader :basename
126
+ attr_reader :clean
127
+ attr_reader :retries
128
+ attr_reader :max_age
129
+ attr_reader :sleep_inc
130
+ attr_reader :min_sleep
131
+ attr_reader :max_sleep
132
+ attr_reader :suspend
133
+ attr_reader :refresh
134
+ attr_reader :timeout
135
+ attr_reader :dont_clean
136
+ attr_reader :poll_retries
137
+ attr_reader :poll_max_sleep
138
+ attr_reader :dont_sweep
139
+ attr_reader :dont_use_lock_id
140
+
141
+ attr_accessor :debug
142
+
143
+ alias thief? thief
144
+ alias locked? locked
145
+ alias debug? debug
146
+
147
+ def Lockfile.create(path, *a, &b)
148
+ opts = {
149
+ 'retries' => 0,
150
+ 'min_sleep' => 0,
151
+ 'max_sleep' => 1,
152
+ 'sleep_inc' => 1,
153
+ 'max_age' => nil,
154
+ 'suspend' => 0,
155
+ 'refresh' => nil,
156
+ 'timeout' => nil,
157
+ 'poll_retries' => 0,
158
+ 'dont_clean' => true,
159
+ 'dont_sweep' => false,
160
+ 'dont_use_lock_id' => true,
161
+ }
162
+ begin
163
+ new(path, opts).lock
164
+ rescue LockError
165
+ raise Errno::EEXIST, path
166
+ end
167
+ open(path, *a, &b)
168
+ end
169
+
170
+ def self.finalizer_proc(file)
171
+ pid = Process.pid
172
+ lambda do |id|
173
+ File.unlink file if Process.pid == pid
174
+ rescue
175
+ nil
176
+ end
177
+ end
178
+
179
+ def initialize(path, opts = {}, &block)
180
+ @klass = self.class
181
+ @path = path
182
+ @opts = opts
183
+
184
+ @retries = getopt 'retries' , @klass.retries
185
+ @max_age = getopt 'max_age' , @klass.max_age
186
+ @sleep_inc = getopt 'sleep_inc' , @klass.sleep_inc
187
+ @min_sleep = getopt 'min_sleep' , @klass.min_sleep
188
+ @max_sleep = getopt 'max_sleep' , @klass.max_sleep
189
+ @suspend = getopt 'suspend' , @klass.suspend
190
+ @timeout = getopt 'timeout' , @klass.timeout
191
+ @refresh = getopt 'refresh' , @klass.refresh
192
+ @dont_clean = getopt 'dont_clean' , @klass.dont_clean
193
+ @poll_retries = getopt 'poll_retries' , @klass.poll_retries
194
+ @poll_max_sleep = getopt 'poll_max_sleep' , @klass.poll_max_sleep
195
+ @dont_sweep = getopt 'dont_sweep' , @klass.dont_sweep
196
+ @dont_use_lock_id = getopt 'dont_use_lock_id' , @klass.dont_use_lock_id
197
+ @debug = getopt 'debug' , @klass.debug
198
+
199
+ @semaphore = Mutex.new
200
+
201
+ @sleep_cycle = SleepCycle.new @min_sleep, @max_sleep, @sleep_inc
202
+
203
+ @clean = @dont_clean ? nil : Lockfile.finalizer_proc(@path)
204
+
205
+ @dirname = File.dirname @path
206
+ @basename = File.basename @path
207
+ @thief = false
208
+ @locked = false
209
+ @refrsher = nil
210
+
211
+ lock(&block) if block
212
+ end
213
+
214
+ ##
215
+ # Executes the given block after acquiring the lock and
216
+ # ensures that the lock is relinquished afterwards.
217
+ #
218
+ def synchronize
219
+ raise ArgumentError, 'block must be given' unless block_given?
220
+ begin
221
+ lock
222
+ yield
223
+ ensure
224
+ unlock
225
+ end
226
+ end
227
+
228
+ def lock
229
+ raise StackingLockError, "<#{ @path }> is locked!" if @locked
230
+
231
+ sweep unless @dont_sweep
232
+
233
+ ret = nil
234
+
235
+ attempt do
236
+ begin
237
+ @sleep_cycle.reset
238
+ create_tmplock do |f|
239
+ begin
240
+ Timeout.timeout(@timeout) do
241
+ tmp_path = f.path
242
+ tmp_stat = f.lstat
243
+ n_retries = 0
244
+ trace{ "attempting to lock <#{ @path }>..." }
245
+ begin
246
+ i = 0
247
+ begin
248
+ trace{ "polling attempt <#{ i }>..." }
249
+ begin
250
+ File.link tmp_path, @path
251
+ rescue Errno::ENOENT
252
+ try_again!
253
+ end
254
+ lock_stat = File.lstat @path
255
+ raise StatLockError, "stat's do not agree" unless
256
+ tmp_stat.rdev == lock_stat.rdev and tmp_stat.ino == lock_stat.ino
257
+ trace{ "aquired lock <#{ @path }>" }
258
+ @locked = true
259
+ rescue => e
260
+ i += 1
261
+ unless i >= @poll_retries
262
+ t = [rand(@poll_max_sleep), @poll_max_sleep].min
263
+ trace{ "poll sleep <#{ t }>..." }
264
+ sleep t
265
+ retry
266
+ end
267
+ raise
268
+ end
269
+
270
+ rescue => e
271
+ n_retries += 1
272
+ trace{ "n_retries <#{ n_retries }>" }
273
+ case validlock?
274
+ when true
275
+ raise MaxTriesLockError, "surpased retries <#{ @retries }>" if
276
+ @retries and n_retries >= @retries
277
+ trace{ "found valid lock" }
278
+ sleeptime = @sleep_cycle.next
279
+ trace{ "sleep <#{ sleeptime }>..." }
280
+ sleep sleeptime
281
+ when false
282
+ trace{ "found invalid lock and removing" }
283
+ begin
284
+ File.unlink @path
285
+ @thief = true
286
+ warn "<#{ @path }> stolen by <#{ Process.pid }> at <#{ timestamp }>"
287
+ trace{ "i am a thief!" }
288
+ rescue Errno::ENOENT
289
+ end
290
+ trace{ "suspending <#{ @suspend }>" }
291
+ sleep @suspend
292
+ when nil
293
+ raise MaxTriesLockError, "surpased retries <#{ @retries }>" if
294
+ @retries and n_retries >= @retries
295
+ end
296
+ retry
297
+ end # begin
298
+ end # timeout
299
+ rescue Timeout::Error
300
+ raise TimeoutLockError, "surpassed timeout <#{ @timeout }>"
301
+ end # begin
302
+ end # create_tmplock
303
+
304
+ if block_given?
305
+ stolen = false
306
+ @refresher = (@refresh ? new_refresher : nil)
307
+ begin
308
+ begin
309
+ ret = yield @path
310
+ rescue StolenLockError
311
+ stolen = true
312
+ raise
313
+ end
314
+ ensure
315
+ begin
316
+ begin
317
+ @semaphore.synchronize do
318
+ @refresher.kill
319
+ end
320
+ rescue
321
+ @refresher.kill
322
+ end if @refresher and @refresher.status
323
+ @refresher = nil
324
+ ensure
325
+ unlock unless stolen
326
+ end
327
+ end
328
+ else
329
+ @refresher = (@refresh ? new_refresher : nil)
330
+ ObjectSpace.define_finalizer self, @clean if @clean
331
+ ret = self
332
+ end
333
+ rescue Errno::ESTALE, Errno::EIO => e
334
+ raise(NFSLockError, errmsg(e))
335
+ end
336
+ end
337
+
338
+ return ret
339
+ end
340
+
341
+ def sweep
342
+ begin
343
+ glob = File.join(@dirname, ".*lck")
344
+ paths = Dir[glob]
345
+ paths.each do |path|
346
+ begin
347
+ basename = File.basename path
348
+ pat = %r/^\s*\.([^_]+)_([^_]+)/o
349
+ if pat.match(basename)
350
+ host, pid = $1, $2
351
+ else
352
+ next
353
+ end
354
+ host.gsub!(%r/^\.+|\.+$/,'')
355
+ quad = host.split %r/\./
356
+ host = quad.first
357
+ pat = %r/^\s*#{ host }/i
358
+ if pat.match(HOSTNAME) and %r/^\s*\d+\s*$/.match(pid)
359
+ unless alive?(pid)
360
+ trace{ "process <#{ pid }> on <#{ host }> is no longer alive" }
361
+ trace{ "sweeping <#{ path }>" }
362
+ FileUtils.rm_f path
363
+ else
364
+ trace{ "process <#{ pid }> on <#{ host }> is still alive" }
365
+ trace{ "ignoring <#{ path }>" }
366
+ end
367
+ else
368
+ trace{ "ignoring <#{ path }> generated by <#{ host }>" }
369
+ end
370
+ rescue
371
+ next
372
+ end
373
+ end
374
+ rescue => e
375
+ warn(errmsg(e))
376
+ end
377
+ end
378
+
379
+ def alive? pid
380
+ pid = Integer("#{ pid }")
381
+ begin
382
+ Process.kill 0, pid
383
+ true
384
+ rescue Errno::ESRCH
385
+ false
386
+ end
387
+ end
388
+
389
+ def unlock
390
+ raise UnLockError, "<#{ @path }> is not locked!" unless @locked
391
+
392
+ begin
393
+ @semaphore.synchronize do
394
+ @refresher.kill
395
+ end
396
+ rescue
397
+ @refresher.kill
398
+ end if @refresher and @refresher.status
399
+
400
+ @refresher = nil
401
+
402
+ begin
403
+ File.unlink @path
404
+ rescue Errno::ENOENT
405
+ raise StolenLockError, @path
406
+ ensure
407
+ @thief = false
408
+ @locked = false
409
+ ObjectSpace.undefine_finalizer self if @clean
410
+ end
411
+ end
412
+
413
+ def new_refresher
414
+ Thread.new(Thread.current, @path, @refresh, @dont_use_lock_id) do |thread, path, refresh, dont_use_lock_id|
415
+ loop do
416
+ begin
417
+ touch path
418
+ trace{"touched <#{ path }> @ <#{ Time.now.to_f }>"}
419
+ unless dont_use_lock_id
420
+ txt = nil
421
+ @semaphore.synchronize do
422
+ txt = IO.read(path)
423
+ end
424
+ loaded = load_lock_id(txt)
425
+ trace{"loaded <\n#{ loaded.inspect }\n>"}
426
+ raise unless loaded == @lock_id
427
+ end
428
+ sleep refresh
429
+ rescue Exception => e
430
+ trace{errmsg e}
431
+ thread.raise StolenLockError
432
+ Thread.exit
433
+ end
434
+ end
435
+ end
436
+ end
437
+
438
+ def validlock?
439
+ if @max_age
440
+ uncache @path rescue nil
441
+ begin
442
+ return((Time.now - File.stat(@path).mtime) < @max_age)
443
+ rescue Errno::ENOENT
444
+ return nil
445
+ end
446
+ else
447
+ exist = File.exist?(@path)
448
+ return(exist ? true : nil)
449
+ end
450
+ end
451
+
452
+ def uncache file
453
+ refresh = nil
454
+ begin
455
+ is_a_file = File === file
456
+ path = (is_a_file ? file.path : file.to_s)
457
+ stat = (is_a_file ? file.stat : File.stat(file.to_s))
458
+ refresh = tmpnam(File.dirname(path))
459
+ File.link path, refresh
460
+ File.chmod stat.mode, path
461
+ File.utime stat.atime, stat.mtime, path
462
+ ensure
463
+ begin
464
+ File.unlink refresh if refresh
465
+ rescue Errno::ENOENT
466
+ end
467
+ end
468
+ end
469
+
470
+ def create_tmplock
471
+ tmplock = tmpnam @dirname
472
+ begin
473
+ create(tmplock) do |f|
474
+ unless dont_use_lock_id
475
+ @lock_id = gen_lock_id
476
+ dumped = dump_lock_id
477
+ trace{"lock_id <\n#{ @lock_id.inspect }\n>"}
478
+ f.write dumped
479
+ f.flush
480
+ end
481
+ yield f
482
+ end
483
+ ensure
484
+ begin; File.unlink tmplock; rescue Errno::ENOENT; end if tmplock
485
+ end
486
+ end
487
+
488
+ def gen_lock_id
489
+ Hash[
490
+ 'host' => "#{ HOSTNAME }",
491
+ 'pid' => "#{ Process.pid }",
492
+ 'ppid' => "#{ Process.ppid }",
493
+ 'time' => timestamp,
494
+ ]
495
+ end
496
+
497
+ def timestamp
498
+ time = Time.now
499
+ usec = time.usec.to_s
500
+ usec << '0' while usec.size < 6
501
+ "#{ time.strftime('%Y-%m-%d %H:%M:%S') }.#{ usec }"
502
+ end
503
+
504
+ def dump_lock_id(lock_id = @lock_id)
505
+ "host: %s\npid: %s\nppid: %s\ntime: %s\n" %
506
+ lock_id.values_at('host','pid','ppid','time')
507
+ end
508
+
509
+ def load_lock_id(buf)
510
+ lock_id = {}
511
+ kv = %r/([^:]+):(.*)/o
512
+ buf.each_line do |line|
513
+ m = kv.match line
514
+ k, v = m[1], m[2]
515
+ next unless m and k and v
516
+ lock_id[k.strip] = v.strip
517
+ end
518
+ lock_id
519
+ end
520
+
521
+ def tmpnam(dir, seed = File.basename($0))
522
+ pid = Process.pid
523
+ time = Time.now
524
+ sec = time.to_i
525
+ usec = time.usec
526
+ "%s%s.%s_%d_%s_%d_%d_%d.lck" %
527
+ [dir, File::SEPARATOR, HOSTNAME, pid, seed, sec, usec, rand(sec)]
528
+ end
529
+
530
+ def create(path)
531
+ umask = nil
532
+ f = nil
533
+ begin
534
+ umask = File.umask 022
535
+ f = open path, File::WRONLY|File::CREAT|File::EXCL, 0644
536
+ ensure
537
+ File.umask umask if umask
538
+ end
539
+ return(block_given? ? begin; yield f; ensure; f.close; end : f)
540
+ end
541
+
542
+ def touch(path)
543
+ FileUtils.touch path
544
+ end
545
+
546
+ def getopt(key, default = nil)
547
+ [ key, key.to_s, key.to_s.intern ].each do |k|
548
+ return @opts[k] if @opts.has_key?(k)
549
+ end
550
+ return default
551
+ end
552
+
553
+ def to_str
554
+ @path
555
+ end
556
+ alias to_s to_str
557
+
558
+ def trace(s = nil)
559
+ STDERR.puts((s ? s : yield)) if @debug
560
+ end
561
+
562
+ def errmsg(e)
563
+ "%s (%s)\n%s\n" % [e.class, e.message, e.backtrace.join("\n")]
564
+ end
565
+
566
+ def attempt
567
+ ret = nil
568
+ loop{ break unless catch('attempt'){ ret = yield } == 'try_again' }
569
+ ret
570
+ end
571
+
572
+ def try_again!
573
+ throw 'attempt', 'try_again'
574
+ end
575
+ alias again! try_again!
576
+
577
+ def give_up!
578
+ throw 'attempt', 'give_up'
579
+ end
580
+ end
581
+
582
+ def Lockfile(path, *a, &b)
583
+ Lockfile.new(path, *a, &b)
584
+ end
585
+
586
+ $__lockfile__ = __FILE__
587
+ end
@@ -1,9 +1,17 @@
1
1
  require_relative '../path'
2
2
  require_relative '../log'
3
3
  require_relative '../exceptions'
4
- require 'lockfile'
4
+ require_relative 'lock/lockfile'
5
5
 
6
6
  module Open
7
+ def self.init_lock
8
+ Lockfile.refresh = 2
9
+ Lockfile.max_age = 30
10
+ Lockfile.suspend = 4
11
+ end
12
+
13
+ self.init_lock
14
+
7
15
  def self.lock(file, unlock = true, options = {})
8
16
  unlock, options = true, unlock if Hash === unlock
9
17
  return yield if file.nil? and not Lockfile === options[:lock]
@@ -47,7 +55,6 @@ module Open
47
55
  begin
48
56
  if lockfile.locked?
49
57
  lockfile.unlock
50
- else
51
58
  end
52
59
  rescue Exception
53
60
  Log.warn "Exception unlocking: #{lockfile.path}"
@@ -23,7 +23,11 @@ module Open
23
23
  m = file.match(/ssh:\/\/([^:]+):(.*)/)
24
24
  server = m[1]
25
25
  file = m[2]
26
- CMD.cmd("ssh '#{server}' cat '#{file}'", :pipe => true, :autojoin => true)
26
+ if server == 'localhost'
27
+ Open.open(file)
28
+ else
29
+ CMD.cmd("ssh '#{server}' cat '#{file}'", :pipe => true, :autojoin => true)
30
+ end
27
31
  end
28
32
 
29
33
  def self.wget(url, options = {})
@@ -81,6 +85,10 @@ module Open
81
85
  end
82
86
  end
83
87
 
88
+ def self.download(url, file)
89
+ CMD.cmd_log(:wget, "'#{url}' -O '#{file}'")
90
+ end
91
+
84
92
  def self.digest_url(url, options = {})
85
93
  params = [url, options.values_at("--post-data", "--post-data="), (options.include?("--post-file")? Open.read(options["--post-file"]).split("\n").sort * "\n" : "")]
86
94
  Misc.digest([url, params])
@@ -117,4 +125,11 @@ module Open
117
125
  filename = cache_file(url, options)
118
126
  Open.open(filename)
119
127
  end
128
+
129
+ def self.scp(source_file, target_file, target: nil, source: nil)
130
+ CMD.cmd_log("ssh #{target} mkdir -p #{File.dirname(target_file)}")
131
+ target_file = [target, target_file] * ":" if target && ! target_file.start_with?(target+":")
132
+ source_file = [source, source_file] * ":" if source && ! source_file.start_with?(source+":")
133
+ CMD.cmd_log("scp -r '#{ source_file }' #{target_file}")
134
+ end
120
135
  end