oneacct-export 0.1.0

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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +11 -0
  6. data/Rakefile +18 -0
  7. data/bin/oneacct-export +72 -0
  8. data/config/conf.yml +53 -0
  9. data/config/sidekiq.yml +6 -0
  10. data/lib/errors.rb +7 -0
  11. data/lib/errors/authentication_error.rb +3 -0
  12. data/lib/errors/resource_not_found_error.rb +3 -0
  13. data/lib/errors/resource_retrieval_error.rb +3 -0
  14. data/lib/errors/resource_state_error.rb +3 -0
  15. data/lib/errors/user_not_authorized_error.rb +3 -0
  16. data/lib/input_validator.rb +18 -0
  17. data/lib/one_data_accessor.rb +147 -0
  18. data/lib/one_worker.rb +181 -0
  19. data/lib/one_writer.rb +51 -0
  20. data/lib/oneacct_exporter.rb +88 -0
  21. data/lib/oneacct_exporter/log.rb +15 -0
  22. data/lib/oneacct_exporter/version.rb +3 -0
  23. data/lib/oneacct_opts.rb +131 -0
  24. data/lib/redis_conf.rb +29 -0
  25. data/lib/settings.rb +13 -0
  26. data/lib/sidekiq_conf.rb +11 -0
  27. data/lib/templates/apel-0.2.erb +30 -0
  28. data/mock/one_worker_DEPLOY_ID_missing.xml +136 -0
  29. data/mock/one_worker_DISK_missing.xml +119 -0
  30. data/mock/one_worker_ETIME_0.xml +137 -0
  31. data/mock/one_worker_ETIME_missing.xml +136 -0
  32. data/mock/one_worker_ETIME_nan.xml +137 -0
  33. data/mock/one_worker_GID_missing.xml +136 -0
  34. data/mock/one_worker_GNAME_missing.xml +136 -0
  35. data/mock/one_worker_HISTORY_RECORDS_missing.xml +91 -0
  36. data/mock/one_worker_HISTORY_many.xml +137 -0
  37. data/mock/one_worker_HISTORY_missing.xml +93 -0
  38. data/mock/one_worker_HISTORY_one.xml +115 -0
  39. data/mock/one_worker_IMAGE_ID_missing.xml +136 -0
  40. data/mock/one_worker_MEMORY_0.xml +137 -0
  41. data/mock/one_worker_MEMORY_missing.xml +136 -0
  42. data/mock/one_worker_MEMORY_nan.xml +137 -0
  43. data/mock/one_worker_NET_RX_0.xml +137 -0
  44. data/mock/one_worker_NET_RX_missing.xml +136 -0
  45. data/mock/one_worker_NET_RX_nan.xml +137 -0
  46. data/mock/one_worker_NET_TX_0.xml +137 -0
  47. data/mock/one_worker_NET_TX_missing.xml +136 -0
  48. data/mock/one_worker_NET_TX_nan.xml +137 -0
  49. data/mock/one_worker_RETIME_0.xml +115 -0
  50. data/mock/one_worker_RETIME_missing.xml +114 -0
  51. data/mock/one_worker_RSTIME_0.xml +115 -0
  52. data/mock/one_worker_RSTIME_>_RETIME.xml +115 -0
  53. data/mock/one_worker_RSTIME_missing.xml +114 -0
  54. data/mock/one_worker_STATE_missing.xml +136 -0
  55. data/mock/one_worker_STATE_out_of_range.xml +137 -0
  56. data/mock/one_worker_STIME_>_ETIME.xml +137 -0
  57. data/mock/one_worker_STIME_missing.xml +136 -0
  58. data/mock/one_worker_STIME_nan.xml +137 -0
  59. data/mock/one_worker_TEMPLATE_missing.xml +79 -0
  60. data/mock/one_worker_UID_missing.xml +136 -0
  61. data/mock/one_worker_VCPU_0.xml +137 -0
  62. data/mock/one_worker_VCPU_missing.xml +136 -0
  63. data/mock/one_worker_VCPU_nan.xml +137 -0
  64. data/mock/one_worker_malformed_vm.xml +136 -0
  65. data/mock/one_worker_valid_machine.xml +137 -0
  66. data/mock/one_worker_vm1.xml +137 -0
  67. data/mock/one_worker_vm2.xml +137 -0
  68. data/mock/one_worker_vm3.xml +137 -0
  69. data/mock/one_writer_testfile +2 -0
  70. data/oneacct-export.gemspec +31 -0
  71. data/spec/one_data_accessor_spec.rb +441 -0
  72. data/spec/one_worker_spec.rb +684 -0
  73. data/spec/one_writer_spec.rb +146 -0
  74. data/spec/oneacct_exporter_spec.rb +262 -0
  75. data/spec/oneacct_opts_spec.rb +229 -0
  76. data/spec/redis_conf_spec.rb +94 -0
  77. data/spec/spec_helper.rb +11 -0
  78. metadata +254 -0
@@ -0,0 +1,2 @@
1
+ aaa: <%= @data['aaa'] %>
2
+ bbb: <%= @data['bbb'] -%>
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oneacct_exporter/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'oneacct-export'
8
+ spec.version = OneacctExporter::VERSION
9
+ spec.authors = ['Michal Kimle']
10
+ spec.email = ['kimle.michal@gmail.com']
11
+ spec.summary = 'Exporting OpenNebula accounting data. '
12
+ spec.description = 'Exporting OpenNebula accounting data. '
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
24
+ spec.add_development_dependency 'simplecov', '~> 0.9.0'
25
+ spec.add_development_dependency 'rubygems-tasks', '~> 0.2.4'
26
+
27
+ spec.add_runtime_dependency 'opennebula', '~> 4.6.0'
28
+ spec.add_runtime_dependency 'syslogger', '~> 1.6.0'
29
+ spec.add_runtime_dependency 'sidekiq', '= 3.2.0'
30
+ spec.add_runtime_dependency 'settingslogic', '~> 2.0.9'
31
+ end
@@ -0,0 +1,441 @@
1
+ require 'spec_helper'
2
+ require 'errors'
3
+ require 'logger'
4
+
5
+ describe OneDataAccessor do
6
+ subject { one_data_accessor }
7
+
8
+ before :example do
9
+ Settings.output['num_of_vms_per_file'] = 100
10
+ Settings.xml_rpc['endpoint'] = nil
11
+ Settings.xml_rpc['secret'] = nil
12
+ allow(OpenNebula::Client).to receive(:new) { 'one_client' }
13
+ end
14
+
15
+ let(:one_data_accessor) { OneDataAccessor.new(false, Logger.new('/dev/null')) }
16
+
17
+ describe '#new' do
18
+ it 'returns OneDataAccessor object' do
19
+ is_expected.to be_instance_of(OneDataAccessor)
20
+ end
21
+
22
+ context 'with logger specified' do
23
+ let(:one_data_accessor) { OneDataAccessor.new(false, 'fake_logger') }
24
+
25
+ it 'correctly assign logger' do
26
+ expect(subject.log).to eq('fake_logger')
27
+ end
28
+ end
29
+
30
+ context 'with no batch size specified in settings' do
31
+ before :example do
32
+ Settings.output['num_of_vms_per_file'] = nil
33
+ end
34
+
35
+ it 'returns default value for batch size' do
36
+ expect(subject.batch_size).to eq(500)
37
+ end
38
+ end
39
+
40
+ context 'with batch size specified in settings' do
41
+ context 'that is a valid batch size' do
42
+ it 'correctly assign batch size' do
43
+ expect(subject.batch_size).to eq(100)
44
+ end
45
+ end
46
+
47
+ context 'that is not a valid batch size' do
48
+ before :example do
49
+ Settings.output['num_of_vms_per_file'] = 'infdf54#!@#'
50
+ end
51
+
52
+ let(:one_data_accessor) { nil }
53
+
54
+ it 'fails with ArgumentError' do
55
+ expect { OneDataAccessor.new(false, Logger.new('/dev/null')) }.to raise_error(ArgumentError)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '.initialize_client' do
62
+ let(:one_data_accessor) { OneDataAccessor.new(false, Logger.new('/dev/null')) }
63
+
64
+ context 'with correct arguments' do
65
+ before :example do
66
+ Settings.xml_rpc['secret'] = 'secret'
67
+ Settings.xml_rpc['endpoint'] = 'http://machine.hogworts:1234'
68
+ end
69
+
70
+ it 'initializes OpenNebula::Client' do
71
+ expect(OpenNebula::Client).to receive(:new).with('secret', 'http://machine.hogworts:1234')
72
+ subject.initialize_client
73
+ end
74
+ end
75
+
76
+ context 'with nil arguments' do
77
+ it 'initializes OpenNebula::Client' do
78
+ expect(OpenNebula::Client).to receive(:new).with(nil, nil)
79
+ subject.initialize_client
80
+ end
81
+ end
82
+
83
+ context 'with invalid endpoint' do
84
+ before :example do
85
+ Settings.xml_rpc['endpoint'] = 'ef21!@%^|>'
86
+ end
87
+
88
+ it 'fails with ArgumentError' do
89
+ expect { OneDataAccessor.new(Logger.new('/dev/null')) }.to raise_error(ArgumentError)
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '.check_retval' do
95
+ context 'without error' do
96
+ let(:error) { 'no_error' }
97
+
98
+ it 'returns true' do
99
+ expect(subject.check_retval(error, nil)).to eq(true)
100
+ end
101
+ end
102
+
103
+ context 'with' do
104
+ context 'authentication error' do
105
+ let(:error) { OpenNebula::Error.new(nil, OpenNebula::Error::EAUTHENTICATION) }
106
+
107
+ it 'fails with AuthenticationError' do
108
+ expect { subject.check_retval(error, nil) }.to raise_error(Errors::AuthenticationError)
109
+ end
110
+ end
111
+
112
+ context 'authorization error' do
113
+ let(:error) { OpenNebula::Error.new(nil, OpenNebula::Error::EAUTHORIZATION) }
114
+
115
+ it 'fails with UserNotAuthorizedError' do
116
+ expect { subject.check_retval(error, nil) }.to raise_error(Errors::UserNotAuthorizedError)
117
+ end
118
+ end
119
+
120
+ context 'non existing resource error' do
121
+ let(:error) { OpenNebula::Error.new(nil, OpenNebula::Error::ENO_EXISTS) }
122
+
123
+ it 'fails with ResourceNotFoundError' do
124
+ expect { subject.check_retval(error, nil) }.to raise_error(Errors::ResourceNotFoundError)
125
+ end
126
+ end
127
+
128
+ context 'resource state error' do
129
+ let(:error) { OpenNebula::Error.new(nil, OpenNebula::Error::EACTION) }
130
+
131
+ it 'fails with ResourceStateError' do
132
+ expect { subject.check_retval(error, nil) }.to raise_error(Errors::ResourceStateError)
133
+ end
134
+ end
135
+
136
+ context 'with any of above errors and custom error class' do
137
+ let(:error) { OpenNebula::Error.new(nil, OpenNebula::Error::EACTION) }
138
+
139
+ it 'fails with specified error' do
140
+ expect { subject.check_retval(error, Errors::ResourceRetrievalError) }.to raise_error(Errors::ResourceStateError)
141
+ end
142
+ end
143
+
144
+ context 'with error not specified above and custom error class' do
145
+ let(:error) { OpenNebula::Error.new(nil, OpenNebula::Error::ENOTDEFINED) }
146
+
147
+ it 'fails with customm error' do
148
+ expect { subject.check_retval(error, Errors::ResourceRetrievalError) }.to raise_error(Errors::ResourceRetrievalError)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ describe '.load_vm_pool' do
155
+ before :example do
156
+ allow(vm_pool).to receive(:info) { 'valid_rc' }
157
+ allow(vm_pool).to receive(:to) { vm_pool }
158
+ allow(OpenNebula::VirtualMachinePool).to receive(:new) { vm_pool }
159
+ end
160
+
161
+ let(:vm_pool) { double('vm_pool') }
162
+
163
+ context 'without compatibility mode' do
164
+ context 'with valid batch number' do
165
+ it 'requests vms with correct range' do
166
+ expect(vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, 0, 99, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
167
+ subject.load_vm_pool(0)
168
+
169
+ expect(vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, 100, 199, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
170
+ subject.load_vm_pool(1)
171
+
172
+ expect(vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, 300, 399, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
173
+ subject.load_vm_pool(3)
174
+
175
+ expect(vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, 500, 599, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
176
+ subject.load_vm_pool(5)
177
+
178
+ expect(vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, 1000, 1099, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
179
+ subject.load_vm_pool(10)
180
+
181
+ expect(vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, 1200, 1299, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
182
+ subject.load_vm_pool(12)
183
+ end
184
+
185
+ it 'returns obtained vm pool' do
186
+ expect(subject.load_vm_pool(0)).to eq(vm_pool)
187
+ end
188
+ end
189
+
190
+ context 'with invalid batch number' do
191
+ it 'fails with ArgumentError' do
192
+ expect { subject.load_vm_pool('invalid_number') }.to raise_error(ArgumentError)
193
+ end
194
+ end
195
+ end
196
+
197
+ context 'with compatibility mode' do
198
+ before :example do
199
+ Settings.output['num_of_vms_per_file'] = 3
200
+ allow(OpenNebula::VirtualMachinePool).to receive(:new) { compatibility_vm_pool }
201
+ allow(compatibility_vm_pool).to receive(:info) { 'valid_rc' }
202
+ end
203
+
204
+ let(:one_data_accessor) { OneDataAccessor.new(true, Logger.new('/dev/null')) }
205
+ let(:compatibility_vm_pool) { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }
206
+
207
+ it 'returns correct parts of vm pool' do
208
+ expect(compatibility_vm_pool).to receive(:info).with(OpenNebula::Pool::INFO_ALL, -1, -1, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
209
+ expect(subject.load_vm_pool(0)).to eq([0, 1, 2])
210
+ expect(subject.load_vm_pool(1)).to eq([3, 4, 5])
211
+ expect(subject.load_vm_pool(2)).to eq([6, 7, 8])
212
+ expect(subject.load_vm_pool(3)).to eq([9, 10])
213
+ end
214
+ end
215
+ end
216
+
217
+ describe '.vm' do
218
+ before :example do
219
+ allow(vm).to receive(:info) { 'valid_rc' }
220
+ allow(vm).to receive(:to) { vm }
221
+ allow(OpenNebula::VirtualMachine).to receive(:new) { vm }
222
+ allow(OpenNebula::VirtualMachine).to receive(:build_xml)
223
+ end
224
+
225
+ let(:vm) { double('vm') }
226
+
227
+ context 'with valid vm id' do
228
+ it 'requests correct vm' do
229
+ expect(OpenNebula::VirtualMachine).to receive(:build_xml).with(0)
230
+ subject.vm(0)
231
+
232
+ expect(OpenNebula::VirtualMachine).to receive(:build_xml).with(42)
233
+ subject.vm(42)
234
+
235
+ expect(OpenNebula::VirtualMachine).to receive(:build_xml).with(123)
236
+ subject.vm(123)
237
+ end
238
+
239
+ it 'returns obtained vm' do
240
+ expect(subject.vm(0)).to eq(vm)
241
+ end
242
+ end
243
+
244
+ context 'with invalid vm id' do
245
+ it 'fails with ArgumentError' do
246
+ expect { subject.vm('invalid_number') }.to raise_error(ArgumentError)
247
+ end
248
+ end
249
+ end
250
+
251
+ describe '.want?' do
252
+ let(:vm) { { 'STATE' => OneDataAccessor::STATE_DONE, 'STIME' => 0, 'ETIME' => 2000, 'GNAME' => 'group1' } }
253
+ let(:range) { { from: 500, to: 1000 } }
254
+ let(:groups) { { include: ['group1'] } }
255
+
256
+ context 'with nils for groups and range' do
257
+ it 'returns true' do
258
+ expect(subject.want?(vm, nil, nil)).to eq(true)
259
+ end
260
+ end
261
+
262
+ context 'with empty groups and range' do
263
+ it 'returns true' do
264
+ expect(subject.want?(vm, {}, {})).to eq(true)
265
+ end
266
+ end
267
+
268
+ context 'with nil vm' do
269
+ it 'returns false' do
270
+ expect(subject.want?(nil, range, groups)).to eq(false)
271
+ end
272
+ end
273
+
274
+ context 'with ranges specified' do
275
+ context 'and vm was stopped before the time range' do
276
+ before :example do
277
+ vm['ETIME'] = 100
278
+ end
279
+
280
+ it 'returns false' do
281
+ expect(subject.want?(vm, range, groups)).to eq(false)
282
+ end
283
+ end
284
+
285
+ context 'and vm was started after the time range' do
286
+ before :example do
287
+ vm['STIME'] = 1500
288
+ end
289
+
290
+ it 'returns false' do
291
+ expect(subject.want?(vm, range, groups)).to eq(false)
292
+ end
293
+ end
294
+ end
295
+
296
+ context 'with groups specified' do
297
+ context 'and vm was not among included groups' do
298
+ before :example do
299
+ vm['GNAME'] = 'group_not_included'
300
+ end
301
+
302
+ it 'returns false' do
303
+ expect(subject.want?(vm, range, groups)).to eq(false)
304
+ end
305
+ end
306
+
307
+ context 'and vm was among excluded groups' do
308
+ before :example do
309
+ groups.delete :include
310
+ groups[:exclude] = ['group1']
311
+ end
312
+
313
+ it 'returns false' do
314
+ expect(subject.want?(vm, range, groups)).to eq(false)
315
+ end
316
+ end
317
+ end
318
+
319
+ context 'with vm within the range and among included groups' do
320
+ it 'returns true' do
321
+ expect(subject.want?(vm, range, groups)).to eq(true)
322
+ end
323
+ end
324
+ end
325
+
326
+ describe '.vms' do
327
+ before :example do
328
+ allow(subject).to receive(:load_vm_pool) { vm_pool }
329
+ allow(subject).to receive(:want?) { true }
330
+ end
331
+
332
+ let(:vm1) { { 'ID' => '1' } }
333
+ let(:vm2) { { 'ID' => '2' } }
334
+ let(:vm3) { { 'ID' => '3' } }
335
+ let(:vm_pool) { [vm1, vm2, vm3] }
336
+ let(:batch_number) { 5 }
337
+
338
+ context 'is called with some batch number' do
339
+ before :example do
340
+ expect(subject).to receive(:load_vm_pool).with(5) { vm_pool }
341
+ end
342
+
343
+ it 'calls load_vm_pool with that batch number' do
344
+ subject.vms(batch_number, nil, nil)
345
+ end
346
+ end
347
+
348
+ context 'when vm pool is empty' do
349
+ let(:vm_pool) { [] }
350
+
351
+ it 'returns nil' do
352
+ expect(subject.vms(batch_number, nil, nil)).to be_nil
353
+ end
354
+ end
355
+
356
+ context 'for every vm obtained from vm pool' do
357
+ before :example do
358
+ expect(subject).to receive(:want?).with(vm1, nil, nil).once
359
+ expect(subject).to receive(:want?).with(vm2, nil, nil).once
360
+ expect(subject).to receive(:want?).with(vm3, nil, nil).once
361
+ end
362
+ it 'calls want?' do
363
+ subject.vms(batch_number, nil, nil)
364
+ end
365
+ end
366
+
367
+ context 'for every vm obtained from vm pool' do
368
+ context 'with ID attribute' do
369
+ it 'returns ID of those vms' do
370
+ expect(subject.vms(batch_number, nil, nil)).to eq([1, 2, 3])
371
+ end
372
+ end
373
+
374
+ context 'with vms with missing ID attribute' do
375
+ let(:vm1) { {} }
376
+ let(:vm3) { {} }
377
+
378
+ it 'skips vms without ID attribute and returns only thouse with it' do
379
+ expect(subject.vms(batch_number, nil, nil)).to eq([2])
380
+ end
381
+ end
382
+ end
383
+ end
384
+
385
+ describe '.mapping' do
386
+ before :example do
387
+ allow(pool_class).to receive(:new) { pool }
388
+ allow(pool).to receive(:respond_to?).and_call_original
389
+ allow(pool).to receive(:respond_to?).with('info_all') { true }
390
+ allow(pool).to receive(:info_all) { 'valid_rc' }
391
+ allow(pool).to receive(:info) { 'valid_rc' }
392
+ end
393
+
394
+ let(:pool_class) { double('pool_class') }
395
+ let(:vm1) { { 'ID' => '1', 'XPATH' => 'data1' } }
396
+ let(:vm2) { { 'ID' => '2', 'XPATH' => 'data2' } }
397
+ let(:vm3) { { 'ID' => '3', 'XPATH' => 'data3' } }
398
+ let(:pool) { [vm1, vm2, vm3] }
399
+ let(:xpath) { 'XPATH' }
400
+ let(:result) { { '1' => 'data1', '2' => 'data2', '3' => 'data3' } }
401
+
402
+ context 'with pool class that has info_all method' do
403
+ before :example do
404
+ expect(pool).to receive(:info_all)
405
+ end
406
+
407
+ it 'calls info_all method for that pool class' do
408
+ subject.mapping(pool_class, xpath)
409
+ end
410
+ end
411
+
412
+ context 'with pool class that does not have info_all method' do
413
+ before :example do
414
+ allow(pool).to receive(:respond_to?).with('info_all') { false }
415
+ expect(pool).to receive(:info)
416
+ end
417
+
418
+ it 'calls info method for that pool class' do
419
+ subject.mapping(pool_class, xpath)
420
+ end
421
+ end
422
+
423
+ context 'for every item obtained from the pool' do
424
+ context 'where every item has an ID' do
425
+ it 'creates a mapping of item\'s ID and its element according to xpath' do
426
+ expect(subject.mapping(pool_class, xpath)).to eq(result)
427
+ end
428
+ end
429
+
430
+ context 'where some items miss ID attribute' do
431
+ let(:vm1) { {} }
432
+ let(:vm3) { {} }
433
+ let(:result) { { '2' => 'data2' } }
434
+
435
+ it 'creates a mapping of item\'s ID and its element according to xpath, skipping items without ID' do
436
+ expect(subject.mapping(pool_class, xpath)).to eq(result)
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end