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,431 @@
|
|
1
|
+
module HQ
|
2
|
+
module Engine
|
3
|
+
module LibXmlRubyMixin
|
4
|
+
|
5
|
+
def load_data_file filename
|
6
|
+
|
7
|
+
ret = []
|
8
|
+
|
9
|
+
doc =
|
10
|
+
XML::Document.file \
|
11
|
+
filename,
|
12
|
+
:options =>XML::Parser::Options::NOBLANKS
|
13
|
+
|
14
|
+
return doc.find("//data/*").to_a
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_data_string string
|
19
|
+
|
20
|
+
ret = []
|
21
|
+
|
22
|
+
doc =
|
23
|
+
XML::Document.string \
|
24
|
+
string,
|
25
|
+
:options =>XML::Parser::Options::NOBLANKS
|
26
|
+
|
27
|
+
return doc.find("//data/*").to_a
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_data_file filename, data
|
32
|
+
|
33
|
+
doc = XML::Document.new
|
34
|
+
doc.root = XML::Node.new "data"
|
35
|
+
|
36
|
+
data.each do
|
37
|
+
|item|
|
38
|
+
doc.root << doc.import(item)
|
39
|
+
end
|
40
|
+
|
41
|
+
File.open filename, "w" do |f|
|
42
|
+
f.print doc.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def load_schema_file filename
|
49
|
+
|
50
|
+
schema_doc =
|
51
|
+
XML::Document.file filename
|
52
|
+
|
53
|
+
schema =
|
54
|
+
Hash[
|
55
|
+
schema_doc.find("*").map do
|
56
|
+
|schema_elem|
|
57
|
+
[
|
58
|
+
"%s/%s" % [
|
59
|
+
schema_elem.name,
|
60
|
+
schema_elem["name"],
|
61
|
+
],
|
62
|
+
schema_elem,
|
63
|
+
]
|
64
|
+
end
|
65
|
+
]
|
66
|
+
|
67
|
+
return schema
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def field_to_json schemas, schema_elem, fields_elem, elem, value
|
72
|
+
|
73
|
+
fields_elem.find(
|
74
|
+
"* [name() != 'option']
|
75
|
+
").each do
|
76
|
+
|field_elem|
|
77
|
+
|
78
|
+
field_name =
|
79
|
+
field_elem["name"]
|
80
|
+
|
81
|
+
case field_elem.name
|
82
|
+
|
83
|
+
when "text"
|
84
|
+
|
85
|
+
value[field_name] =
|
86
|
+
elem[field_name]
|
87
|
+
|
88
|
+
when "int", "ts-update"
|
89
|
+
|
90
|
+
temp = elem.attributes[field_name]
|
91
|
+
|
92
|
+
value[field_name] = temp.empty? ? nil : temp.to_i
|
93
|
+
|
94
|
+
when "list"
|
95
|
+
|
96
|
+
value[field_name] = []
|
97
|
+
|
98
|
+
elem.find("* [ name () = #{xp field_name} ]") \
|
99
|
+
.each do |child_elem|
|
100
|
+
|
101
|
+
prop = {}
|
102
|
+
|
103
|
+
field_to_json \
|
104
|
+
schemas,
|
105
|
+
schema_elem,
|
106
|
+
field_elem,
|
107
|
+
child_elem,
|
108
|
+
prop
|
109
|
+
|
110
|
+
value[field_name] << prop
|
111
|
+
end
|
112
|
+
|
113
|
+
when "struct"
|
114
|
+
|
115
|
+
prop = {}
|
116
|
+
|
117
|
+
child_elem =
|
118
|
+
elem.find_first \
|
119
|
+
"* [ name () = #{xp field_name} ]"
|
120
|
+
|
121
|
+
if child_elem
|
122
|
+
field_to_json \
|
123
|
+
schemas,
|
124
|
+
schema_elem,
|
125
|
+
field_elem,
|
126
|
+
child_elem,
|
127
|
+
prop
|
128
|
+
end
|
129
|
+
|
130
|
+
value[field_name] = prop unless prop.empty?
|
131
|
+
|
132
|
+
when "xml"
|
133
|
+
|
134
|
+
value[field_name] = ""
|
135
|
+
|
136
|
+
elem.find("* [ name () = #{xp field_name} ] / *") \
|
137
|
+
.each do |prop|
|
138
|
+
|
139
|
+
value[field_name] += prop.to_s
|
140
|
+
end
|
141
|
+
|
142
|
+
when "bool"
|
143
|
+
|
144
|
+
value[field_name] = \
|
145
|
+
elem.attributes[field_name] == "yes"
|
146
|
+
|
147
|
+
when "bigtext"
|
148
|
+
|
149
|
+
value[field_name] =
|
150
|
+
elem.find_first("* [ name () = #{xp field_name} ]") \
|
151
|
+
.content
|
152
|
+
|
153
|
+
else
|
154
|
+
|
155
|
+
raise "unexpected element #{field_elem.name} found in " \
|
156
|
+
"field list for schema " \
|
157
|
+
"#{schema_elem.attributes["name"]}"
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
content = []
|
163
|
+
|
164
|
+
elem.find("*").each do |child_elem|
|
165
|
+
|
166
|
+
option_elem =
|
167
|
+
fields_elem.find_first \
|
168
|
+
"option [ @name = #{xp child_elem.name} ]"
|
169
|
+
|
170
|
+
next unless option_elem
|
171
|
+
|
172
|
+
option_ref =
|
173
|
+
option_elem.attributes["ref"]
|
174
|
+
|
175
|
+
schema_option_elem =
|
176
|
+
schemas["schema-opion/#{option_ref}"]
|
177
|
+
|
178
|
+
raise "Error" \
|
179
|
+
unless schema_option_elem
|
180
|
+
|
181
|
+
schema_option_props_elem =
|
182
|
+
schema_option_elem.find_first "props"
|
183
|
+
|
184
|
+
prop = {}
|
185
|
+
|
186
|
+
field_to_json \
|
187
|
+
schemas,
|
188
|
+
schema_elem,
|
189
|
+
schema_option_props_elem,
|
190
|
+
child_elem,
|
191
|
+
prop
|
192
|
+
|
193
|
+
content << {
|
194
|
+
"type" => child_elem.name,
|
195
|
+
"value" => prop,
|
196
|
+
}
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
value["content"] = content \
|
201
|
+
unless content.empty?
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
def js_to_xml schemas, type, value
|
206
|
+
|
207
|
+
schema_elem =
|
208
|
+
schemas["schema/#{type}"]
|
209
|
+
|
210
|
+
elem =
|
211
|
+
XML::Node.new type
|
212
|
+
|
213
|
+
field_to_xml \
|
214
|
+
schemas,
|
215
|
+
schema_elem.find_first("id"),
|
216
|
+
value,
|
217
|
+
elem
|
218
|
+
|
219
|
+
field_to_xml \
|
220
|
+
schemas,
|
221
|
+
schema_elem.find_first("fields"),
|
222
|
+
value,
|
223
|
+
elem
|
224
|
+
|
225
|
+
return elem
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
def xml_to_json schemas, schema_elem, elem
|
230
|
+
|
231
|
+
value = {}
|
232
|
+
|
233
|
+
field_to_json \
|
234
|
+
schemas,
|
235
|
+
schema_elem,
|
236
|
+
schema_elem.find_first("id"),
|
237
|
+
elem,
|
238
|
+
value
|
239
|
+
|
240
|
+
field_to_json \
|
241
|
+
schemas,
|
242
|
+
schema_elem,
|
243
|
+
schema_elem.find_first("fields"),
|
244
|
+
elem,
|
245
|
+
value
|
246
|
+
|
247
|
+
return value
|
248
|
+
end
|
249
|
+
|
250
|
+
def field_to_xml schemas, fields_elem, value, elem
|
251
|
+
|
252
|
+
unless value.is_a? Hash
|
253
|
+
value = {}
|
254
|
+
end
|
255
|
+
|
256
|
+
fields_elem.find("
|
257
|
+
* [name() != 'option']
|
258
|
+
").each do
|
259
|
+
|field_elem|
|
260
|
+
|
261
|
+
field_name =
|
262
|
+
field_elem["name"]
|
263
|
+
|
264
|
+
case field_elem.name
|
265
|
+
|
266
|
+
when "text", "int", "ts-update", "enum"
|
267
|
+
elem[field_name] = value[field_name].to_s
|
268
|
+
|
269
|
+
when "bigtext"
|
270
|
+
prop = XML::Node.new field_name
|
271
|
+
prop << value[field_name]
|
272
|
+
elem << prop
|
273
|
+
|
274
|
+
when "bool"
|
275
|
+
elem[field_name] = "yes" if value[field_name]
|
276
|
+
|
277
|
+
when "list"
|
278
|
+
|
279
|
+
items = value[field_name]
|
280
|
+
|
281
|
+
if items.is_a? Array
|
282
|
+
|
283
|
+
items.each do
|
284
|
+
|item|
|
285
|
+
|
286
|
+
prop =
|
287
|
+
XML::Node.new field_name
|
288
|
+
|
289
|
+
field_to_xml \
|
290
|
+
schemas,
|
291
|
+
field_elem,
|
292
|
+
item,
|
293
|
+
prop
|
294
|
+
|
295
|
+
elem << prop
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
when "struct"
|
302
|
+
|
303
|
+
prop =
|
304
|
+
XML::Node.new field_name
|
305
|
+
|
306
|
+
field_to_xml \
|
307
|
+
schemas,
|
308
|
+
field_elem,
|
309
|
+
value[field_name],
|
310
|
+
prop
|
311
|
+
|
312
|
+
if prop.attributes.length + prop.children.size > 0
|
313
|
+
elem << prop
|
314
|
+
end
|
315
|
+
|
316
|
+
when "xml"
|
317
|
+
|
318
|
+
prop =
|
319
|
+
XML::Node.new field_name
|
320
|
+
|
321
|
+
temp_doc =
|
322
|
+
XML::Document.string \
|
323
|
+
"<xml>#{value[field_name]}</xml>",
|
324
|
+
:options =>XML::Parser::Options::NOBLANKS
|
325
|
+
|
326
|
+
temp_doc.root.each do
|
327
|
+
|temp_elem|
|
328
|
+
prop << temp_elem.copy(true)
|
329
|
+
end
|
330
|
+
|
331
|
+
elem << prop
|
332
|
+
|
333
|
+
else
|
334
|
+
|
335
|
+
raise "unexpected element #{field_elem.name} found in field "
|
336
|
+
"list for schema #{schema_elem["name"]}"
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
if value["content"].is_a? Array
|
343
|
+
|
344
|
+
value["content"].each do
|
345
|
+
|item|
|
346
|
+
|
347
|
+
item_type = item["type"]
|
348
|
+
item_value = item["value"]
|
349
|
+
|
350
|
+
option_elem =
|
351
|
+
fields_elem.find_first "
|
352
|
+
option [@name = '#{item_type}']
|
353
|
+
"
|
354
|
+
|
355
|
+
next unless option_elem
|
356
|
+
|
357
|
+
option_ref =
|
358
|
+
option_elem["ref"]
|
359
|
+
|
360
|
+
schema_option_elem =
|
361
|
+
schemas["schema-option/#{option_ref}"]
|
362
|
+
|
363
|
+
next unless schema_option_elem
|
364
|
+
|
365
|
+
schema_option_props_elem =
|
366
|
+
schema_option_elem.find_first "props"
|
367
|
+
|
368
|
+
prop =
|
369
|
+
XML::Node.new item_type
|
370
|
+
|
371
|
+
field_to_xml \
|
372
|
+
schemas,
|
373
|
+
schema_option_props_elem,
|
374
|
+
item_value,
|
375
|
+
prop
|
376
|
+
|
377
|
+
elem << prop
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
def get_record_id_short schemas, record_elem
|
386
|
+
|
387
|
+
schema_elem =
|
388
|
+
schemas["schema/#{record_elem.name}"]
|
389
|
+
|
390
|
+
unless schema_elem
|
391
|
+
raise "No schema for #{record_elem.name}"
|
392
|
+
end
|
393
|
+
|
394
|
+
id_parts =
|
395
|
+
schema_elem.find("id/*").to_a.map do
|
396
|
+
|id_elem|
|
397
|
+
|
398
|
+
part =
|
399
|
+
record_elem[id_elem["name"]]
|
400
|
+
|
401
|
+
unless part
|
402
|
+
raise "No #{id_elem["name"]} for #{record_elem.name}"
|
403
|
+
end
|
404
|
+
|
405
|
+
part
|
406
|
+
|
407
|
+
end
|
408
|
+
|
409
|
+
id =
|
410
|
+
id_parts.join "/"
|
411
|
+
|
412
|
+
return id
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
def get_record_id_long schemas, record_elem
|
417
|
+
|
418
|
+
return "%s/%s" % [
|
419
|
+
record_elem.name,
|
420
|
+
get_record_id_short(schemas, record_elem),
|
421
|
+
]
|
422
|
+
|
423
|
+
end
|
424
|
+
|
425
|
+
def to_xml_string elem
|
426
|
+
return elem.to_s
|
427
|
+
end
|
428
|
+
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module HQ
|
2
|
+
module Engine
|
3
|
+
class MVCC
|
4
|
+
|
5
|
+
attr_accessor :transactions
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@transactions = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_transaction_id
|
12
|
+
chars = (?a..?z).to_a
|
13
|
+
return (0...20).map { chars.sample }.join
|
14
|
+
end
|
15
|
+
-
|
16
|
+
def transaction_begin
|
17
|
+
|
18
|
+
tx_id =
|
19
|
+
generate_transaction_id
|
20
|
+
|
21
|
+
@transactions[tx_id] =
|
22
|
+
{
|
23
|
+
state: :begun,
|
24
|
+
changes: {},
|
25
|
+
}
|
26
|
+
|
27
|
+
return tx_id
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def transaction_commit transaction_id
|
32
|
+
|
33
|
+
transaction =
|
34
|
+
@transactions[transaction_id]
|
35
|
+
|
36
|
+
transaction[:state] =
|
37
|
+
:committed
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def transaction_rollback transaction_id
|
42
|
+
|
43
|
+
transaction =
|
44
|
+
@transactions[transaction_id]
|
45
|
+
|
46
|
+
transaction[:state] =
|
47
|
+
:rolled_back
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_transaction_info transaction_id
|
52
|
+
|
53
|
+
transaction =
|
54
|
+
@transactions[transaction_id]
|
55
|
+
|
56
|
+
unless transaction
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
|
60
|
+
transaction_info = {
|
61
|
+
state: transaction[:state],
|
62
|
+
}
|
63
|
+
|
64
|
+
return transaction_info
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def data_store \
|
69
|
+
transaction_id,
|
70
|
+
record_id,
|
71
|
+
record_value
|
72
|
+
|
73
|
+
transaction =
|
74
|
+
@transactions[transaction_id]
|
75
|
+
|
76
|
+
transaction[:changes][record_id] =
|
77
|
+
record_value
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def data_retrieve \
|
82
|
+
transaction_id,
|
83
|
+
record_id
|
84
|
+
|
85
|
+
transaction =
|
86
|
+
@transactions[transaction_id]
|
87
|
+
|
88
|
+
return \
|
89
|
+
transaction[:changes][record_id]
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module HQ
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
def self.register_commands hq
|
5
|
+
|
6
|
+
hq.register_command \
|
7
|
+
"unlock",
|
8
|
+
nil,
|
9
|
+
"Unlock crashed deployment" \
|
10
|
+
do
|
11
|
+
require "hq/engine/unlock-command"
|
12
|
+
command = HQ::Engine::UnlockCommand.new
|
13
|
+
command.hq = hq
|
14
|
+
command
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "multi_json"
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module Engine
|
5
|
+
class SubProcessRuleProvider
|
6
|
+
|
7
|
+
def initialize req_wr, resp_rd
|
8
|
+
|
9
|
+
@state = :error
|
10
|
+
|
11
|
+
@req_wr = req_wr
|
12
|
+
@resp_rd = resp_rd
|
13
|
+
|
14
|
+
@state = :open
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
|
20
|
+
@state == :open \
|
21
|
+
or raise "Invalid state #{@state}"
|
22
|
+
|
23
|
+
@state = :error
|
24
|
+
|
25
|
+
@req_wr.close
|
26
|
+
@resp_rd.close
|
27
|
+
|
28
|
+
@state = :closed
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def session
|
33
|
+
|
34
|
+
@state == :open \
|
35
|
+
or raise "Invalid state #{@state}"
|
36
|
+
|
37
|
+
require "hq/engine/subprocess-rule-provider/session"
|
38
|
+
|
39
|
+
chars = "abcdefghijklmnopqrstuvwxyz"
|
40
|
+
session_id = (0...16).map { chars[rand chars.length] }.join("")
|
41
|
+
|
42
|
+
return Session.new self, session_id
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def perform request
|
47
|
+
|
48
|
+
@state == :open \
|
49
|
+
or raise "Invalid state #{@state}"
|
50
|
+
|
51
|
+
# send request
|
52
|
+
|
53
|
+
request_string =
|
54
|
+
MultiJson.dump request
|
55
|
+
|
56
|
+
@req_wr.puts request_string.length + 1
|
57
|
+
@req_wr.puts request_string
|
58
|
+
|
59
|
+
# receive reply
|
60
|
+
|
61
|
+
reply_len = @resp_rd.gets.to_i
|
62
|
+
reply_string = @resp_rd.read reply_len
|
63
|
+
|
64
|
+
return MultiJson.load reply_string
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|