hq-engine 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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