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
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>