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
data/lib/one_writer.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'erb'
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+ require 'settings'
5
+ require 'logger'
6
+
7
+ class OneWriter
8
+ attr_reader :data, :output, :log
9
+ def initialize(data, output, log = Logger.new(STDOUT))
10
+ fail ArgumentError, 'Data and output cannot be nil' if data.nil? || output.nil?
11
+
12
+ @template = OneWriter.template_filename(Settings.output['output_type']) if Settings['output']
13
+ fail ArgumentError, "No such file: #{@template}." unless File.exist?(@template)
14
+
15
+ @data = data
16
+ @output = output
17
+ @log = log
18
+ end
19
+
20
+ def write
21
+ @log.debug('Creating temporary file...')
22
+ tmp = Tempfile.new('oneacct_export')
23
+ @log.debug("Temporary file: '#{tmp.path}' created.")
24
+ @log.debug('Writing to temporary file...')
25
+ write_to_tmp(tmp, fill_template)
26
+ copy_to_output(tmp.path, @output)
27
+ ensure
28
+ tmp.close(true)
29
+ end
30
+
31
+ def write_to_tmp(tmp, data)
32
+ tmp.write(data)
33
+ tmp.flush
34
+ end
35
+
36
+ def copy_to_output(from, to)
37
+ @log.debug("Copying temporary file into '#{@output}'")
38
+ FileUtils.cp(from, to)
39
+ end
40
+
41
+ def fill_template
42
+ @log.debug("Reading erb template from file: '#{@template}'.")
43
+ erb = ERB.new(File.read(@template), nil, '-')
44
+ erb.filename = @template
45
+ erb.result(binding)
46
+ end
47
+
48
+ def self.template_filename(template_name)
49
+ "#{File.dirname(__FILE__)}/templates/#{template_name}.erb"
50
+ end
51
+ end
@@ -0,0 +1,88 @@
1
+ require 'oneacct_exporter/version'
2
+ require 'opennebula'
3
+ require 'one_worker'
4
+ require 'settings'
5
+ require 'sidekiq/api'
6
+
7
+ class OneacctExporter
8
+ CONVERT_FORMAT = '%014d'
9
+
10
+ attr_reader :log, :range, :groups, :blocking, :timeout, :compatibility
11
+
12
+ def initialize(options, log)
13
+ @log = log
14
+ @range = options[:range]
15
+ @groups = options[:groups]
16
+ @blocking = options[:blocking]
17
+ @timeout = options[:timeout]
18
+ @compatibility = options[:compatibility]
19
+ end
20
+
21
+ def export
22
+ @log.debug('Starting export...')
23
+
24
+ clean_output_dir
25
+
26
+ new_file_number = 1
27
+ batch_number = 0
28
+ oda = OneDataAccessor.new(@compatibility, @log)
29
+
30
+ vms = []
31
+ while vms = oda.vms(batch_number, @range, @groups)
32
+ output_file = CONVERT_FORMAT % new_file_number
33
+ @log.info("Starting worker with batch number: #{batch_number}.")
34
+ unless vms.empty?
35
+ OneWorker.perform_async(vms.join('|'), "#{Settings.output['output_dir']}/#{output_file}")
36
+ new_file_number += 1
37
+ end
38
+ batch_number += 1
39
+ end
40
+
41
+ @log.info('No more records to read.')
42
+
43
+ wait_for_processing if @blocking
44
+
45
+ @log.info('Exiting.')
46
+ rescue Errors::AuthenticationError, Errors::UserNotAuthorizedError,\
47
+ Errors::ResourceNotFoundError, Errors::ResourceStateError,\
48
+ Errors::ResourceRetrievalError => e
49
+ @log.error("Virtual machine retrieval for batch number #{batch_number} "\
50
+ "failed with error: #{e.message}. Exiting.")
51
+ end
52
+
53
+ def wait_for_processing
54
+ @log.info('Processing...')
55
+
56
+ end_time = Time.new + @timeout
57
+
58
+ until queue_empty? && all_workers_done?
59
+ if end_time < Time.new
60
+ @log.error("Processing time exceeded timeout of #{@timeout} seconds.")
61
+ break
62
+ end
63
+ sleep(5)
64
+ end
65
+
66
+ @log.info('All processing ended.')
67
+ end
68
+
69
+ def queue_empty?
70
+ queue = (Settings['sidekiq'] && Settings.sidekiq['queue']) ? Settings.sidekiq['queue'] : 'default'
71
+ Sidekiq::Stats.new.queues.each_pair do |queue_name, items_in_queue|
72
+ return items_in_queue == 0 if queue_name == queue
73
+ end
74
+
75
+ true
76
+ end
77
+
78
+ def all_workers_done?
79
+ Sidekiq::Workers.new.size == 0
80
+ end
81
+
82
+ def clean_output_dir
83
+ output_dir = Dir.new(Settings.output['output_dir'])
84
+ output_dir.entries.each do |entry|
85
+ File.delete("#{Settings.output['output_dir']}/#{entry}") if /[0-9]{14}/ =~ entry
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,15 @@
1
+ require 'logger'
2
+
3
+ class OneacctExporter
4
+ module Log
5
+ def self.setup_log_level(logger)
6
+ if ENV['ONEACCT_EXPORT_LOG_LEVEL'] && Logger::Severity.const_defined?(ENV['ONEACCT_EXPORT_LOG_LEVEL'])
7
+ logger.level = Logger::Severity.const_get ENV['ONEACCT_EXPORT_LOG_LEVEL']
8
+ else
9
+ logger.level = Logger::INFO
10
+ end
11
+
12
+ logger
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ class OneacctExporter
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,131 @@
1
+ require 'optparse'
2
+ require 'optparse/time'
3
+ require 'ostruct'
4
+ require 'oneacct_exporter'
5
+ require 'settings'
6
+
7
+ class OneacctOpts
8
+ BLOCKING_DEFAULT = false
9
+ TIMEOUT_DEFAULT = 60 * 60
10
+ COMPATIBILITY_DEFAULT = false
11
+
12
+ def self.parse(args)
13
+ options = OpenStruct.new
14
+
15
+ opt_parser = OptionParser.new do |opts|
16
+ opts.banner = 'Usage oneacct-export [options]'
17
+ opts.separator ''
18
+
19
+ opts.on('--records-from TIME', Time,
20
+ 'Retrieves only records newer than TIME') do |time|
21
+ options.records_from = time
22
+ end
23
+
24
+ opts.on('--records-to TIME', Time,
25
+ 'Retrieves only records older than TIME') do |time|
26
+ options.records_to = time
27
+ end
28
+
29
+ opts.on('--include-groups GROUP1[,GROUP2,...]', Array,
30
+ 'Retrieves only records of virtual machines which '\
31
+ 'belong to the specified groups') do |groups|
32
+ options.include_groups = groups
33
+ end
34
+
35
+ opts.on('--exclude-groups GROUP1[,GROUP2,...]', Array,
36
+ 'Retrieves only records of virtual machines which '\
37
+ "don't belong to the specified groups") do |groups|
38
+ options.exclude_groups = groups
39
+ end
40
+
41
+ opts.on('--group-file FILE',
42
+ 'If --include-groups or --exclude-groups specified, '\
43
+ 'loads groups from file FILE') do |file|
44
+ options.groups_file = file
45
+ end
46
+
47
+ opts.on('-b', '--[no-]blocking', 'Run in a blocking mode - '\
48
+ 'wait until all submitted jobs are processed') do |blocking|
49
+ options.blocking = blocking
50
+ end
51
+
52
+ opts.on('-t', '--timeout N', Integer, 'Timeout for blocking mode in seconds. '\
53
+ 'Default is 1 hour.') do |timeout|
54
+ options.timeout = timeout
55
+ end
56
+
57
+ opts.on('-c', '--[no-]compatibility-mode', 'Run in compatibility mode - '\
58
+ 'supports OpenNebula 4.4.x') do |compatibility|
59
+ options.compatibility = compatibility
60
+ end
61
+
62
+ opts.on_tail('-h', '--help', 'Shows this message') do
63
+ puts opts
64
+ exit
65
+ end
66
+
67
+ opts.on_tail('-v', '--version', 'Shows version') do
68
+ puts OneacctExporter::VERSION
69
+ exit
70
+ end
71
+ end
72
+
73
+ opt_parser.parse!(args)
74
+ set_defaults(options)
75
+
76
+ check_restrictions(options)
77
+
78
+ options
79
+ end
80
+
81
+ def self.set_defaults(options)
82
+ options.blocking = BLOCKING_DEFAULT unless options.blocking
83
+ unless options.timeout
84
+ options.timeout = TIMEOUT_DEFAULT if options.blocking
85
+ end
86
+ options.compatibility = COMPATIBILITY_DEFAULT unless options.compatibility
87
+ end
88
+
89
+ def self.check_restrictions(options)
90
+ check_options_restrictions(options)
91
+ check_settings_restrictions
92
+ end
93
+
94
+ def self.check_options_restrictions(options)
95
+ if options.records_from && options.records_to && options.records_from >= options.records_to
96
+ fail ArgumentError, 'Wrong time range for records retrieval.'
97
+ end
98
+
99
+ if options.include_groups && options.exclude_groups
100
+ fail ArgumentError, 'Mixing of group options is not possible.'
101
+ end
102
+
103
+ unless options.include_groups || options.exclude_groups
104
+ if options.groups_file
105
+ fail ArgumentError, 'Cannot use group file without specifying group restriction type.'
106
+ end
107
+ end
108
+
109
+ if options.timeout && !options.blocking
110
+ fail ArgumentError, 'Cannot set timeout without a blocking mode.'
111
+ end
112
+ end
113
+
114
+ def self.check_settings_restrictions
115
+ unless Settings['site_name'] && Settings['cloud_type'] && Settings['endpoint'] &&
116
+ Settings['output'] && Settings.output['output_dir'] && Settings.output['output_type']
117
+ fail ArgumentError, 'Missing some mandatory parameters. Check your configuration file.'
118
+ end
119
+ Settings['endpoint'].chop! if Settings['endpoint'].end_with?('/')
120
+
121
+ if Settings['logging'] && Settings.logging['log_type'] == 'file' &&
122
+ !Settings.logging['log_file']
123
+ fail ArgumentError, 'Missing file for logging. Check your configuration file.'
124
+ end
125
+
126
+ template_filename = OneWriter.template_filename(Settings.output['output_type'])
127
+ unless File.exist?(template_filename)
128
+ fail ArgumentError, "Non-existing template #{Settings.output['output_type']}."
129
+ end
130
+ end
131
+ end
data/lib/redis_conf.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'settings'
2
+ require 'uri'
3
+ require 'input_validator'
4
+
5
+ class RedisConf
6
+ extend InputValidator
7
+
8
+ def self.options
9
+ options = {}
10
+ if Settings['redis']
11
+ options[:namespace] = Settings.redis['namespace']
12
+ options[:url] = Settings.redis['url']
13
+ end
14
+
15
+ options[:namespace] ||= 'oneacct_export'
16
+ options[:url] ||= 'redis://localhost:6379'
17
+
18
+ fail ArgumentError, "#{options[:url]} is not a valid URL."\
19
+ unless is_uri?(options[:url])
20
+
21
+ if Settings['redis'] && Settings.redis['password']
22
+ fail ArgumentError, 'Redis password cannot be empty'\
23
+ if Settings.redis['password'].empty?
24
+ options[:url].insert(options[:url].index('/') + 2, ":#{Settings.redis['password']}@")
25
+ end
26
+
27
+ options
28
+ end
29
+ end
data/lib/settings.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'settingslogic'
2
+
3
+ class Settings < Settingslogic
4
+ CONF_NAME = 'conf.yml'
5
+
6
+ source "#{ENV['HOME']}/.oneacct-export/#{CONF_NAME}"\
7
+ if File.exist?("#{ENV['HOME']}/.oneacct-export/#{CONF_NAME}")
8
+ source "/etc/oneacct-export/#{CONF_NAME}"\
9
+ if File.exist?("/etc/oneacct-export/#{CONF_NAME}")
10
+ source "#{File.dirname(__FILE__)}/../config/#{CONF_NAME}"
11
+
12
+ namespace ENV['RAILS_ENV'] ? ENV['RAILS_ENV'] : 'production'
13
+ end
@@ -0,0 +1,11 @@
1
+ require 'sidekiq'
2
+ require 'redis_conf'
3
+
4
+ Sidekiq.configure_client do |config|
5
+ options = RedisConf.options
6
+ options[:size] = 1
7
+ config.redis = options
8
+ end
9
+ Sidekiq.configure_server do |config|
10
+ config.redis = RedisConf.options
11
+ end
@@ -0,0 +1,30 @@
1
+ APEL-cloud-message: v0.2
2
+ <% for vm in @data -%>
3
+ VMUUID: <%= vm['endpoint'] %>/compute/<%= vm['vm_uuid'] %> <%= vm['start_time_readable']%>
4
+ SiteName: <%= vm['site_name']%>
5
+ MachineName: <%= vm['machine_name']%>
6
+ LocalUserId: <%= vm['user_id']%>
7
+ LocalGroupId: <%= vm['group_id']%>
8
+ GlobalUserName: <%= vm['user_name']%>
9
+ <% if vm['fqan']-%>
10
+ FQAN: /<%= vm['fqan']%>/Role=NULL/Capability=NULL
11
+ <% else -%>
12
+ FQAN: NULL
13
+ <% end -%>
14
+ Status: <%= vm['status']%>
15
+ StartTime: <%= vm['start_time']%>
16
+ EndTime: <%= vm['end_time']%>
17
+ SuspendDuration: <%= vm['suspend']%>
18
+ WallDuration: <%= vm['duration']%>
19
+ CpuDuration: <%= vm['duration']%>
20
+ CpuCount: <%= vm['cpu_count']%>
21
+ NetworkType: NULL
22
+ NetworkInbound: <%= vm['network_inbound']%>
23
+ NetworkOutbound: <%= vm['network_outbound']%>
24
+ Memory: <%= vm['memory']%>
25
+ Disk: NULL
26
+ StorageRecordId: NULL
27
+ ImageId: <%= vm['image_name']%>
28
+ CloudType: OpenNebula
29
+ %%
30
+ <% end -%>
@@ -0,0 +1,136 @@
1
+ <VM>
2
+ <ID>36551</ID>
3
+ <UID>120</UID>
4
+ <GID>0</GID>
5
+ <UNAME>uname</UNAME>
6
+ <GNAME>gname</GNAME>
7
+ <NAME>one-36551</NAME>
8
+ <PERMISSIONS>
9
+ <OWNER_U>1</OWNER_U>
10
+ <OWNER_M>1</OWNER_M>
11
+ <OWNER_A>0</OWNER_A>
12
+ <GROUP_U>0</GROUP_U>
13
+ <GROUP_M>0</GROUP_M>
14
+ <GROUP_A>0</GROUP_A>
15
+ <OTHER_U>0</OTHER_U>
16
+ <OTHER_M>0</OTHER_M>
17
+ <OTHER_A>0</OTHER_A>
18
+ </PERMISSIONS>
19
+ <LAST_POLL>1383741679</LAST_POLL>
20
+ <STATE>6</STATE>
21
+ <LCM_STATE>0</LCM_STATE>
22
+ <RESCHED>0</RESCHED>
23
+ <STIME>1383741160</STIME>
24
+ <ETIME>1383742270</ETIME>
25
+ <MEMORY>1736960</MEMORY>
26
+ <CPU>0</CPU>
27
+ <NET_TX>0</NET_TX>
28
+ <NET_RX>0</NET_RX>
29
+ <TEMPLATE>
30
+ <CONTEXT>
31
+ <DISK_ID><![CDATA[1]]></DISK_ID>
32
+ <FILES><![CDATA[https://somewhere.com/init.sh]]></FILES>
33
+ <HOSTNAME><![CDATA[DebianVM]]></HOSTNAME>
34
+ <PUBLIC_IP><![CDATA[123.123.5.5]]></PUBLIC_IP>
35
+ <SSH_KEY><![CDATA[ssh-rsa AAAAB3NzaC1yc2EAAAADAQABasdgsdfhfgjhxhcHPNFthrHT5/+9lfrQCorJy5YjMJEGBC22dfgjghkDjSDFgjdhgKd54645dfJKd39LdlyzlEaCnjGUeD4C6tZdAISLSDfghSDFgSJ54sSFGJ5RTHARJIRJcX8QRVH46Zq02AeTgrty567ss34tqaa8Pt user@machine1.somewhere.com]]></SSH_KEY>
36
+ <TARGET><![CDATA[xvdb]]></TARGET>
37
+ </CONTEXT>
38
+ <CPU><![CDATA[0.5]]></CPU>
39
+ <DISK>
40
+ <BUS><![CDATA[ide]]></BUS>
41
+ <CLONE><![CDATA[YES]]></CLONE>
42
+ <CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
43
+ <DATASTORE><![CDATA[default]]></DATASTORE>
44
+ <DATASTORE_ID><![CDATA[1]]></DATASTORE_ID>
45
+ <DEV_PREFIX><![CDATA[xvd]]></DEV_PREFIX>
46
+ <DISK_ID><![CDATA[0]]></DISK_ID>
47
+ <DRIVER><![CDATA[tap2:tapdisk:aio:]]></DRIVER>
48
+ <IMAGE><![CDATA[debian6]]></IMAGE>
49
+ <IMAGE_ID><![CDATA[31]]></IMAGE_ID>
50
+ <READONLY><![CDATA[NO]]></READONLY>
51
+ <SAVE><![CDATA[NO]]></SAVE>
52
+ <SOURCE><![CDATA[/opt/opennebula/var/datastores/1/bc8db254875412f417ac745b7d47820]]></SOURCE>
53
+ <TARGET><![CDATA[xvda]]></TARGET>
54
+ <TM_MAD><![CDATA[ssh]]></TM_MAD>
55
+ <TYPE><![CDATA[FILE]]></TYPE>
56
+ </DISK>
57
+ <ERROR>
58
+ <MESSAGE><![CDATA[Error executing image transfer script: Path https://somewhere.com/init.sh is not allowed!]]></MESSAGE>
59
+ <TIMESTAMP><![CDATA[Wed Nov 6 13:34:19 2013]]></TIMESTAMP>
60
+ </ERROR>
61
+ <FEATURES>
62
+ <ACPI><![CDATA[no]]></ACPI>
63
+ </FEATURES>
64
+ <MEMORY>1736960</MEMORY>
65
+ <NAME><![CDATA[one-36551]]></NAME>
66
+ <NIC>
67
+ <BRIDGE><![CDATA[xenbr0]]></BRIDGE>
68
+ <CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
69
+ <IP><![CDATA[123.123.5.5]]></IP>
70
+ <MAC><![CDATA[01:23:45:67:89:ab]]></MAC>
71
+ <NETWORK><![CDATA[public]]></NETWORK>
72
+ <NETWORK_ID><![CDATA[4]]></NETWORK_ID>
73
+ <VLAN><![CDATA[NO]]></VLAN>
74
+ <WHITE_PORTS_TCP><![CDATA[22]]></WHITE_PORTS_TCP>
75
+ <WHITE_PORTS_UDP><![CDATA[67,68]]></WHITE_PORTS_UDP>
76
+ </NIC>
77
+ <OS>
78
+ <BOOTLOADER><![CDATA[pygrub]]></BOOTLOADER>
79
+ </OS>
80
+ <RAW>
81
+ <TYPE><![CDATA[xen]]></TYPE>
82
+ </RAW>
83
+ <TEMPLATE_ID><![CDATA[66]]></TEMPLATE_ID>
84
+ <VCPU><![CDATA[1]]></VCPU>
85
+ <VMID><![CDATA[36551]]></VMID>
86
+ </TEMPLATE>
87
+ <USER_TEMPLATE>
88
+ <SCHED_REQUIREMENTS><![CDATA[CLUSTER_ID = 100]]></SCHED_REQUIREMENTS>
89
+ </USER_TEMPLATE>
90
+ <HISTORY_RECORDS>
91
+ <HISTORY>
92
+ <OID>36551</OID>
93
+ <SEQ>0</SEQ>
94
+ <HOSTNAME>supermachine1.somewhere.com</HOSTNAME>
95
+ <HID>11</HID>
96
+ <CID>100</CID>
97
+ <STIME>1383741169</STIME>
98
+ <ETIME>1383741259</ETIME>
99
+ <VMMMAD>vmm_xen</VMMMAD>
100
+ <VNMMAD>fw</VNMMAD>
101
+ <TMMAD>ssh</TMMAD>
102
+ <DS_LOCATION>/opt/opennebula/var/datastores</DS_LOCATION>
103
+ <DS_ID>0</DS_ID>
104
+ <PSTIME>1383741169</PSTIME>
105
+ <PETIME>1383741259</PETIME>
106
+ <RSTIME>0</RSTIME>
107
+ <RETIME>0</RETIME>
108
+ <ESTIME>0</ESTIME>
109
+ <EETIME>0</EETIME>
110
+ <REASON>1</REASON>
111
+ <ACTION>0</ACTION>
112
+ </HISTORY>
113
+ <HISTORY>
114
+ <OID>36551</OID>
115
+ <SEQ>1</SEQ>
116
+ <HOSTNAME>supermachine1.somewhere.com</HOSTNAME>
117
+ <HID>11</HID>
118
+ <CID>100</CID>
119
+ <STIME>1383741589</STIME>
120
+ <ETIME>1383742270</ETIME>
121
+ <VMMMAD>vmm_xen</VMMMAD>
122
+ <VNMMAD>fw</VNMMAD>
123
+ <TMMAD>ssh</TMMAD>
124
+ <DS_LOCATION>/opt/opennebula/var/datastores</DS_LOCATION>
125
+ <DS_ID>0</DS_ID>
126
+ <PSTIME>1383741589</PSTIME>
127
+ <PETIME>1383741674</PETIME>
128
+ <RSTIME>1383741674</RSTIME>
129
+ <RETIME>1383742270</RETIME>
130
+ <ESTIME>1383742270</ESTIME>
131
+ <EETIME>1383742270</EETIME>
132
+ <REASON>0</REASON>
133
+ <ACTION>0</ACTION>
134
+ </HISTORY>
135
+ </HISTORY_RECORDS>
136
+ </VM>