rrrspec-client 0.2.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.
@@ -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