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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +11 -0
- data/Rakefile +18 -0
- data/bin/oneacct-export +72 -0
- data/config/conf.yml +53 -0
- data/config/sidekiq.yml +6 -0
- data/lib/errors.rb +7 -0
- data/lib/errors/authentication_error.rb +3 -0
- data/lib/errors/resource_not_found_error.rb +3 -0
- data/lib/errors/resource_retrieval_error.rb +3 -0
- data/lib/errors/resource_state_error.rb +3 -0
- data/lib/errors/user_not_authorized_error.rb +3 -0
- data/lib/input_validator.rb +18 -0
- data/lib/one_data_accessor.rb +147 -0
- data/lib/one_worker.rb +181 -0
- data/lib/one_writer.rb +51 -0
- data/lib/oneacct_exporter.rb +88 -0
- data/lib/oneacct_exporter/log.rb +15 -0
- data/lib/oneacct_exporter/version.rb +3 -0
- data/lib/oneacct_opts.rb +131 -0
- data/lib/redis_conf.rb +29 -0
- data/lib/settings.rb +13 -0
- data/lib/sidekiq_conf.rb +11 -0
- data/lib/templates/apel-0.2.erb +30 -0
- data/mock/one_worker_DEPLOY_ID_missing.xml +136 -0
- data/mock/one_worker_DISK_missing.xml +119 -0
- data/mock/one_worker_ETIME_0.xml +137 -0
- data/mock/one_worker_ETIME_missing.xml +136 -0
- data/mock/one_worker_ETIME_nan.xml +137 -0
- data/mock/one_worker_GID_missing.xml +136 -0
- data/mock/one_worker_GNAME_missing.xml +136 -0
- data/mock/one_worker_HISTORY_RECORDS_missing.xml +91 -0
- data/mock/one_worker_HISTORY_many.xml +137 -0
- data/mock/one_worker_HISTORY_missing.xml +93 -0
- data/mock/one_worker_HISTORY_one.xml +115 -0
- data/mock/one_worker_IMAGE_ID_missing.xml +136 -0
- data/mock/one_worker_MEMORY_0.xml +137 -0
- data/mock/one_worker_MEMORY_missing.xml +136 -0
- data/mock/one_worker_MEMORY_nan.xml +137 -0
- data/mock/one_worker_NET_RX_0.xml +137 -0
- data/mock/one_worker_NET_RX_missing.xml +136 -0
- data/mock/one_worker_NET_RX_nan.xml +137 -0
- data/mock/one_worker_NET_TX_0.xml +137 -0
- data/mock/one_worker_NET_TX_missing.xml +136 -0
- data/mock/one_worker_NET_TX_nan.xml +137 -0
- data/mock/one_worker_RETIME_0.xml +115 -0
- data/mock/one_worker_RETIME_missing.xml +114 -0
- data/mock/one_worker_RSTIME_0.xml +115 -0
- data/mock/one_worker_RSTIME_>_RETIME.xml +115 -0
- data/mock/one_worker_RSTIME_missing.xml +114 -0
- data/mock/one_worker_STATE_missing.xml +136 -0
- data/mock/one_worker_STATE_out_of_range.xml +137 -0
- data/mock/one_worker_STIME_>_ETIME.xml +137 -0
- data/mock/one_worker_STIME_missing.xml +136 -0
- data/mock/one_worker_STIME_nan.xml +137 -0
- data/mock/one_worker_TEMPLATE_missing.xml +79 -0
- data/mock/one_worker_UID_missing.xml +136 -0
- data/mock/one_worker_VCPU_0.xml +137 -0
- data/mock/one_worker_VCPU_missing.xml +136 -0
- data/mock/one_worker_VCPU_nan.xml +137 -0
- data/mock/one_worker_malformed_vm.xml +136 -0
- data/mock/one_worker_valid_machine.xml +137 -0
- data/mock/one_worker_vm1.xml +137 -0
- data/mock/one_worker_vm2.xml +137 -0
- data/mock/one_worker_vm3.xml +137 -0
- data/mock/one_writer_testfile +2 -0
- data/oneacct-export.gemspec +31 -0
- data/spec/one_data_accessor_spec.rb +441 -0
- data/spec/one_worker_spec.rb +684 -0
- data/spec/one_writer_spec.rb +146 -0
- data/spec/oneacct_exporter_spec.rb +262 -0
- data/spec/oneacct_opts_spec.rb +229 -0
- data/spec/redis_conf_spec.rb +94 -0
- data/spec/spec_helper.rb +11 -0
- metadata +254 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 670e8800aaf7a64b5f75d0f6d930e0d9be4d96fb
|
4
|
+
data.tar.gz: e6545c409b1c10e466a927833d3ffcde5b0c9c19
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79f668da9123a2b2d83eb3131062494614ce605132b394921b0867faeb72c53c7a16649095e31928f6e59eb0163b4e78574670515cd928dfbaf75914bdf708bc
|
7
|
+
data.tar.gz: fe9e971c3b06d3f7717e24feca0379206f4e8123276d01815618c475b331fbcf2e30db1bad2cbfa19b0190615ff3d759950d2f097c25bd9e913596f15c822fa2
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# OneacctExport
|
2
|
+
|
3
|
+
Exporting OpenNebula accounting data.
|
4
|
+
|
5
|
+
## Contributing
|
6
|
+
|
7
|
+
1. Fork it ( https://github.com/Misenko/oneacct_export/fork )
|
8
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
9
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
10
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
11
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubygems/tasks'
|
3
|
+
|
4
|
+
task :default => 'test'
|
5
|
+
|
6
|
+
desc "Run all tests; includes rspec and coverage reports"
|
7
|
+
task :test => 'rcov:rspec'
|
8
|
+
|
9
|
+
desc "Run all tests; includes rspec and coverage reports"
|
10
|
+
task :spec => 'test'
|
11
|
+
|
12
|
+
Gem::Tasks.new(:build => {:tar => true, :zip => true}, :sign => {:checksum => true, :pgp => false})
|
13
|
+
|
14
|
+
namespace :rcov do
|
15
|
+
require 'rspec/core/rake_task'
|
16
|
+
|
17
|
+
RSpec::Core::RakeTask.new(:rspec)
|
18
|
+
end
|
data/bin/oneacct-export
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
ENV['RAILS_ENV'] = ENV['RAILS_ENV'] ? ENV['RAILS_ENV'] : 'production'
|
3
|
+
|
4
|
+
require 'syslogger'
|
5
|
+
require 'oneacct_exporter'
|
6
|
+
require 'oneacct_exporter/log'
|
7
|
+
require 'settings'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'oneacct_opts'
|
10
|
+
|
11
|
+
options = OneacctOpts.parse(ARGV)
|
12
|
+
|
13
|
+
log = Logger.new(STDOUT)
|
14
|
+
|
15
|
+
if Settings['logging'] && Settings['logging']['log_type'] == 'file'
|
16
|
+
begin
|
17
|
+
log_file = File.open(Settings['logging']['log_file'], File::WRONLY | File::CREAT | File::APPEND)
|
18
|
+
log = Logger.new(log_file)
|
19
|
+
rescue => e
|
20
|
+
OneacctExporter::Log.setup_logging(log)
|
21
|
+
log.warn("Unable to create log file #{Settings['logging']['log_file']}: #{e.message}.\
|
22
|
+
Falling back to STDOUT.")
|
23
|
+
end
|
24
|
+
elsif Settings['logging'] && Settings['logging']['log_type'] == 'syslog'
|
25
|
+
log = Syslogger.new('oneacct-export')
|
26
|
+
end
|
27
|
+
|
28
|
+
OneacctExporter::Log.setup_log_level(log)
|
29
|
+
|
30
|
+
range = {}
|
31
|
+
range[:from] = options.records_from
|
32
|
+
range[:to] = options.records_to
|
33
|
+
|
34
|
+
groups = {}
|
35
|
+
groups[:include] = options.include_groups if options.include_groups
|
36
|
+
groups[:exclude] = options.exclude_groups if options.exclude_groups
|
37
|
+
|
38
|
+
if options.groups_file
|
39
|
+
log.debug('Reading groups from file...')
|
40
|
+
if File.exist?(options.groups_file) && File.readable?(options.groups_file)
|
41
|
+
file = File.open(options.groups_file, 'r')
|
42
|
+
file.each_line do |line|
|
43
|
+
groups[groups.keys.first] << line
|
44
|
+
end
|
45
|
+
file.close
|
46
|
+
else
|
47
|
+
log.error("File contaning groups: #{options.groups_file} doesn't exists or cannot be read. "\
|
48
|
+
'Skipping groups restriction...')
|
49
|
+
groups[groups.keys.first] = []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
FileUtils.mkdir_p Settings.output['output_dir']
|
55
|
+
rescue SystemCallError => e
|
56
|
+
puts "Cannot create an output directory: #{e.message}. Quitting."
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
|
60
|
+
log.debug('Creating OneacctExporter...')
|
61
|
+
|
62
|
+
opts = {}
|
63
|
+
opts[:range] = range
|
64
|
+
opts[:groups] = groups
|
65
|
+
opts[:blocking] = options.blocking
|
66
|
+
opts[:timeout] = options.timeout
|
67
|
+
opts[:compatibility] = options.compatibility
|
68
|
+
|
69
|
+
log.debug(opts)
|
70
|
+
|
71
|
+
oneacct_exporter = OneacctExporter.new(opts, log)
|
72
|
+
oneacct_exporter.export
|
data/config/conf.yml
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#Five mandatory parameters:
|
2
|
+
# site_name
|
3
|
+
# cloud_type
|
4
|
+
# endpoint
|
5
|
+
# output -> output_dir
|
6
|
+
# output -> output_type
|
7
|
+
defaults: &defaults
|
8
|
+
site_name: <fill in>
|
9
|
+
cloud_type: <fill in>
|
10
|
+
endpoint: <fill in>
|
11
|
+
output:
|
12
|
+
output_dir: <fill in>
|
13
|
+
output_type: apel-0.2 #currently only option
|
14
|
+
# num_of_vms_per_file: 500
|
15
|
+
#
|
16
|
+
#logging:
|
17
|
+
# log_type: file #Two options: file, syslog. Defaults to stdout
|
18
|
+
# log_file: /var/log/oneacct-export.log #Used when type file selected
|
19
|
+
#
|
20
|
+
#xml_rpc:
|
21
|
+
# secret: username:password #If not specified looking for secret in ONE_AUTH and ~/.one/one_auth
|
22
|
+
# endpoint: http://localhost:2633/RPC2 #Defaults to content of ONE_XMLRPC or content of ~/.one/one_endpoint or http://localhost:2633/RPC2
|
23
|
+
#
|
24
|
+
#redis:
|
25
|
+
# namespace: oneacct_export
|
26
|
+
# url: redis://localhost:6379
|
27
|
+
# password: password
|
28
|
+
sidekiq:
|
29
|
+
queue: oneacct_export
|
30
|
+
production:
|
31
|
+
<<: *defaults
|
32
|
+
development:
|
33
|
+
<<: *defaults
|
34
|
+
test:
|
35
|
+
site_name: <placeholder>
|
36
|
+
cloud_type: <placeholder>
|
37
|
+
endpoint: <placeholder>
|
38
|
+
output:
|
39
|
+
output_dir: <placeholder>
|
40
|
+
output_type: <placeholder>
|
41
|
+
num_of_vms_per_file: <placeholder>
|
42
|
+
logging:
|
43
|
+
log_type: <placeholder>
|
44
|
+
log_file: <placeholder>
|
45
|
+
xml_rpc:
|
46
|
+
secret: <placeholder>
|
47
|
+
endpoint: <placeholder>
|
48
|
+
redis:
|
49
|
+
namespace: <placeholder>
|
50
|
+
url: http://some.random.url.com
|
51
|
+
password: password
|
52
|
+
sidekiq:
|
53
|
+
queue: oneacct_export
|
data/config/sidekiq.yml
ADDED
data/lib/errors.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module InputValidator
|
4
|
+
URI_RE = /\A#{URI.regexp}\z/
|
5
|
+
NUMBER_RE = /\A[[:digit:]]+\z/
|
6
|
+
|
7
|
+
def is?(object, regexp)
|
8
|
+
object.to_s =~ regexp
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_number?(object)
|
12
|
+
is?(object, NUMBER_RE)
|
13
|
+
end
|
14
|
+
|
15
|
+
def is_uri?(object)
|
16
|
+
is?(object, URI_RE)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'opennebula'
|
2
|
+
require 'settings'
|
3
|
+
require 'errors'
|
4
|
+
require 'logger'
|
5
|
+
require 'input_validator'
|
6
|
+
|
7
|
+
class OneDataAccessor
|
8
|
+
include Errors
|
9
|
+
include InputValidator
|
10
|
+
|
11
|
+
STATE_DONE = '6'
|
12
|
+
|
13
|
+
attr_reader :log, :batch_size, :client, :compatibility
|
14
|
+
|
15
|
+
def initialize(compatibility, log = nil)
|
16
|
+
@log = log ? log : Logger.new(STDOUT)
|
17
|
+
@compatibility = compatibility
|
18
|
+
|
19
|
+
@batch_size = Settings.output['num_of_vms_per_file'] ? Settings.output['num_of_vms_per_file'] : 500
|
20
|
+
fail ArgumentError, 'Wrong number of vms per file.' unless is_number?(@batch_size)
|
21
|
+
|
22
|
+
@compatibility_vm_pool = nil
|
23
|
+
|
24
|
+
initialize_client
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_client
|
28
|
+
secret = Settings['xml_rpc'] ? Settings.xml_rpc['secret'] : nil
|
29
|
+
endpoint = Settings['xml_rpc'] ? Settings.xml_rpc['endpoint'] : nil
|
30
|
+
fail ArgumentError, "#{endpoint} is not a valid URL." if endpoint && !is_uri?(endpoint)
|
31
|
+
|
32
|
+
@client = OpenNebula::Client.new(secret, endpoint)
|
33
|
+
end
|
34
|
+
|
35
|
+
def mapping(pool_class, xpath)
|
36
|
+
@log.debug("Generating mapping for class: #{pool_class} and xpath: '#{xpath}'.")
|
37
|
+
pool = pool_class.new(@client)
|
38
|
+
if pool.respond_to? 'info_all'
|
39
|
+
rc = pool.info_all
|
40
|
+
check_retval(rc, Errors::ResourceRetrievalError)
|
41
|
+
else
|
42
|
+
rc = pool.info
|
43
|
+
check_retval(rc, Errors::ResourceRetrievalError)
|
44
|
+
end
|
45
|
+
|
46
|
+
map = {}
|
47
|
+
pool.each do |item|
|
48
|
+
unless item['ID']
|
49
|
+
@log.error("Skipping a resource of the type #{pool_class} without an ID present.")
|
50
|
+
next
|
51
|
+
end
|
52
|
+
map[item['ID']] = item[xpath]
|
53
|
+
end
|
54
|
+
|
55
|
+
map
|
56
|
+
end
|
57
|
+
|
58
|
+
def vm(vm_id)
|
59
|
+
fail ArgumentError, "#{vm_id} is not a valid id." unless is_number?(vm_id)
|
60
|
+
@log.debug("Retrieving virtual machine with id: #{vm_id}.")
|
61
|
+
vm = OpenNebula::VirtualMachine.new(OpenNebula::VirtualMachine.build_xml(vm_id), @client)
|
62
|
+
rc = vm.info
|
63
|
+
check_retval(rc, Errors::ResourceRetrievalError)
|
64
|
+
vm
|
65
|
+
end
|
66
|
+
|
67
|
+
def vms(batch_number, range, groups)
|
68
|
+
vms = []
|
69
|
+
vm_pool = load_vm_pool(batch_number)
|
70
|
+
return nil if vm_pool.count == 0
|
71
|
+
|
72
|
+
@log.debug("Searching for vms based on range: #{range} and groups: #{groups}.")
|
73
|
+
vm_pool.each do |vm|
|
74
|
+
unless vm['ID']
|
75
|
+
@log.error('Skipping a record without an ID present.')
|
76
|
+
next
|
77
|
+
end
|
78
|
+
|
79
|
+
next unless want?(vm, range, groups)
|
80
|
+
|
81
|
+
vms << vm['ID'].to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
@log.debug("Selected vms: #{vms}.")
|
85
|
+
vms
|
86
|
+
end
|
87
|
+
|
88
|
+
def want?(vm, range, groups)
|
89
|
+
if vm.nil?
|
90
|
+
@log.warn('Obtained nil vm from vm pool.')
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
# range restriction
|
94
|
+
unless range.nil? || range.empty?
|
95
|
+
return false if range[:from] && vm['STATE'] == STATE_DONE && vm['ETIME'].to_i < range[:from].to_i
|
96
|
+
return false if range[:to] && vm['STIME'].to_i > range[:to].to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
# groups restriction
|
100
|
+
unless groups.nil? || groups.empty?
|
101
|
+
return false if groups[:include] && !groups[:include].include?(vm['GNAME'])
|
102
|
+
return false if groups[:exclude] && groups[:exclude].include?(vm['GNAME'])
|
103
|
+
end
|
104
|
+
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
def load_vm_pool(batch_number)
|
109
|
+
fail ArgumentError, "#{batch_number} is not a valid number" unless is_number?(batch_number)
|
110
|
+
@log.debug("Loading vm pool with batch number: #{batch_number}.")
|
111
|
+
from = batch_number * @batch_size
|
112
|
+
to = (batch_number + 1) * @batch_size - 1
|
113
|
+
|
114
|
+
if @compatibility
|
115
|
+
unless @compatibility_vm_pool
|
116
|
+
vm_pool = OpenNebula::VirtualMachinePool.new(@client)
|
117
|
+
rc = vm_pool.info(OpenNebula::Pool::INFO_ALL, -1, -1, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
|
118
|
+
check_retval(rc, Errors::ResourceRetrievalError)
|
119
|
+
@compatibility_vm_pool = vm_pool.to_a
|
120
|
+
end
|
121
|
+
|
122
|
+
return @compatibility_vm_pool[from..to] || []
|
123
|
+
else
|
124
|
+
vm_pool = OpenNebula::VirtualMachinePool.new(@client)
|
125
|
+
rc = vm_pool.info(OpenNebula::Pool::INFO_ALL, from, to, OpenNebula::VirtualMachinePool::INFO_ALL_VM)
|
126
|
+
check_retval(rc, Errors::ResourceRetrievalError)
|
127
|
+
|
128
|
+
return vm_pool
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def check_retval(rc, e_klass)
|
133
|
+
return true unless OpenNebula.is_error?(rc)
|
134
|
+
case rc.errno
|
135
|
+
when OpenNebula::Error::EAUTHENTICATION
|
136
|
+
fail Errors::AuthenticationError, rc.message
|
137
|
+
when OpenNebula::Error::EAUTHORIZATION
|
138
|
+
fail Errors::UserNotAuthorizedError, rc.message
|
139
|
+
when OpenNebula::Error::ENO_EXISTS
|
140
|
+
fail Errors::ResourceNotFoundError, rc.message
|
141
|
+
when OpenNebula::Error::EACTION
|
142
|
+
fail Errors::ResourceStateError, rc.message
|
143
|
+
else
|
144
|
+
fail e_klass, rc.message
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/one_worker.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
file_dir_name = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(file_dir_name) unless $LOAD_PATH.include?(file_dir_name)
|
3
|
+
|
4
|
+
require 'sidekiq'
|
5
|
+
require 'one_data_accessor'
|
6
|
+
require 'one_writer'
|
7
|
+
require 'sidekiq_conf'
|
8
|
+
require 'oneacct_exporter/log'
|
9
|
+
require 'settings'
|
10
|
+
|
11
|
+
class OneWorker
|
12
|
+
include Sidekiq::Worker
|
13
|
+
|
14
|
+
sidekiq_options retry: 5, dead: false,\
|
15
|
+
queue: (Settings['sidekiq'] && Settings.sidekiq['queue']) ? Settings.sidekiq['queue'].to_sym : :default
|
16
|
+
|
17
|
+
B_IN_GB = 1_073_741_824
|
18
|
+
|
19
|
+
STRING = /[[:print:]]+/
|
20
|
+
NUMBER = /[[:digit:]]+/
|
21
|
+
NON_ZERO = /[1-9][[:digit:]]*/
|
22
|
+
STATES = %w(started started suspended started suspended suspended completed completed suspended)
|
23
|
+
|
24
|
+
def common_data
|
25
|
+
common_data = {}
|
26
|
+
common_data['endpoint'] = Settings['endpoint']
|
27
|
+
common_data['site_name'] = Settings['site_name']
|
28
|
+
common_data['cloud_type'] = Settings['cloud_type']
|
29
|
+
|
30
|
+
common_data
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_user_map(oda)
|
34
|
+
logger.debug('Creating user map.')
|
35
|
+
create_map(OpenNebula::UserPool, 'TEMPLATE/X509_DN', oda)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_image_map(oda)
|
39
|
+
logger.debug('Creating image map.')
|
40
|
+
create_map(OpenNebula::ImagePool, 'NAME', oda)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_map(pool_type, mapping, oda)
|
44
|
+
oda.mapping(pool_type, mapping)
|
45
|
+
rescue => e
|
46
|
+
msg = "Couldn't create map: #{e.message}. "\
|
47
|
+
'Stopping to avoid malformed records.'
|
48
|
+
logger.error(msg)
|
49
|
+
raise msg
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_vm(vm_id, oda)
|
53
|
+
oda.vm(vm_id)
|
54
|
+
rescue => e
|
55
|
+
logger.error("Couldn't retrieve data for vm with id: #{vm_id}. #{e.message}. Skipping.")
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_vm(vm, user_map, image_map)
|
60
|
+
data = common_data.clone
|
61
|
+
|
62
|
+
data['vm_uuid'] = parse(vm['ID'], STRING)
|
63
|
+
unless vm['STIME']
|
64
|
+
logger.error('Skipping a malformed record. '\
|
65
|
+
"VM with id #{data['vm_uuid']} has no StartTime.")
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
data['start_time'] = parse(vm['STIME'], NUMBER)
|
70
|
+
start_time = data['start_time'].to_i
|
71
|
+
if start_time == 0
|
72
|
+
logger.error('Skipping a malformed record. '\
|
73
|
+
"VM with id #{data['vm_uuid']} has malformed StartTime.")
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
data['start_time_readable'] = parse(Time.at(start_time).strftime('%F %T%:z'), STRING)
|
77
|
+
data['end_time'] = parse(vm['ETIME'], NON_ZERO)
|
78
|
+
end_time = data['end_time'].to_i
|
79
|
+
|
80
|
+
if end_time != 0 && start_time > end_time
|
81
|
+
logger.error('Skipping malformed record. '\
|
82
|
+
"VM with id #{data['vm_uuid']} has wrong time entries.")
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
data['machine_name'] = parse(vm['DEPLOY_ID'], STRING, "one-#{data['vm_uuid']}")
|
87
|
+
data['user_id'] = parse(vm['UID'], STRING)
|
88
|
+
data['group_id'] = parse(vm['GID'], STRING)
|
89
|
+
data['user_name'] = parse(user_map[data['user_id']], STRING)
|
90
|
+
data['fqan'] = parse(vm['GNAME'], STRING, nil)
|
91
|
+
|
92
|
+
if vm['STATE']
|
93
|
+
data['status'] = parse(STATES[vm['STATE'].to_i], STRING)
|
94
|
+
else
|
95
|
+
data['status'] = 'NULL'
|
96
|
+
end
|
97
|
+
|
98
|
+
unless vm['HISTORY_RECORDS/HISTORY[1]']
|
99
|
+
logger.warn('Skipping malformed record. '\
|
100
|
+
"VM with id #{data['vm_uuid']} has no history records.")
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
rstime = sum_rstime(vm)
|
105
|
+
return nil unless rstime
|
106
|
+
|
107
|
+
data['duration'] = parse(rstime.to_s, NON_ZERO)
|
108
|
+
|
109
|
+
suspend = (end_time - start_time) - data['duration'].to_i unless end_time == 0
|
110
|
+
data['suspend'] = parse(suspend.to_s, NUMBER)
|
111
|
+
|
112
|
+
vcpu = vm['TEMPLATE/VCPU']
|
113
|
+
data['cpu_count'] = parse(vcpu, NON_ZERO, '1')
|
114
|
+
|
115
|
+
net_tx = parse(vm['NET_TX'], NUMBER, 0)
|
116
|
+
data['network_inbound'] = (net_tx.to_i / B_IN_GB).round
|
117
|
+
net_rx = parse(vm['NET_RX'], NUMBER, 0)
|
118
|
+
data['network_outbound'] = (net_rx.to_i / B_IN_GB).round
|
119
|
+
|
120
|
+
data['memory'] = parse(vm['MEMORY'], NUMBER, '0')
|
121
|
+
data['image_name'] = parse(image_map[vm['TEMPLATE/DISK[1]/IMAGE_ID']], STRING)
|
122
|
+
|
123
|
+
data
|
124
|
+
end
|
125
|
+
|
126
|
+
def sum_rstime(vm)
|
127
|
+
rstime = 0
|
128
|
+
vm.each 'HISTORY_RECORDS/HISTORY' do |h|
|
129
|
+
next unless h['RSTIME'] && h['RETIME'] && h['RETIME'] != '0' && h['RSTIME'] != '0'
|
130
|
+
if h['RSTIME'].to_i > h['RETIME'].to_i
|
131
|
+
logger.warn('Skipping malformed record. '\
|
132
|
+
"VM with id #{vm['ID']} has wrong CpuDuration.")
|
133
|
+
rstime = nil
|
134
|
+
break
|
135
|
+
end
|
136
|
+
rstime += h['RETIME'].to_i - h['RSTIME'].to_i
|
137
|
+
end
|
138
|
+
|
139
|
+
rstime
|
140
|
+
end
|
141
|
+
|
142
|
+
def perform(vms, output)
|
143
|
+
OneacctExporter::Log.setup_log_level(logger)
|
144
|
+
|
145
|
+
vms = vms.split('|')
|
146
|
+
|
147
|
+
oda = OneDataAccessor.new(logger)
|
148
|
+
user_map = create_user_map(oda)
|
149
|
+
image_map = create_image_map(oda)
|
150
|
+
|
151
|
+
data = []
|
152
|
+
|
153
|
+
vms.each do |vm_id|
|
154
|
+
vm = load_vm(vm_id, oda)
|
155
|
+
next unless vm
|
156
|
+
|
157
|
+
logger.debug("Processing vm with id: #{vm_id}.")
|
158
|
+
vm_data = process_vm(vm, user_map, image_map)
|
159
|
+
next unless vm_data
|
160
|
+
|
161
|
+
logger.debug("Adding vm with data: #{vm_data} for export.")
|
162
|
+
data << vm_data
|
163
|
+
end
|
164
|
+
|
165
|
+
write_data(data, output)
|
166
|
+
end
|
167
|
+
|
168
|
+
def write_data(data, output)
|
169
|
+
logger.debug('Creating writer...')
|
170
|
+
ow = OneWriter.new(data, output, logger)
|
171
|
+
ow.write
|
172
|
+
rescue => e
|
173
|
+
msg = "Canno't write result to #{output}: #{e.message}"
|
174
|
+
logger.error(msg)
|
175
|
+
raise msg
|
176
|
+
end
|
177
|
+
|
178
|
+
def parse(value, regex, substitute = 'NULL')
|
179
|
+
regex =~ value ? value : substitute
|
180
|
+
end
|
181
|
+
end
|