oneacct-export 0.1.0

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