flor 1.0.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -109,15 +109,13 @@ module Flor
109
109
  rescue Exception => exc
110
110
 
111
111
  # TODO eventually, have a dump dir
112
+
112
113
  fn =
113
- [
114
- 'flor',
115
- @unit.conf['env'], @unit.identifier, @exid,
116
- 'r' + counter('runs').to_s
117
- ].collect(&:to_s).join('_') + '.dump'
114
+ [ 'flor', @unit.conf['env'], @unit.identifier, @exid,
115
+ 'r' + counter('runs').to_s ].collect(&:to_s).join('_') + '.dump'
118
116
 
119
117
  @unit.logger.error(
120
- "#{self.class}#do_run()", exc, "(dumping to #{fn})")
118
+ "#{self.class}#do_run()", exc, "(dumping to #{fn} ...)")
121
119
 
122
120
  File.open(fn, 'wb') do |f|
123
121
  f.puts(Flor.to_pretty_s({
@@ -134,19 +132,26 @@ module Flor
134
132
  f.puts(on_do_run_exc(exc))
135
133
  end
136
134
 
135
+ @unit.logger.error(
136
+ "#{self.class}#do_run()", exc, "(dumped to #{fn})")
137
+
137
138
  #puts on_do_run_exc(exc)
138
139
  # dump notification above
139
140
  end
140
141
 
141
142
  def task(message)
142
143
 
143
- return error_reply(
144
- node(message['nid']),
145
- message,
146
- "don't know how to apply #{message['tasker'].inspect}"
147
- ) if message['routed'] == false
148
- #
149
- # use an error message similar to what the core executor would emit
144
+ if message['routed'] == false
145
+
146
+ t = message['tasker']
147
+ n = node(message['nid'])
148
+
149
+ msg = n['heat0'] != t ?
150
+ "tasker #{t.inspect} not found" :
151
+ "don't know how to apply #{t.inspect}"
152
+
153
+ return error_reply(n, message, msg)
154
+ end
150
155
 
151
156
  @unit.ganger.task(self, message)
152
157
  end
@@ -54,6 +54,9 @@ module Flor
54
54
  (@unit.loader.tasker(domain, 'ganger', message) ||
55
55
  @unit.loader.tasker(domain, 'tasker', message))) ||
56
56
  @unit.loader.tasker(domain, tname, message)
57
+ #puts "=" * 80
58
+ #pp tconf
59
+ #puts "=" * 80
57
60
 
58
61
  fail ArgumentError.new(
59
62
  "tasker #{tname.inspect} not found"
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flor
4
+
5
+ # A ModuleGanger accepts a `module:` conf entry that points to a Ruby
6
+ # module. The tasker implementations are searched for among the classes
7
+ # in the given module.
8
+ #
9
+ # Among the tasker classes (classes that respond to on_task, on_detask, ...)
10
+ # it selects the first tasker that matches the tasker name.
11
+ #
12
+ class ModuleGanger
13
+
14
+ def initialize(service, conf, message)
15
+
16
+ @service = service
17
+ @conf = conf
18
+ @message = message
19
+ end
20
+
21
+ def task
22
+
23
+ tas = @message['tasker']
24
+ clas = list_tasker_classes
25
+ cla = clas.find { |c| tasker_name(c) == tas }
26
+
27
+ return [ Flor.dup_and_merge(@message, 'routed' => false) ] \
28
+ unless cla
29
+
30
+ call_tasker(cla)
31
+ end
32
+
33
+ alias detask task
34
+
35
+ protected
36
+
37
+ def list_tasker_classes
38
+
39
+ mod_name = @conf['module']
40
+
41
+ fail ArgumentError.new('ganger module: configuration entry missing') \
42
+ unless mod_name
43
+
44
+ mod = Flor.const_lookup(mod_name) rescue nil
45
+
46
+ fail ArgumentError.new("ganger cannot find module #{mod_name.inspect}") \
47
+ unless mod
48
+
49
+ list_classes(mod, [])
50
+ end
51
+
52
+ def list_classes(start, r)
53
+
54
+ # place leave classes on top if possible
55
+ # within a level, sort alphabetically
56
+
57
+ clas = start.constants.collect { |co| start.const_get(co) }
58
+ clas, mods = clas.partition { |c| c.is_a?(Class) }
59
+
60
+ mods.each { |m| list_classes(m, r) }
61
+ r.concat(clas.select { |c| tasker?(c) }.sort_by { |c| c.name })
62
+
63
+ r
64
+ end
65
+
66
+ TASKER_METHODS = [
67
+ :on, :on_message,
68
+ :task, :on_task,
69
+ :detask, :on_detask, :cancel, :on_cancel
70
+ ].freeze
71
+
72
+ def tasker?(cla)
73
+
74
+ return true if (TASKER_METHODS & cla.public_instance_methods).any?
75
+ return true if (TASKER_METHODS & cla.public_methods).any?
76
+ false
77
+ end
78
+
79
+ def tasker_name(cla)
80
+
81
+ if cla.public_instance_methods.include?(:tasker_name)
82
+
83
+ unless cla.respond_to?(:_ganged)
84
+ class << cla
85
+ attr_accessor :_ganged
86
+ end
87
+ cla._ganged = cla.allocate
88
+ end
89
+
90
+ call_tasker_name(cla._ganged)
91
+
92
+ elsif cla.public_methods.include?(:tasker_name)
93
+
94
+ call_tasker_name(cla)
95
+
96
+ else
97
+
98
+ cla.name.split('::').last.gsub(/Tasker\z/, '')
99
+ .gsub(/([a-z])([A-Z])/) { |_| $1 + '_' + $2.downcase }
100
+ .gsub(/([A-Z])/) { |c| c.downcase }
101
+ end
102
+ end
103
+
104
+ def call_tasker_name(o)
105
+
106
+ case i = o.method(:tasker_name).arity
107
+ when 1 then o.tasker_name(@message)
108
+ when 2 then o.tasker_name(@conf, @message)
109
+ when 3 then o.tasker_name(@service, @conf, @message)
110
+ when -1 then o.tasker_name(
111
+ service: @service, conf: @conf, message: @message)
112
+ else o.tasker_name
113
+ end
114
+ end
115
+
116
+ def call_tasker(c)
117
+
118
+ cnf = @conf.merge('class' => c)
119
+
120
+ @service.unit.caller
121
+ .call(@service, cnf, @message)
122
+ end
123
+ end
124
+ end
125
+
@@ -46,7 +46,7 @@ module Flor
46
46
  .collect { |pa| [ pa, expose_d(pa, {}) ] }
47
47
  .select { |pa, d| Flor.sub_domain?(d, domain) }
48
48
  .sort_by { |pa, d| d.count('.') }
49
- .inject({}) { |vars, (pa, _)| vars.merge!(eval(pa, {})) }
49
+ .inject({}) { |vars, (pa, _)| vars.merge!(eval_variables(pa, {})) }
50
50
  end
51
51
 
52
52
  #def procedures(path)
@@ -87,25 +87,28 @@ module Flor
87
87
  .select { |pa| pa.index('/lib/taskers/') }
88
88
  .collect { |pa| [ pa, *expose_dn(pa, {}) ] }
89
89
  .select { |pa, d, n|
90
- Flor.sub_domain?([ d, n ].join('.'), domain) ||
90
+ Flor.sub_domain?([ d, n ], domain) ||
91
91
  (n == name && Flor.sub_domain?(d, domain)) }
92
92
  .sort_by { |pa, d, n| d.count('.') }
93
93
  .last
94
94
 
95
95
  return nil unless pat
96
96
 
97
- conf = eval(pat, message)
97
+ conf = eval_tasker_conf(pat, message)
98
98
 
99
99
  return conf if nam == name
100
100
 
101
- conf = conf[name]
101
+ cnf = conf[name]
102
102
 
103
- return nil unless conf
103
+ return nil unless cnf
104
104
 
105
- (conf.is_a?(Array) ? conf : [ conf ])
106
- .each { |h| h['_path'] = pat }
105
+ extras = conf.select { |_, v| ! v.is_a?(Hash) }
106
+ extras['_path'] = pat
107
107
 
108
- conf
108
+ (cnf.is_a?(Array) ? cnf : [ cnf ])
109
+ .each { |h| h.merge!(extras) }
110
+
111
+ cnf
109
112
  end
110
113
 
111
114
  def hooks(domain)
@@ -119,8 +122,8 @@ module Flor
119
122
  .select { |pa, d| Flor.sub_domain?(d, domain) }
120
123
  .sort_by { |pa, d| d.count('.') }
121
124
  .collect { |pa, d|
122
- eval(pa, {})
123
- .each_with_index { |h, i| h['_path'] = pa + ":#{i}" } }
125
+ eval_hook_conf(pa, {})
126
+ .each_with_index { |h, i| h['_path'] = "#{pa}:#{i}" } }
124
127
  .flatten(1)
125
128
  end
126
129
 
@@ -247,6 +250,35 @@ module Flor
247
250
  end
248
251
  end
249
252
 
253
+ def eval_variables(path, context)
254
+ eval(path, context)
255
+ end
256
+ def eval_tasker_conf(path, context)
257
+ eval(path, context)
258
+ end
259
+ # TODO like in eval_hook_conf, reject fautly tasker confs...
260
+ # TODO like in eval_hook_conf, reject fautly variables...
261
+
262
+ def eval_hook_conf(path, context)
263
+
264
+ a = eval(path, context)
265
+
266
+ fail ArgumentError.new(
267
+ "hook conf at #{path} must be an array of hashes"
268
+ ) unless a.is_a?(Array)
269
+
270
+ a.each do |e|
271
+ fail ArgumentError.new(
272
+ "hook conf at #{path} has non-hash entry #{e.inspect}"
273
+ ) unless e.is_a?(Hash)
274
+ fail ArgumentError.new(
275
+ "hook conf at #{path} has incorrect point #{e['point'].inspect}"
276
+ ) unless e['point'].is_a?(String)
277
+ end
278
+
279
+ a
280
+ end
281
+
250
282
  def eval(path, context)
251
283
 
252
284
  ext =
@@ -71,7 +71,8 @@ module Flor
71
71
  dbi = ' ' + dbi if dbi.length > 0
72
72
 
73
73
  txt = elts.collect(&:to_s).join(' ')
74
- err = elts.find { |e| e.is_a?(Exception) }
74
+
75
+ err = find_err(elts)
75
76
 
76
77
  head = "#{stp} #{@uni}#{dbi} #{lvl} "
77
78
 
@@ -198,7 +199,8 @@ module Flor
198
199
 
199
200
  return unless @unit.conf['log_err']
200
201
 
201
- @out.puts(Flor.msg_to_detail_s(executor, message, opts.merge(flag: true)))
202
+ s = Flor.msg_to_detail_s(executor, message, opts.merge(flag: true))
203
+ @out.puts(s) if s
202
204
  end
203
205
 
204
206
  def log_src(source, opts, log_opts={})
@@ -245,6 +247,13 @@ module Flor
245
247
  message[0..k + 2 + 4] + "(...len#{i - (k + 2 + 1)})" + message[i..-1]
246
248
  end
247
249
 
250
+ def find_err(elts)
251
+
252
+ elts.find { |e| e.is_a?(Exception) } ||
253
+ (defined?(Java) &&
254
+ elts.find { |e| e.class.ancestors.include?(Java::JavaLang::Error) })
255
+ end
256
+
248
257
  class Out
249
258
 
250
259
  attr_reader :unit
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  module Flor
4
5
 
5
6
  class Message < FlorModel
@@ -19,6 +20,10 @@ module Flor
19
20
  #
20
21
  # index :exid
21
22
  #end
23
+
24
+ def nid; data['nid']; end
25
+ def tasker; data['tasker']; end
26
+ alias payload data
22
27
  end
23
28
  end
24
29
 
@@ -30,7 +30,7 @@ module Flor
30
30
  #end
31
31
 
32
32
  # If the pointer is a "var" pointer, returns the full value
33
- # for the variable, as fund in the execution's node "0".
33
+ # for the variable, as found in the execution's node "0".
34
34
  #
35
35
  def full_value
36
36
 
@@ -38,6 +38,25 @@ module Flor
38
38
 
39
39
  node['vars'][name]
40
40
  end
41
+
42
+ def attd
43
+
44
+ data['atts'].inject({}) { |h, (k, v)| h[k] = v if k; h }
45
+
46
+ rescue; []
47
+ end
48
+
49
+ def attl
50
+
51
+ data['atts'].inject([]) { |a, (k, v)| a << v if k == nil; a }
52
+
53
+ rescue; []
54
+ end
55
+
56
+ def att_texts
57
+
58
+ attl.select { |e| e.is_a?(String) }
59
+ end
41
60
  end
42
61
  end
43
62
 
@@ -138,11 +138,7 @@ module Flor
138
138
 
139
139
  # TODO heartbeat, every x minutes, when idle, log something
140
140
 
141
- fail(
142
- "database not ready, " +
143
- "db ver: #{@storage.db_version.inspect}, " +
144
- "mig ver: #{@storage.migration_version}"
145
- ) if !! @conf['sto_migration_check'] && @storage.ready?
141
+ check_migration_version
146
142
 
147
143
  @thread_status = :running
148
144
 
@@ -162,6 +158,15 @@ module Flor
162
158
  self
163
159
  end
164
160
 
161
+ def check_migration_version
162
+
163
+ fail(
164
+ "database not ready, " +
165
+ "db ver: #{@storage.db_version.inspect}, " +
166
+ "mig ver: #{@storage.migration_version}"
167
+ ) if !! @conf['sto_migration_check'] && @storage.ready?
168
+ end
169
+
165
170
  def stop
166
171
 
167
172
  @thread_status = :stop
@@ -227,6 +232,9 @@ module Flor
227
232
  @last_queued_message_id =
228
233
  @storage.put_message(message)
229
234
 
235
+ # Nota bene:
236
+ # the #wait method is added to the Scheduler by Flor::WaitList
237
+
230
238
  if opts[:wait]
231
239
  wait(message['exid'], opts)
232
240
  else
@@ -587,7 +595,7 @@ module Flor
587
595
 
588
596
  rescue Exception => ex
589
597
 
590
- puts on_start_exc(ex)
598
+ puts(on_start_exc(ex))
591
599
  end
592
600
 
593
601
  def prepare_message(point, args)
@@ -675,9 +683,9 @@ module Flor
675
683
  def should_wake_up?
676
684
 
677
685
  return true if @wake_up
678
- return true if Time.now - @reloaded_at >= reload_after
686
+ return true if (Time.now - @reloaded_at) >= reload_after
679
687
 
680
- @next_time && @next_time <= Flor.tstamp.split('.').first
688
+ @next_time && (@next_time <= Flor.tstam)
681
689
  end
682
690
 
683
691
  def unreserve_messages
@@ -7,6 +7,14 @@ module Flor
7
7
 
8
8
  class Storage
9
9
 
10
+ MESSAGE_COLUMNS = [
11
+ :domain, :exid, :point, :content,
12
+ :status, :ctime, :mtime, :cunit, :munit
13
+ ].freeze
14
+ POINTER_COLUMNS = [
15
+ :domain, :exid, :nid, :type, :name, :value, :ctime, :cunit
16
+ ].freeze
17
+
10
18
  attr_reader :unit, :db, :models
11
19
 
12
20
  attr_reader :mutex
@@ -352,8 +360,7 @@ module Flor
352
360
 
353
361
  @db[:flor_messages]
354
362
  .import(
355
- [ :domain, :exid, :point, :content,
356
- :status, :ctime, :mtime, :cunit, :munit ],
363
+ MESSAGE_COLUMNS,
357
364
  unstored.map { |m|
358
365
  [ Flor.domain(m['exid']), m['exid'], m['point'], to_blob(m),
359
366
  'created', n, n, u, u ] }) \
@@ -517,25 +524,6 @@ module Flor
517
524
  end
518
525
  end
519
526
 
520
- def put_task_pointer(msg, tname, tconf)
521
-
522
- exid = msg['exid']
523
- dom = Flor.domain(exid)
524
-
525
- synchronize do
526
-
527
- @db[:flor_pointers]
528
- .insert(
529
- domain: dom,
530
- exid: exid,
531
- nid: msg['nid'],
532
- type: 'tasker',
533
- name: tname,
534
- ctime: Flor.tstamp,
535
- cunit: @unit.identifier)
536
- end
537
- end
538
-
539
527
  def fetch_next_time
540
528
 
541
529
  t =
@@ -598,14 +586,13 @@ module Flor
598
586
 
599
587
  @db[:flor_messages]
600
588
  .where(
601
- id: messages.collect { |m| m['mid'] }.compact)
589
+ id: messages.collect { |m| m['mid'] }.uniq.compact)
602
590
  .update(
603
591
  status: 'consumed', mtime: n, munit: u)
604
592
 
605
593
  @db[:flor_messages]
606
594
  .import(
607
- [ :domain, :exid, :point, :content,
608
- :status, :ctime, :mtime, :cunit, :munit ],
595
+ MESSAGE_COLUMNS,
609
596
  messages
610
597
  .select { |m|
611
598
  ! m['mid'] && POINTS_TO_ARCHIVE.include?(m['point']) }
@@ -621,19 +608,16 @@ module Flor
621
608
 
622
609
  @db[:flor_messages]
623
610
  .where(
624
- id: messages.collect { |m| m['mid'] }.compact)
611
+ id: messages.collect { |m| m['mid'] }.uniq.compact)
625
612
  .delete
626
613
  end
627
614
  end
628
615
 
629
616
  def load_timers
630
617
 
631
- now = Flor.tstamp
632
- no = now[0, now.rindex('.')]
633
-
634
618
  timers
635
619
  .where(status: 'active')
636
- .where { ntime <= no }
620
+ .where { ntime <= Flor.tstam }
637
621
  .order(:ntime)
638
622
  .all
639
623
 
@@ -703,9 +687,9 @@ module Flor
703
687
 
704
688
  def update_pointers(exe, status, now)
705
689
 
706
- # Q Should we archive old pointers?
707
- # Well, it might be better to only archive the execution and leave
708
- # in there enough information...
690
+ # Q Should we archive old pointers?
691
+ # A Well, it might be better to only archive the execution and leave
692
+ # in there enough information...
709
693
 
710
694
  exid = exe['exid']
711
695
 
@@ -731,23 +715,25 @@ module Flor
731
715
 
732
716
  ts = node['tags']
733
717
  ts.each { |t|
734
- a << [ dom, exid, nid, 'tag', t, nil, now, u ] } if ts
718
+ a << [ dom, exid, nid, 'tag', t, nil, now, u, nil ] } if ts
735
719
 
736
720
  vs = nid == '0' ? node['vars'] : nil
737
721
  vs.each { |k, v|
738
722
  case v; when Numeric, String, TrueClass, FalseClass, NilClass
739
- a << [ dom, exid, '0', 'var', k, v.to_s, now, u ]
723
+ a << [ dom, exid, '0', 'var', k, v.to_s, now, u, nil ]
740
724
  when Array, Hash
741
725
  s = '(array)'; s = '(object)' if v.is_a?(Hash)
742
- a << [ dom, exid, '0', 'var', k, s, now, u ]
726
+ a << [ dom, exid, '0', 'var', k, s, now, u, nil ]
743
727
  else
744
- a << [ dom, exid, '0', 'var', k, nil, now, u ]
728
+ a << [ dom, exid, '0', 'var', k, nil, now, u, nil ]
745
729
  end } if vs
746
730
 
747
- #ta = node['heap'] == 'task' ? node['task'] : nil
748
- ta = node['task']
749
- a << [ dom, exid, nid, 'tasker', ta['tasker'], ta['name'], now, u ] \
750
- if ta
731
+ if ta = node['task']
732
+ tasker = ta['tasker']
733
+ name = ta['name']
734
+ content = { message: node['message'], atts: node['atts'] }
735
+ a << [ dom, exid, nid, 'tasker', tasker, name, now, u, content ]
736
+ end
751
737
 
752
738
  a }
753
739
 
@@ -755,17 +741,35 @@ module Flor
755
741
  .where(exid: exid)
756
742
  .select(:nid, :type, :name)
757
743
  .all
758
- pointers.reject! { |_, _, ni, ty, na, _, _, _|
744
+ pointers.reject! { |_, _, ni, ty, na, _, _, _, _|
759
745
  cps.find { |cp| cp[:nid] == ni && cp[:type] == ty && cp[:name] == na } }
760
746
  #
761
747
  # don't insert when already inserted
762
748
 
749
+ if pointer_columns.include?(:content)
750
+ pointers.each { |ptr|
751
+ c = ptr[8]; ptr[8] = to_blob(c) if c }
752
+ else
753
+ pointers.each { |ptr|
754
+ ptr.pop }
755
+ end
756
+
763
757
  @db[:flor_pointers]
764
758
  .import(
765
- [ :domain, :exid, :nid, :type, :name, :value, :ctime, :cunit ],
759
+ pointer_columns,
766
760
  pointers)
767
761
  end
768
762
 
763
+ def pointer_columns
764
+
765
+ @pointer_columns ||=
766
+ if @db[:flor_pointers].columns.include?(:content)
767
+ POINTER_COLUMNS + [ :content ]
768
+ else
769
+ POINTER_COLUMNS
770
+ end
771
+ end
772
+
769
773
  def determine_type_and_schedule(message)
770
774
 
771
775
  t, s = message['type'], message['string']
@@ -822,8 +826,9 @@ module Flor
822
826
  fail ArgumentError.new("no 'sto_uri' conf, cannot connect to db") \
823
827
  unless uri
824
828
 
825
- #uri = uri.to_s
826
- #return Kernel.const_get(uri) if uri.match(/\A[A-Z]+\z/)
829
+ return Kernel.const_get(uri) \
830
+ if uri.is_a?(String) && uri.match(/\A[A-Z]+\z/)
831
+ # for cases where uri == 'DB'
827
832
 
828
833
  Sequel.connect(uri)
829
834
  end
@@ -844,8 +849,11 @@ module Flor
844
849
  # NB: -1 means "check at every use"
845
850
  end
846
851
 
847
- @db_logger = DbLogger.new(@unit)
848
- @db.loggers << @db_logger
852
+ if @unit.conf['sto_db_logger'] != false
853
+
854
+ @db_logger = DbLogger.new(@unit)
855
+ @db.loggers << @db_logger
856
+ end
849
857
  end
850
858
 
851
859
  class << self