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