flor 1.0.0 → 1.2.1

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.
@@ -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