hq-engine 0.0.1 → 0.0.2
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.
- data/lib/hq/engine/api-spec.rb +111 -0
- data/lib/hq/engine/api.rb +44 -0
- data/lib/hq/engine/engine.rb +355 -0
- data/lib/hq/engine/libxmlruby-mixin.rb +431 -0
- data/lib/hq/engine/mvcc.rb +95 -0
- data/lib/hq/engine/register-engine-commands.rb +20 -0
- data/lib/hq/engine/rule-error.rb +18 -0
- data/lib/hq/engine/subprocess-rule-provider/rule-provider.rb +70 -0
- data/lib/hq/engine/subprocess-rule-provider/session.rb +136 -0
- data/lib/hq/engine/subprocess-rule-provider/start.rb +71 -0
- data/lib/hq/engine/transformer.rb +460 -0
- data/lib/hq/engine/unlock-command.rb +60 -0
- data/spec/hq/engine/transformer-spec.rb +9 -0
- metadata +18 -4
@@ -0,0 +1,111 @@
|
|
1
|
+
require "hq/engine/api"
|
2
|
+
|
3
|
+
describe HQ::Engine::API do
|
4
|
+
|
5
|
+
let(:mvcc) { double :mvcc }
|
6
|
+
|
7
|
+
before do
|
8
|
+
subject.mvcc = mvcc
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has a property named :mvcc" do
|
12
|
+
subject.mvcc = "some value"
|
13
|
+
subject.mvcc.should == "some value"
|
14
|
+
end
|
15
|
+
|
16
|
+
context "#transaction_begin" do
|
17
|
+
|
18
|
+
it "calls mvcc.transaction_begin" do
|
19
|
+
|
20
|
+
mvcc.should_receive(:transaction_begin)
|
21
|
+
|
22
|
+
subject.transaction_begin
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns the transaction id" do
|
27
|
+
|
28
|
+
mvcc.stub(:transaction_begin)
|
29
|
+
.and_return("transaction id")
|
30
|
+
|
31
|
+
subject.transaction_begin.should ==
|
32
|
+
"transaction id"
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
context "#transaction_commit" do
|
39
|
+
|
40
|
+
it "calls mvcc.transaction_commit" do
|
41
|
+
|
42
|
+
mvcc.should_receive(:transaction_commit)
|
43
|
+
.with("transaction id")
|
44
|
+
|
45
|
+
subject.transaction_commit \
|
46
|
+
"transaction id"
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#transaction_rollback" do
|
53
|
+
|
54
|
+
it "calls mvcc.transaction_rollback" do
|
55
|
+
|
56
|
+
mvcc.should_receive(:transaction_rollback)
|
57
|
+
.with("transaction id")
|
58
|
+
|
59
|
+
subject.transaction_rollback \
|
60
|
+
"transaction id"
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
context "#data_store" do
|
67
|
+
|
68
|
+
it "calls mvcc.data_store" do
|
69
|
+
|
70
|
+
mvcc.should_receive(:data_store)
|
71
|
+
.with(
|
72
|
+
"transaction id",
|
73
|
+
"record id",
|
74
|
+
"record value"
|
75
|
+
)
|
76
|
+
|
77
|
+
subject.data_store \
|
78
|
+
"transaction id",
|
79
|
+
"record id",
|
80
|
+
"record value"
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
context "#data_retrieve" do
|
87
|
+
|
88
|
+
it "calls mvcc.data_retrieve" do
|
89
|
+
|
90
|
+
mvcc.should_receive(:data_retrieve)
|
91
|
+
.with(
|
92
|
+
"transaction id",
|
93
|
+
"record id"
|
94
|
+
)
|
95
|
+
.and_return(
|
96
|
+
"record value"
|
97
|
+
)
|
98
|
+
|
99
|
+
record_value =
|
100
|
+
subject.data_retrieve \
|
101
|
+
"transaction id",
|
102
|
+
"record id"
|
103
|
+
|
104
|
+
record_value
|
105
|
+
.should == "record value"
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module HQ
|
2
|
+
module Engine
|
3
|
+
class API
|
4
|
+
|
5
|
+
attr_accessor :mvcc
|
6
|
+
|
7
|
+
def transaction_begin
|
8
|
+
mvcc.transaction_begin
|
9
|
+
end
|
10
|
+
|
11
|
+
def transaction_commit transaction_id
|
12
|
+
mvcc.transaction_commit transaction_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def transaction_rollback transaction_id
|
16
|
+
mvcc.transaction_rollback transaction_id
|
17
|
+
end
|
18
|
+
|
19
|
+
def data_store \
|
20
|
+
transaction_id,
|
21
|
+
record_id,
|
22
|
+
record_value
|
23
|
+
|
24
|
+
mvcc.data_store \
|
25
|
+
transaction_id,
|
26
|
+
record_id,
|
27
|
+
record_value
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def data_retrieve \
|
32
|
+
transaction_id,
|
33
|
+
record_id
|
34
|
+
|
35
|
+
return \
|
36
|
+
mvcc.data_retrieve \
|
37
|
+
transaction_id,
|
38
|
+
record_id
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,355 @@
|
|
1
|
+
require "hq/engine/libxmlruby-mixin"
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module Engine
|
5
|
+
class Engine
|
6
|
+
|
7
|
+
include LibXmlRubyMixin
|
8
|
+
|
9
|
+
attr_accessor :main
|
10
|
+
|
11
|
+
attr_accessor :results
|
12
|
+
|
13
|
+
def config_dir() main.config_dir end
|
14
|
+
def couch() main.couch end
|
15
|
+
def logger() main.logger end
|
16
|
+
def work_dir() main.work_dir end
|
17
|
+
def rule_provider() main.rule_provider end
|
18
|
+
|
19
|
+
def schema_file() "#{work_dir}/schema.xml" end
|
20
|
+
|
21
|
+
def abstract
|
22
|
+
|
23
|
+
return @abstract if @abstract
|
24
|
+
|
25
|
+
abstract = {}
|
26
|
+
|
27
|
+
results.each do
|
28
|
+
|result_name, result|
|
29
|
+
|
30
|
+
abstract[result_name] =
|
31
|
+
result[:doc].root
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
return @abstract = abstract
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_work_dir
|
40
|
+
|
41
|
+
if File.exist? "#{work_dir}/error-flag"
|
42
|
+
logger.warning "removing work directory due to previous error"
|
43
|
+
FileUtils.rm_rf work_dir
|
44
|
+
end
|
45
|
+
|
46
|
+
FileUtils.mkdir_p work_dir
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_default_schema
|
51
|
+
|
52
|
+
return if File.exists? schema_file
|
53
|
+
|
54
|
+
logger.trace "writing schema.xml (empty)"
|
55
|
+
|
56
|
+
File.open schema_file, "w" do |f|
|
57
|
+
f.print "<data>\n"
|
58
|
+
f.print "\t<schema name=\"schema\">\n"
|
59
|
+
f.print "\t\t<id>\n"
|
60
|
+
f.print "\t\t\t<text name=\"name\"/>\n"
|
61
|
+
f.print "\t\t</id>\n"
|
62
|
+
f.print "\t\t<fields>\n"
|
63
|
+
f.print "\t\t</fields>\n"
|
64
|
+
f.print "\t\t<table>\n"
|
65
|
+
f.print "\t\t\t<col name=\"name\"/>\n"
|
66
|
+
f.print "\t\t</table>\n"
|
67
|
+
f.print "\t</schema>\n"
|
68
|
+
f.print "</data>\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def transform
|
74
|
+
|
75
|
+
return if warn_no_config
|
76
|
+
|
77
|
+
create_work_dir
|
78
|
+
|
79
|
+
create_default_schema
|
80
|
+
|
81
|
+
old_schemas_str =
|
82
|
+
File.read schema_file
|
83
|
+
|
84
|
+
loop do
|
85
|
+
|
86
|
+
input_ready
|
87
|
+
|
88
|
+
# process abstract config
|
89
|
+
|
90
|
+
require "hq/engine/transformer"
|
91
|
+
|
92
|
+
transformer = HQ::Engine::Transformer.new
|
93
|
+
transformer.parent = self
|
94
|
+
|
95
|
+
transformer.schema_file = schema_file
|
96
|
+
|
97
|
+
transformer.rules_dir = "#{config_dir}/rules"
|
98
|
+
transformer.include_dir = "#{config_dir}/include"
|
99
|
+
|
100
|
+
transformer.input_dir = "#{work_dir}/input"
|
101
|
+
transformer.output_dir = "#{work_dir}/output"
|
102
|
+
|
103
|
+
transform_result =
|
104
|
+
transformer.rebuild
|
105
|
+
|
106
|
+
# write new schema file
|
107
|
+
|
108
|
+
logger.trace "writing schema.xml"
|
109
|
+
|
110
|
+
new_schemas =
|
111
|
+
transformer.data.select {
|
112
|
+
|item_id, item_xml|
|
113
|
+
item_id =~ /^(schema|schema-option|abstract-rule)\//
|
114
|
+
}
|
115
|
+
.map {
|
116
|
+
|item_id, item_xml|
|
117
|
+
item_doc = XML::Document.string item_xml
|
118
|
+
item_doc.root
|
119
|
+
}
|
120
|
+
|
121
|
+
|
122
|
+
write_data_file schema_file, new_schemas
|
123
|
+
|
124
|
+
# restart if schema changed
|
125
|
+
|
126
|
+
new_schemas_str =
|
127
|
+
File.read schema_file
|
128
|
+
|
129
|
+
if new_schemas_str != old_schemas_str
|
130
|
+
|
131
|
+
old_schemas_str = new_schemas_str
|
132
|
+
new_schemas_str = nil
|
133
|
+
|
134
|
+
logger.notice "restart due to schema changes"
|
135
|
+
|
136
|
+
next
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
# error if the transform was not complete
|
141
|
+
|
142
|
+
unless transform_result[:success]
|
143
|
+
|
144
|
+
transform_result[:missing_types].each do
|
145
|
+
|type_name|
|
146
|
+
logger.warning "type missing: #{type_name}"
|
147
|
+
end
|
148
|
+
|
149
|
+
transform_result[:remaining_rules].each do
|
150
|
+
|rule_name|
|
151
|
+
logger.warning "rule could not be run: #{rule_name}"
|
152
|
+
end
|
153
|
+
|
154
|
+
logger.die "exiting due to failed transformation"
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
# we're done
|
159
|
+
|
160
|
+
load_results
|
161
|
+
|
162
|
+
return
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
def warn_no_config
|
169
|
+
return false unless $no_config
|
170
|
+
return true if @warned_no_config
|
171
|
+
logger.warning "not rebuilding configuration due to --no-config option"
|
172
|
+
@warned_no_config = true
|
173
|
+
return true
|
174
|
+
end
|
175
|
+
|
176
|
+
def input_ready
|
177
|
+
if $no_database
|
178
|
+
logger.warning "using previous input due to --no-database option"
|
179
|
+
else
|
180
|
+
input_dump
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def input_dump
|
185
|
+
|
186
|
+
logger.notice "loading input from database"
|
187
|
+
|
188
|
+
require "xml"
|
189
|
+
|
190
|
+
logger.time "loading input from database" do
|
191
|
+
|
192
|
+
@input_docs = {}
|
193
|
+
@input_strs = {}
|
194
|
+
|
195
|
+
FileUtils.remove_entry_secure "#{work_dir}/input" \
|
196
|
+
if File.directory? "#{work_dir}/input"
|
197
|
+
|
198
|
+
FileUtils.mkdir_p "#{work_dir}/input", :mode => 0700
|
199
|
+
|
200
|
+
rows = couch.view("root", "by_type")["rows"]
|
201
|
+
values_by_type = Hash.new
|
202
|
+
|
203
|
+
legacy = false
|
204
|
+
|
205
|
+
rows.each do |row|
|
206
|
+
|
207
|
+
if legacy
|
208
|
+
|
209
|
+
type = row["value"]["mandar_type"]
|
210
|
+
value = row["value"]
|
211
|
+
|
212
|
+
else
|
213
|
+
|
214
|
+
type = row["value"]["type"]
|
215
|
+
value = row["value"]["value"]
|
216
|
+
|
217
|
+
row["id"] =~ /^current\/(.+)$/
|
218
|
+
value["_id"] = $1
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
values_by_type[type] ||= Hash.new
|
223
|
+
values_by_type[type][value["_id"]] = value
|
224
|
+
|
225
|
+
main.continue
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
change =
|
230
|
+
staged_change
|
231
|
+
|
232
|
+
schema =
|
233
|
+
load_schema_file "#{work_dir}/schema.xml"
|
234
|
+
|
235
|
+
schema_types =
|
236
|
+
schema
|
237
|
+
.keys
|
238
|
+
.map {
|
239
|
+
|name|
|
240
|
+
name =~ /^schema\/(.+)$/ ? $1 : nil
|
241
|
+
}
|
242
|
+
.compact
|
243
|
+
.sort
|
244
|
+
.uniq
|
245
|
+
|
246
|
+
schema_types.each do
|
247
|
+
|type|
|
248
|
+
|
249
|
+
values = values_by_type[type] ||= {}
|
250
|
+
|
251
|
+
input_doc = XML::Document.new
|
252
|
+
input_doc.root = XML::Node.new "data"
|
253
|
+
|
254
|
+
if change
|
255
|
+
change["items"].each do |key, item|
|
256
|
+
case item["action"]
|
257
|
+
when "create", "update"
|
258
|
+
next unless key =~ /^#{Regexp.quote type}\//
|
259
|
+
values[key] = item["record"]
|
260
|
+
when "delete"
|
261
|
+
values.delete key
|
262
|
+
else
|
263
|
+
raise "Error"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
sorted_values =
|
269
|
+
values.values.sort {
|
270
|
+
|a,b|
|
271
|
+
a["_id"] <=> b["_id"]
|
272
|
+
}
|
273
|
+
|
274
|
+
xml_values =
|
275
|
+
sorted_values.map {
|
276
|
+
|value|
|
277
|
+
js_to_xml schema, type, value
|
278
|
+
}
|
279
|
+
|
280
|
+
write_data_file \
|
281
|
+
"#{work_dir}/input/#{type}.xml",
|
282
|
+
xml_values
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
def staged_change
|
291
|
+
|
292
|
+
return nil \
|
293
|
+
unless $deploy_mode == :staged
|
294
|
+
|
295
|
+
locks =
|
296
|
+
couch.get "mandar-locks"
|
297
|
+
|
298
|
+
return nil \
|
299
|
+
unless locks
|
300
|
+
|
301
|
+
change =
|
302
|
+
locks["changes"][$deploy_role]
|
303
|
+
|
304
|
+
return nil \
|
305
|
+
unless change
|
306
|
+
|
307
|
+
return change
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
def load_results
|
312
|
+
|
313
|
+
@results = {}
|
314
|
+
|
315
|
+
item_path_regex =
|
316
|
+
/^#{Regexp.escape work_dir}\/output\/data\/(.+)\.xml$/
|
317
|
+
|
318
|
+
Dir["#{work_dir}/output/data/**/*.xml"].each do
|
319
|
+
|item_path|
|
320
|
+
|
321
|
+
item_path =~ item_path_regex
|
322
|
+
item_id = $1
|
323
|
+
|
324
|
+
item_doc =
|
325
|
+
XML::Document.file \
|
326
|
+
item_path,
|
327
|
+
:options => XML::Parser::Options::NOBLANKS
|
328
|
+
|
329
|
+
item_dom = item_doc.root
|
330
|
+
item_type = item_dom.name
|
331
|
+
|
332
|
+
result = @results[item_type]
|
333
|
+
|
334
|
+
unless result
|
335
|
+
|
336
|
+
result = {}
|
337
|
+
|
338
|
+
result[:doc] = XML::Document.new
|
339
|
+
result[:doc].root = XML::Node.new "data"
|
340
|
+
|
341
|
+
@results[item_type] = result
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
doc = result[:doc]
|
346
|
+
doc.root << doc.import(item_dom)
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|