rrrspec-client 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1091 @@
1
+ require 'fileutils'
2
+ require 'uuidtools'
3
+
4
+ module RRRSpec
5
+ def self.convert_if_present(h, key)
6
+ if h[key].present?
7
+ h[key] = yield h[key]
8
+ else
9
+ h[key] = nil
10
+ end
11
+ end
12
+
13
+ module RSyncInfo
14
+ WAIT_SERVER_SEC = 10
15
+ RSYNC_INFO_KEY = 'rrrspec:rsync_info'
16
+
17
+ module_function
18
+
19
+ def self.hget_loop(key)
20
+ loop do
21
+ v = RRRSpec.redis.hget(RSYNC_INFO_KEY, key)
22
+ return v if v.present?
23
+ sleep WAIT_SERVER_SEC
24
+ end
25
+ end
26
+
27
+ # Public
28
+ def self.rsync_server
29
+ hget_loop('rsync_server')
30
+ end
31
+
32
+ # Public
33
+ def self.rsync_dir
34
+ hget_loop('rsync_dir')
35
+ end
36
+
37
+ # Public
38
+ def self.rsync_options
39
+ hget_loop('rsync_options')
40
+ end
41
+
42
+ # ==========================================================================
43
+ # Heartbeat
44
+
45
+ # Public: Check its existence.
46
+ #
47
+ # Returns bool
48
+ def self.exist?
49
+ RRRSpec.redis.exists('rrrspec:rsync_info')
50
+ end
51
+
52
+ # Public: Maintain heartbeat
53
+ def self.heartbeat(time)
54
+ unless Dir.exist?(RRRSpec.configuration.rsync_dir)
55
+ FileUtils.makedirs(RRRSpec.configuration.rsync_dir)
56
+ end
57
+ RRRSpec.redis.multi do
58
+ RRRSpec.redis.hmset(
59
+ 'rrrspec:rsync_info',
60
+ 'rsync_server', RRRSpec.configuration.rsync_server,
61
+ 'rsync_dir', RRRSpec.configuration.rsync_dir,
62
+ 'rsync_options', RRRSpec.configuration.rsync_options
63
+ )
64
+ RRRSpec.redis.expire('rrrspec:rsync_info', time)
65
+ end
66
+ end
67
+ end
68
+
69
+ module ArbiterQueue
70
+ ARBITER_QUEUE_KEY = 'rrrspec:arbiter_queue'
71
+
72
+ # Public: Cancel the taskset.
73
+ def self.cancel(taskset)
74
+ RRRSpec.redis.rpush(ARBITER_QUEUE_KEY, "cancel\t#{taskset.key}")
75
+ end
76
+
77
+ # Public: Mark taskset failed
78
+ def self.fail(taskset)
79
+ RRRSpec.redis.rpush(ARBITER_QUEUE_KEY, "fail\t#{taskset.key}")
80
+ end
81
+
82
+ # Public: Check if there is no tasks left in the taskset.
83
+ def self.check(taskset)
84
+ RRRSpec.redis.rpush(ARBITER_QUEUE_KEY, "check\t#{taskset.key}")
85
+ end
86
+
87
+ # Public: Update the status of the task based on the result of the trial.
88
+ def self.trial(trial)
89
+ RRRSpec.redis.rpush(ARBITER_QUEUE_KEY, "trial\t#{trial.key}")
90
+ end
91
+
92
+ # Public: Dequeue the task.
93
+ #
94
+ # Returns [command_name, arg]
95
+ def self.dequeue
96
+ _, line = RRRSpec.redis.blpop(ARBITER_QUEUE_KEY, 0)
97
+ command, arg = line.split("\t", 2)
98
+ case command
99
+ when 'cancel', 'check', 'fail'
100
+ arg = Taskset.new(arg)
101
+ when 'trial'
102
+ arg = Trial.new(arg)
103
+ else
104
+ raise 'Unknown command'
105
+ end
106
+ return command, arg
107
+ end
108
+ end
109
+
110
+ module DispatcherQueue
111
+ DISPATCHER_QUEUE_KEY = 'rrrspec:dispatcher_queue'
112
+ DEFAULT_TIMEOUT = 10
113
+
114
+ # Public: Check the active tasksets and dispatch them
115
+ def self.notify
116
+ RRRSpec.redis.rpush(DISPATCHER_QUEUE_KEY, 0)
117
+ end
118
+
119
+ # Public: Wait for the check command
120
+ def self.wait(timeout=DEFAULT_TIMEOUT)
121
+ RRRSpec.redis.blpop(DISPATCHER_QUEUE_KEY, timeout)
122
+ end
123
+ end
124
+
125
+ module PersisterQueue
126
+ PERSISTER_QUEUE_KEY = 'rrrspec:persister_queue'
127
+
128
+ module_function
129
+
130
+ # Public: Request the taskset to be persisted.
131
+ def enqueue(taskset)
132
+ RRRSpec.redis.rpush(PERSISTER_QUEUE_KEY, taskset.key)
133
+ end
134
+
135
+ # Public: Wait for the persistence request.
136
+ def dequeue
137
+ _, line = RRRSpec.redis.blpop(PERSISTER_QUEUE_KEY, 0)
138
+ Taskset.new(line)
139
+ end
140
+
141
+ def empty?
142
+ RRRSpec.redis.llen(PERSISTER_QUEUE_KEY) == 0
143
+ end
144
+ end
145
+
146
+ module ActiveTaskset
147
+ ACTIVE_TASKSET_KEY = 'rrrspec:active_taskset'
148
+
149
+ # Public: Add the taskset to the active tasksets
150
+ def self.add(taskset)
151
+ RRRSpec.redis.rpush(ACTIVE_TASKSET_KEY, taskset.key)
152
+ end
153
+
154
+ # Public: Remove the taskset from the active tasksets
155
+ def self.remove(taskset)
156
+ RRRSpec.redis.lrem(ACTIVE_TASKSET_KEY, 0, taskset.key)
157
+ end
158
+
159
+ # Public: Returns an array of the active tasksets.
160
+ def self.list
161
+ RRRSpec.redis.lrange(ACTIVE_TASKSET_KEY, 0, -1).map do |key|
162
+ Taskset.new(key)
163
+ end
164
+ end
165
+
166
+ # Public: Returns an array of the active tasksets whose rsync name is
167
+ # specified one.
168
+ def self.all_tasksets_of(rsync_name)
169
+ list.select { |taskset| taskset.rsync_name == rsync_name }
170
+ end
171
+ end
172
+
173
+ class Taskset
174
+ attr_reader :key
175
+
176
+ def initialize(taskset_key)
177
+ @key = taskset_key
178
+ end
179
+
180
+ # Public: Create a new taskset.
181
+ # NOTE: This method will **NOT** call ActiveTaskset.add.
182
+ def self.create(rsync_name, setup_command, slave_command, worker_type,
183
+ taskset_class, max_workers, max_trials,
184
+ unknown_spec_timeout_sec, least_timeout_sec)
185
+ now = Time.zone.now
186
+ # For the reasons unknown, UUIDTools::UUID.timestamp_create changes 'now'.
187
+ taskset_key = RRRSpec.make_key(
188
+ 'rrrspec', 'taskset', UUIDTools::UUID.timestamp_create(now.dup)
189
+ )
190
+ RRRSpec.redis.hmset(
191
+ taskset_key,
192
+ 'rsync_name', rsync_name,
193
+ 'setup_command', setup_command,
194
+ 'slave_command', slave_command,
195
+ 'worker_type', worker_type,
196
+ 'max_workers', max_workers,
197
+ 'max_trials', max_trials,
198
+ 'taskset_class', taskset_class,
199
+ 'unknown_spec_timeout_sec', unknown_spec_timeout_sec.to_s,
200
+ 'least_timeout_sec', least_timeout_sec.to_s,
201
+ 'created_at', now.to_s,
202
+ )
203
+ return new(taskset_key)
204
+ end
205
+
206
+ def ==(other)
207
+ @key == other.key
208
+ end
209
+
210
+ def exist?
211
+ RRRSpec.redis.exists(key)
212
+ end
213
+
214
+ def persisted?
215
+ RRRSpec.redis.ttl(key) != -1
216
+ end
217
+
218
+ def cancel
219
+ ArbiterQueue.cancel(self)
220
+ end
221
+
222
+ # ==========================================================================
223
+ # Property
224
+
225
+ # Public: The path name that is used in rsync
226
+ #
227
+ # Returns string
228
+ def rsync_name
229
+ RRRSpec.redis.hget(key, 'rsync_name')
230
+ end
231
+
232
+ # Public: The command used in setup
233
+ #
234
+ # Returns string
235
+ def setup_command
236
+ RRRSpec.redis.hget(key, 'setup_command')
237
+ end
238
+
239
+ # Public: The command that invokes rrrspec slave
240
+ #
241
+ # Returns string
242
+ def slave_command
243
+ RRRSpec.redis.hget(key, 'slave_command')
244
+ end
245
+
246
+ # Public: Type of the worker required to run the specs
247
+ #
248
+ # Returns string
249
+ def worker_type
250
+ RRRSpec.redis.hget(key, 'worker_type')
251
+ end
252
+
253
+ # Public: The number of workers that is used to run the specs
254
+ #
255
+ # Returns number
256
+ def max_workers
257
+ RRRSpec.redis.hget(key, 'max_workers').to_i
258
+ end
259
+
260
+ # Public: The number of trials that should be made.
261
+ #
262
+ # Returns number
263
+ def max_trials
264
+ RRRSpec.redis.hget(key, 'max_trials').to_i
265
+ end
266
+
267
+ # Public: A value that identifies the same taskset.
268
+ #
269
+ # Returns string
270
+ def taskset_class
271
+ RRRSpec.redis.hget(key, 'taskset_class')
272
+ end
273
+
274
+ # Public: The timeout sec for unknown spec files.
275
+ #
276
+ # Returns number
277
+ def unknown_spec_timeout_sec
278
+ RRRSpec.redis.hget(key, 'unknown_spec_timeout_sec').to_i
279
+ end
280
+
281
+ # Public: Timeout sec at least any specs should wait.
282
+ #
283
+ # Returns number
284
+ def least_timeout_sec
285
+ RRRSpec.redis.hget(key, 'least_timeout_sec').to_i
286
+ end
287
+
288
+ # Public: Returns the created_at
289
+ #
290
+ # Returns Time
291
+ def created_at
292
+ v = RRRSpec.redis.hget(key, 'created_at')
293
+ v.present? ? Time.zone.parse(v) : nil
294
+ end
295
+
296
+ # ==========================================================================
297
+ # WorkerLogs
298
+
299
+ # Public: Add a worker log
300
+ def add_worker_log(worker_log)
301
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'worker_log'),
302
+ worker_log.key)
303
+ end
304
+
305
+ # Public: Return an array of worker_logs
306
+ def worker_logs
307
+ RRRSpec.redis.lrange(RRRSpec.make_key(key, 'worker_log'), 0, -1).map do |key|
308
+ WorkerLog.new(key)
309
+ end
310
+ end
311
+
312
+ # ==========================================================================
313
+ # Slaves
314
+
315
+ # Public: Add a slave
316
+ def add_slave(slave)
317
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'slave'),
318
+ slave.key)
319
+ end
320
+
321
+ # Public: Return an array of slaves
322
+ def slaves
323
+ RRRSpec.redis.lrange(RRRSpec.make_key(key, 'slave'), 0, -1).map do |key|
324
+ Slave.new(key)
325
+ end
326
+ end
327
+
328
+ # ==========================================================================
329
+ # Tasks
330
+
331
+ # Public: Add a task.
332
+ # NOTE: This method does **NOT** enqueue to the task_queue
333
+ def add_task(task)
334
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'tasks'), task.key)
335
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'tasks_left'), task.key)
336
+ end
337
+
338
+ # Public: Finish the task. It is no longer appeared in the `tasks_left`.
339
+ def finish_task(task)
340
+ RRRSpec.redis.lrem(RRRSpec.make_key(key, 'tasks_left'), 0, task.key)
341
+ end
342
+
343
+ # Public: All the tasks that are contained by the taskset.
344
+ #
345
+ # Returns an array of the task instances
346
+ def tasks
347
+ RRRSpec.redis.lrange(RRRSpec.make_key(key, 'tasks'), 0, -1).map do |key|
348
+ Task.new(key)
349
+ end
350
+ end
351
+
352
+ # Public: Size of all tasks.
353
+ def task_size
354
+ RRRSpec.redis.llen(RRRSpec.make_key(key, 'tasks')).to_i
355
+ end
356
+
357
+ # Public: All the tasks that are not migrated into the persistent store.
358
+ # In short, the tasks that are `add_task`ed but not `finish_task`ed.
359
+ #
360
+ # Returns an array of the task instances.
361
+ def tasks_left
362
+ RRRSpec.redis.lrange(RRRSpec.make_key(key, 'tasks_left'), 0, -1).map do |key|
363
+ Task.new(key)
364
+ end
365
+ end
366
+
367
+ # Public: Enqueue the task to the task_queue.
368
+ def enqueue_task(task)
369
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'task_queue'), task.key)
370
+ end
371
+
372
+ # Public: Enqueue the task in the reversed way.
373
+ def reversed_enqueue_task(task)
374
+ RRRSpec.redis.lpush(RRRSpec.make_key(key, 'task_queue'), task.key)
375
+ end
376
+
377
+ # Public: Dequeue the task from the task_queue.
378
+ #
379
+ # Returns a task or nil if timeouts
380
+ def dequeue_task(timeout)
381
+ if timeout < 0
382
+ task_key = RRRSpec.redis.lpop(RRRSpec.make_key(key, 'task_queue'))
383
+ else
384
+ _, task_key = RRRSpec.redis.blpop(RRRSpec.make_key(key, 'task_queue'), timeout)
385
+ end
386
+ return nil unless task_key
387
+ Task.new(task_key)
388
+ end
389
+
390
+ # Public: Remove all the tasks enqueued to the task_queue.
391
+ def clear_queue
392
+ RRRSpec.redis.del(RRRSpec.make_key(key, 'task_queue'))
393
+ end
394
+
395
+ # Public: Checks whether the task_queue is empty.
396
+ def queue_empty?
397
+ RRRSpec.redis.llen(RRRSpec.make_key(key, 'task_queue')) == 0
398
+ end
399
+
400
+ # ==========================================================================
401
+ # Status
402
+
403
+ # Public: Current status
404
+ #
405
+ # Returns either nil, "running", "succeeded", "cancelled" or "failed"
406
+ def status
407
+ RRRSpec.redis.hget(key, 'status')
408
+ end
409
+
410
+ # Public: Update the status. It should be one of:
411
+ # ["running", "succeeded", "cancelled", "failed"]
412
+ def update_status(status)
413
+ RRRSpec.redis.hset(key, 'status', status)
414
+ end
415
+
416
+ # Public: Current succeeded task count. A task is counted as succeeded one
417
+ # if its status is "passed" or "pending".
418
+ #
419
+ # Returns a number
420
+ def succeeded_count
421
+ RRRSpec.redis.hget(key, 'succeeded_count').to_i
422
+ end
423
+
424
+ # Public: Increment succeeded_count
425
+ def incr_succeeded_count
426
+ RRRSpec.redis.hincrby(key, 'succeeded_count', 1)
427
+ end
428
+
429
+ # Public: Current failed task count. A task is counted as failed one if its
430
+ # status is "failed".
431
+ #
432
+ # Returns a number
433
+ def failed_count
434
+ RRRSpec.redis.hget(key, 'failed_count').to_i
435
+ end
436
+
437
+ # Public: Increment failed_count
438
+ def incr_failed_count
439
+ RRRSpec.redis.hincrby(key, 'failed_count', 1)
440
+ end
441
+
442
+ # Public: Returns the finished_at
443
+ def finished_at
444
+ v = RRRSpec.redis.hget(key, 'finished_at')
445
+ v.present? ? Time.zone.parse(v) : nil
446
+ end
447
+
448
+ # Public: Set finished_at time if it is empty
449
+ def set_finished_time
450
+ RRRSpec.redis.hsetnx(key, 'finished_at', Time.zone.now.to_s)
451
+ end
452
+
453
+ # Public: Overall logs of the taskset
454
+ def log
455
+ RRRSpec.redis.get(RRRSpec.make_key(key, 'log')) || ""
456
+ end
457
+
458
+ # Public: Append a line to the log
459
+ def append_log(string)
460
+ RRRSpec.redis.append(RRRSpec.make_key(key, 'log'), string)
461
+ end
462
+
463
+ # ==========================================================================
464
+ # Serialize
465
+
466
+ def to_h
467
+ h = RRRSpec.redis.hgetall(key)
468
+ h['key'] = key
469
+ h['log'] = log
470
+ h['tasks'] = tasks.map { |task| { 'key' => task.key } }
471
+ h['slaves'] = slaves.map { |slave| { 'key' => slave.key } }
472
+ h['worker_logs'] = worker_logs.map { |worker_log| { 'key' => worker_log.key } }
473
+ RRRSpec.convert_if_present(h, 'max_workers') { |v| v.to_i }
474
+ RRRSpec.convert_if_present(h, 'max_trials') { |v| v.to_i }
475
+ RRRSpec.convert_if_present(h, 'unknown_spec_timeout_sec') { |v| v.to_i }
476
+ RRRSpec.convert_if_present(h, 'least_timeout_sec') { |v| v.to_i }
477
+ RRRSpec.convert_if_present(h, 'created_at') { |v| Time.zone.parse(v) }
478
+ RRRSpec.convert_if_present(h, 'finished_at') { |v| Time.zone.parse(v) }
479
+ h.delete('succeeded_count')
480
+ h.delete('failed_count')
481
+ h
482
+ end
483
+
484
+ def to_json(options=nil)
485
+ to_h.to_json(options)
486
+ end
487
+
488
+ # ==========================================================================
489
+ # Persistence
490
+
491
+ def expire(sec)
492
+ tasks.each { |task| task.expire(sec) }
493
+ slaves.each { |slave| slave.expire(sec) }
494
+ worker_logs.each { |worker_log| worker_log.expire(sec) }
495
+ RRRSpec.redis.expire(key, sec)
496
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'log'), sec)
497
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'slave'), sec)
498
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'worker_log'), sec)
499
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'task_queue'), sec)
500
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'tasks'), sec)
501
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'tasks_left'), sec)
502
+ end
503
+ end
504
+
505
+ class WorkerLog
506
+ attr_reader :key
507
+
508
+ def initialize(worker_log_key)
509
+ @key = worker_log_key
510
+ end
511
+
512
+ # Public: Create a new worker_log.
513
+ # This method will call Taskset#add_worker_log
514
+ def self.create(worker, taskset)
515
+ worker_log_key = RRRSpec.make_key(taskset.key, worker.key)
516
+ RRRSpec.redis.hmset(
517
+ worker_log_key,
518
+ 'worker', worker.key,
519
+ 'taskset', taskset.key,
520
+ 'started_at', Time.zone.now.to_s,
521
+ )
522
+ worker_log = new(worker_log_key)
523
+ taskset.add_worker_log(worker_log)
524
+ return worker_log
525
+ end
526
+
527
+ # ==========================================================================
528
+ # Property
529
+
530
+ # Public: Returns the started_at
531
+ def started_at
532
+ v = RRRSpec.redis.hget(key, 'started_at')
533
+ v.present? ? Time.zone.parse(v) : nil
534
+ end
535
+
536
+ # ==========================================================================
537
+ # Status
538
+
539
+ # Public: Returns the rsync_finished_at
540
+ def rsync_finished_at
541
+ v = RRRSpec.redis.hget(key, 'rsync_finished_at')
542
+ v.present? ? Time.zone.parse(v) : nil
543
+ end
544
+
545
+ # Public: Set rsync_finished_at time
546
+ def set_rsync_finished_time
547
+ RRRSpec.redis.hset(key, 'rsync_finished_at', Time.zone.now.to_s)
548
+ end
549
+
550
+ # Public: Returns the setup_finished_at
551
+ def setup_finished_at
552
+ v = RRRSpec.redis.hget(key, 'setup_finished_at')
553
+ v.present? ? Time.zone.parse(v) : nil
554
+ end
555
+
556
+ # Public: Set setup_finished_at time
557
+ def set_setup_finished_time
558
+ RRRSpec.redis.hset(key, 'setup_finished_at', Time.zone.now.to_s)
559
+ end
560
+
561
+ # Public: Returns the finished_at
562
+ def finished_at
563
+ v = RRRSpec.redis.hget(key, 'finished_at')
564
+ v.present? ? Time.zone.parse(v) : nil
565
+ end
566
+
567
+ # Public: Set finished_at time if it is empty
568
+ def set_finished_time
569
+ RRRSpec.redis.hsetnx(key, 'finished_at', Time.zone.now.to_s)
570
+ end
571
+
572
+ # Public: Logs happend in worker
573
+ def log
574
+ RRRSpec.redis.get(RRRSpec.make_key(key, 'log')) || ""
575
+ end
576
+
577
+ # Public: Append a line to the log
578
+ def append_log(string)
579
+ RRRSpec.redis.append(RRRSpec.make_key(key, 'log'), string)
580
+ end
581
+
582
+ # ==========================================================================
583
+ # Serialize
584
+
585
+ def to_h
586
+ h = RRRSpec.redis.hgetall(key)
587
+ h['key'] = key
588
+ h['log'] = log
589
+ h['worker'] = { 'key' => h['worker'] }
590
+ h['taskset'] = { 'key' => h['taskset'] }
591
+ RRRSpec.convert_if_present(h, 'started_at') { |v| Time.zone.parse(v) }
592
+ RRRSpec.convert_if_present(h, 'rsync_finished_at') { |v| Time.zone.parse(v) }
593
+ RRRSpec.convert_if_present(h, 'setup_finished_at') { |v| Time.zone.parse(v) }
594
+ RRRSpec.convert_if_present(h, 'finished_at') { |v| Time.zone.parse(v) }
595
+ h
596
+ end
597
+
598
+ def to_json(options=nil)
599
+ to_h.to_json(options)
600
+ end
601
+
602
+ # ==========================================================================
603
+ # Persistence
604
+
605
+ def expire(sec)
606
+ RRRSpec.redis.expire(key, sec)
607
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'log'), sec)
608
+ end
609
+ end
610
+
611
+ class Task
612
+ attr_reader :key
613
+
614
+ def initialize(task_key)
615
+ @key = task_key
616
+ end
617
+
618
+ def self.create(taskset, estimate_sec, spec_file)
619
+ task_key = RRRSpec.make_key(taskset.key, 'task', spec_file)
620
+ RRRSpec.redis.hmset(
621
+ task_key,
622
+ 'taskset', taskset.key,
623
+ 'estimate_sec', estimate_sec,
624
+ 'spec_file', spec_file
625
+ )
626
+ return new(task_key)
627
+ end
628
+
629
+ def ==(other)
630
+ @key == other.key
631
+ end
632
+
633
+ # ==========================================================================
634
+ # Property
635
+
636
+ # Public: Estimate time to finishe the task.
637
+ #
638
+ # Returns seconds or nil if there is no estimation
639
+ def estimate_sec
640
+ v = RRRSpec.redis.hget(key, 'estimate_sec')
641
+ v.present? ? v.to_i : nil
642
+ end
643
+
644
+ # Public: Spec file to run.
645
+ #
646
+ # Returns a path to the spec
647
+ def spec_file
648
+ RRRSpec.redis.hget(key, 'spec_file')
649
+ end
650
+
651
+ # Public: Included taskset
652
+ #
653
+ # Returns a Taskset
654
+ def taskset
655
+ Taskset.new(RRRSpec.redis.hget(key, 'taskset'))
656
+ end
657
+
658
+ # ==========================================================================
659
+ # Trial
660
+
661
+ # Public: Returns the trials of the task.
662
+ # The return value should be sorted in the order added.
663
+ #
664
+ # Returns an array of the Trials
665
+ def trials
666
+ RRRSpec.redis.lrange(RRRSpec.make_key(key, 'trial'), 0, -1).map do |key|
667
+ Trial.new(key)
668
+ end
669
+ end
670
+
671
+ # Public: Add a trial of the task.
672
+ def add_trial(trial)
673
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'trial'),
674
+ trial.key)
675
+ end
676
+
677
+ # ==========================================================================
678
+ # Status
679
+
680
+ # Public: Current status
681
+ #
682
+ # Returns either nil, "running", "passed", "pending" or "failed"
683
+ def status
684
+ RRRSpec.redis.hget(key, 'status')
685
+ end
686
+
687
+ # Public: Update the status. It should be one of:
688
+ # [nil, "running", "passed", "pending", "failed"]
689
+ def update_status(status)
690
+ if status.present?
691
+ RRRSpec.redis.hset(key, 'status', status)
692
+ else
693
+ RRRSpec.redis.hdel(key, 'status')
694
+ end
695
+ end
696
+
697
+ # ==========================================================================
698
+ # Serialize
699
+
700
+ def to_h
701
+ h = RRRSpec.redis.hgetall(key)
702
+ h['key'] = key
703
+ h['trials'] = trials.map { |trial| { 'key' => trial.key } }
704
+ h['taskset'] = { 'key' => h['taskset'] }
705
+ RRRSpec.convert_if_present(h, 'estimate_sec') { |v| v.to_i }
706
+ h
707
+ end
708
+
709
+ def to_json(options=nil)
710
+ to_h.to_json(options)
711
+ end
712
+
713
+ # ==========================================================================
714
+ # Persistence
715
+
716
+ def expire(sec)
717
+ trials.each { |trial| trial.expire(sec) }
718
+ RRRSpec.redis.expire(key, sec)
719
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'trial'), sec)
720
+ end
721
+ end
722
+
723
+ class Trial
724
+ attr_reader :key
725
+
726
+ def initialize(trial_key)
727
+ @key = trial_key
728
+ end
729
+
730
+ # Public: Create a new trial.
731
+ # This method will call Task#add_trial and Slave#add_trial.
732
+ def self.create(task, slave)
733
+ trial_key = RRRSpec.make_key(
734
+ task.key, 'trial', UUIDTools::UUID.timestamp_create
735
+ )
736
+ RRRSpec.redis.hmset(
737
+ trial_key,
738
+ 'task', task.key,
739
+ 'slave', slave.key,
740
+ )
741
+ trial = new(trial_key)
742
+ task.add_trial(trial)
743
+ slave.add_trial(trial)
744
+ return trial
745
+ end
746
+
747
+ # ==========================================================================
748
+ # Property
749
+
750
+ # Public: Tried task
751
+ #
752
+ # Returns a Task
753
+ def task
754
+ Task.new(RRRSpec.redis.hget(key, 'task'))
755
+ end
756
+
757
+ # Public: The slave worked for this.
758
+ #
759
+ # Returns a Slave
760
+ def slave
761
+ Slave.new(RRRSpec.redis.hget(key, 'slave'))
762
+ end
763
+
764
+ # ==========================================================================
765
+ # Status
766
+
767
+ # Public: Current status
768
+ #
769
+ # Returns either nil, "passed", "pending", "failed" or "error"
770
+ def status
771
+ RRRSpec.redis.hget(key, 'status')
772
+ end
773
+
774
+ # Public: Set started_at time.
775
+ def start
776
+ RRRSpec.redis.hset(key, 'started_at', Time.zone.now.to_s)
777
+ end
778
+
779
+ # Public: Finish the trial
780
+ # status should be one of ["passed", "pending", "failed", "error"].
781
+ # stdout and stderr should be string or nil.
782
+ # passed, pending and failed is the count of examplegroups and should be
783
+ # either nil or numbers.
784
+ def finish(status, stdout, stderr, passed, pending, failed)
785
+ RRRSpec.redis.hmset(
786
+ key,
787
+ 'finished_at', Time.zone.now.to_s,
788
+ 'status', status,
789
+ 'stdout', stdout,
790
+ 'stderr', stderr,
791
+ 'passed', passed,
792
+ 'pending', pending,
793
+ 'failed', failed
794
+ )
795
+ end
796
+
797
+ # Public: Returns the started_at
798
+ def started_at
799
+ v = RRRSpec.redis.hget(key, 'started_at')
800
+ v.present? ? Time.zone.parse(v) : nil
801
+ end
802
+
803
+ # Public: Returns the finished_at
804
+ def finished_at
805
+ v = RRRSpec.redis.hget(key, 'finished_at')
806
+ v.present? ? Time.zone.parse(v) : nil
807
+ end
808
+
809
+ # Public: Returns the stdout
810
+ def stdout
811
+ RRRSpec.redis.hget(key, 'stdout')
812
+ end
813
+
814
+ # Public: Returns the stderr
815
+ def stderr
816
+ RRRSpec.redis.hget(key, 'stderr')
817
+ end
818
+
819
+ # Public: Returns the passed examples
820
+ def passed
821
+ v = RRRSpec.redis.hget(key, 'passed')
822
+ v.present? ? v.to_i : nil
823
+ end
824
+
825
+ # Public: Returns the pending examples
826
+ def pending
827
+ v = RRRSpec.redis.hget(key, 'pending')
828
+ v.present? ? v.to_i : nil
829
+ end
830
+
831
+ # Public: Returns the failed examples
832
+ def failed
833
+ v = RRRSpec.redis.hget(key, 'failed')
834
+ v.present? ? v.to_i : nil
835
+ end
836
+
837
+ # ==========================================================================
838
+ # Serialize
839
+
840
+ def to_h
841
+ h = RRRSpec.redis.hgetall(key)
842
+ h['key'] = key
843
+ h['task'] = { 'key' => h['task'] }
844
+ h['slave'] = { 'key' => h['slave'] }
845
+ RRRSpec.convert_if_present(h, 'started_at') { |v| Time.zone.parse(v) }
846
+ RRRSpec.convert_if_present(h, 'finished_at') { |v| Time.zone.parse(v) }
847
+ RRRSpec.convert_if_present(h, 'passed') { |v| v.to_i }
848
+ RRRSpec.convert_if_present(h, 'pending') { |v| v.to_i }
849
+ RRRSpec.convert_if_present(h, 'failed') { |v| v.to_i }
850
+ h
851
+ end
852
+
853
+ def to_json(options=nil)
854
+ to_h.to_json(options)
855
+ end
856
+
857
+ # ==========================================================================
858
+ # Persistence
859
+
860
+ def expire(sec)
861
+ RRRSpec.redis.expire(key, sec)
862
+ end
863
+ end
864
+
865
+ class Worker
866
+ attr_reader :key
867
+
868
+ def initialize(worker_key)
869
+ @key = worker_key
870
+ end
871
+
872
+ # Public: Create a new worker.
873
+ # The worker returned is **NOT** appeared in Worker.list.
874
+ def self.create(worker_type, hostname=RRRSpec.hostname)
875
+ worker_key = RRRSpec.make_key('rrrspec', 'worker', hostname)
876
+ RRRSpec.redis.hset(worker_key, 'worker_type', worker_type)
877
+
878
+ worker = new(worker_key)
879
+ return worker
880
+ end
881
+
882
+ # Public: A list of the workers which are possibly available.
883
+ #
884
+ # Returns an array of the workers
885
+ def self.list
886
+ RRRSpec.redis.smembers(RRRSpec.make_key('rrrspec', 'worker')).map do |key|
887
+ new(key)
888
+ end
889
+ end
890
+
891
+ # Public: Remove myself from the worker list.
892
+ def evict
893
+ RRRSpec.redis.srem(RRRSpec.make_key('rrrspec', 'worker'), key)
894
+ end
895
+
896
+ def ==(other)
897
+ @key == other.key
898
+ end
899
+
900
+ # ==========================================================================
901
+ # Property
902
+
903
+ # Public: The worker_type
904
+ def worker_type
905
+ RRRSpec.redis.hget(key, 'worker_type')
906
+ end
907
+
908
+ # ==========================================================================
909
+ # Taskset
910
+
911
+ # Public: Current taskset
912
+ #
913
+ # Returns a taskset or nil
914
+ def current_taskset
915
+ taskset_key = RRRSpec.redis.hget(key, 'taskset')
916
+ if taskset_key.present?
917
+ return Taskset.new(taskset_key)
918
+ else
919
+ nil
920
+ end
921
+ end
922
+
923
+ # Public: Update the current taskset
924
+ def update_current_taskset(taskset)
925
+ if taskset.present?
926
+ RRRSpec.redis.hset(key, 'taskset', taskset.key)
927
+ else
928
+ RRRSpec.redis.hset(key, 'taskset', nil)
929
+ end
930
+ end
931
+
932
+ # Public: Enqueue the taskset to the taskset_queue
933
+ def enqueue_taskset(taskset)
934
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'worker_queue'), taskset.key)
935
+ end
936
+
937
+ # Public: Dequeue the taskset from the taskset_queue
938
+ def dequeue_taskset
939
+ _, taskset_key = RRRSpec.redis.blpop(RRRSpec.make_key(key, 'worker_queue'), 0)
940
+ return Taskset.new(taskset_key)
941
+ end
942
+
943
+ # Public: Checks whether the taskset_queue is empty.
944
+ def queue_empty?
945
+ RRRSpec.redis.llen(RRRSpec.make_key(key, 'worker_queue')) == 0
946
+ end
947
+
948
+ # ==========================================================================
949
+ # Heartbeat
950
+
951
+ # Public: Check its existence with heartbeat key.
952
+ #
953
+ # Returns bool
954
+ def exist?
955
+ RRRSpec.redis.exists(RRRSpec.make_key(key, 'heartbeat'))
956
+ end
957
+
958
+ # Public: Maintain heartbeat
959
+ def heartbeat(time)
960
+ RRRSpec.redis.setex(RRRSpec.make_key(key, 'heartbeat'), time, "alive")
961
+ RRRSpec.redis.sadd(RRRSpec.make_key('rrrspec', 'worker'), key)
962
+ end
963
+ end
964
+
965
+ class Slave
966
+ attr_reader :key
967
+
968
+ def initialize(slave_key)
969
+ @key = slave_key
970
+ end
971
+
972
+ def self.create
973
+ slave_key = RRRSpec.make_key('rrrspec', 'worker', RRRSpec.hostname, 'slave', Process.getpgrp)
974
+ slave = new(slave_key)
975
+ return slave
976
+ end
977
+
978
+ def self.build_from_pid(pid)
979
+ slave_key = RRRSpec.make_key('rrrspec', 'worker', RRRSpec.hostname, 'slave', pid)
980
+ return new(slave_key)
981
+ end
982
+
983
+ # ==========================================================================
984
+ # Status
985
+
986
+ # Public: Returns the trials of the slave.
987
+ # The return value should be sorted in the order added.
988
+ #
989
+ # Returns an array of the Trials
990
+ def trials
991
+ RRRSpec.redis.lrange(RRRSpec.make_key(key, 'trial'), 0, -1).map do |key|
992
+ Trial.new(key)
993
+ end
994
+ end
995
+
996
+ # Public: Add trial to the list of the trials that the slave worked for.
997
+ def add_trial(trial)
998
+ RRRSpec.redis.rpush(RRRSpec.make_key(key, 'trial'),
999
+ trial.key)
1000
+ end
1001
+
1002
+ # ==========================================================================
1003
+ # Status
1004
+
1005
+ # Public: Current status
1006
+ #
1007
+ # Returns either nil, "normal_exit", "timeout_exit" or "failure_exit"
1008
+ def status
1009
+ RRRSpec.redis.hget(key, 'status')
1010
+ end
1011
+
1012
+ # Public: Update the status. It should be one of:
1013
+ # ["normal_exit", "timeout_exit", "failure_exit"]
1014
+ def update_status(status)
1015
+ RRRSpec.redis.hset(key, 'status', status)
1016
+ end
1017
+
1018
+ # Public: Execution log of the slave
1019
+ def log
1020
+ RRRSpec.redis.get(RRRSpec.make_key(key, 'log')) || ""
1021
+ end
1022
+
1023
+ # Public: Append a line to the worker_log
1024
+ def append_log(string)
1025
+ RRRSpec.redis.append(RRRSpec.make_key(key, 'log'), string)
1026
+ end
1027
+
1028
+ # ==========================================================================
1029
+ # Heartbeat
1030
+
1031
+ # Public: Check its existence with heartbeat key.
1032
+ #
1033
+ # Returns bool
1034
+ def exist?
1035
+ RRRSpec.redis.exists(RRRSpec.make_key(key, 'heartbeat'))
1036
+ end
1037
+
1038
+ # Public: Maintain heartbeat
1039
+ def heartbeat(time)
1040
+ RRRSpec.redis.setex(RRRSpec.make_key(key, 'heartbeat'), time, "alive")
1041
+ end
1042
+
1043
+ # ==========================================================================
1044
+ # Serialize
1045
+
1046
+ def to_h
1047
+ h = RRRSpec.redis.hgetall(key)
1048
+ h['trials'] = trials.map { |trial| { 'key' => trial.key } }
1049
+ h['key'] = key
1050
+ h['log'] = log
1051
+ h
1052
+ end
1053
+
1054
+ def to_json(options=nil)
1055
+ to_h.to_json(options)
1056
+ end
1057
+
1058
+ # ==========================================================================
1059
+ # Persistence
1060
+
1061
+ def expire(sec)
1062
+ RRRSpec.redis.expire(key, sec)
1063
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'trial'), sec)
1064
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'log'), sec)
1065
+ RRRSpec.redis.expire(RRRSpec.make_key(key, 'heartbeat'), sec)
1066
+ end
1067
+ end
1068
+
1069
+ module TasksetEstimation
1070
+ # Public: Return the cache on the estimated execution time of the specs.
1071
+ #
1072
+ # Returns a hash of spec_file to estimate_sec
1073
+ def self.estimate_secs(taskset_class)
1074
+ h = RRRSpec.redis.hgetall(RRRSpec.make_key('rrrspec', 'estimate_sec', taskset_class))
1075
+ estimate_secs = {}
1076
+ h.each do |spec_file, estimate_sec|
1077
+ estimate_secs[spec_file] = estimate_sec.to_i
1078
+ end
1079
+ return estimate_secs
1080
+ end
1081
+
1082
+ # Public: Update the estimation.
1083
+ #
1084
+ # The estimation argument should be a hash like {"spec_file" => 20}.
1085
+ def self.update_estimate_secs(taskset_class, estimation)
1086
+ return if estimation.empty?
1087
+ key = RRRSpec.make_key('rrrspec', 'estimate_sec', taskset_class)
1088
+ RRRSpec.redis.hmset(key, *estimation.to_a.flatten)
1089
+ end
1090
+ end
1091
+ end