oneacct-export 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +36 -0
- data/.yardopts +1 -0
- data/README.md +119 -2
- data/bin/oneacct-export +7 -1
- data/config/conf.yml +18 -17
- data/lib/input_validator.rb +1 -0
- data/lib/one_data_accessor.rb +41 -0
- data/lib/one_worker.rb +54 -7
- data/lib/one_writer.rb +14 -0
- data/lib/oneacct_exporter.rb +15 -0
- data/lib/oneacct_exporter/version.rb +1 -1
- data/lib/oneacct_opts.rb +11 -0
- data/lib/redis_conf.rb +4 -0
- data/lib/settings.rb +3 -0
- data/lib/sidekiq_conf.rb +1 -0
- data/lib/templates/apel-0.2.erb +3 -3
- data/mock/one_worker_vm4.xml +106 -0
- data/mock/one_worker_vm5.xml +106 -0
- data/mock/one_worker_vm6.xml +107 -0
- data/oneacct-export.gemspec +6 -5
- data/spec/one_worker_spec.rb +43 -6
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e251289fc61da90a2cd802b46c6a9bb432a267b4
|
4
|
+
data.tar.gz: 1ee83f262bdf1234ce6e9c90665a8b4716293651
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fb58b1c9b0c824a1a1dbc3aecc6e43fcc96de03dc915235a09d6b36d7a44ddf6a7dbaf92724afbfadfeec47229bb78c060233389f5dc298450f098b59d0637f
|
7
|
+
data.tar.gz: b505e55867d9defd5682e9ea50a2a12ee8f5609ade511122d21bccca7b0abf14e07c92b7a59c2bb19982cd6c627dfdb6ccd4640f6cab65ddb2800001b2582715
|
data/.travis.yml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 2.0.0
|
5
|
+
- 2.1
|
6
|
+
- ruby-head
|
7
|
+
- jruby-19mode
|
8
|
+
- jruby-head
|
9
|
+
|
10
|
+
jdk:
|
11
|
+
- openjdk7
|
12
|
+
- oraclejdk7
|
13
|
+
- openjdk6
|
14
|
+
|
15
|
+
matrix:
|
16
|
+
allow_failures:
|
17
|
+
- rvm: ruby-head
|
18
|
+
- rvm: jruby-head
|
19
|
+
exclude:
|
20
|
+
- rvm: 2.0.0
|
21
|
+
jdk: openjdk7
|
22
|
+
- rvm: 2.0.0
|
23
|
+
jdk: oraclejdk7
|
24
|
+
- rvm: 2.1
|
25
|
+
jdk: openjdk7
|
26
|
+
- rvm: 2.1
|
27
|
+
jdk: oraclejdk7
|
28
|
+
- rvm: ruby-head
|
29
|
+
jdk: openjdk7
|
30
|
+
- rvm: ruby-head
|
31
|
+
jdk: oraclejdk7
|
32
|
+
fast_finish: true
|
33
|
+
|
34
|
+
branches:
|
35
|
+
only:
|
36
|
+
- master
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--title "Documentation of the OneacctExport gem" --markup markdown --private
|
data/README.md
CHANGED
@@ -1,9 +1,126 @@
|
|
1
1
|
# OneacctExport
|
2
|
-
|
3
2
|
Exporting OpenNebula accounting data.
|
4
3
|
|
5
|
-
|
4
|
+
[![Build Status](https://secure.travis-ci.org/Misenko/oneacct_export.png)](http://travis-ci.org/Misenko/oneacct_export)
|
5
|
+
[![Dependency Status](https://gemnasium.com/Misenko/oneacct_export.png)](https://gemnasium.com/Misenko/oneacct_export)
|
6
|
+
[![Gem Version](https://fury-badge.herokuapp.com/rb/oneacct-export.png)](https://badge.fury.io/rb/oneacct-export)
|
7
|
+
[![Code Climate](https://codeclimate.com/github/Misenko/oneacct_export.png)](https://codeclimate.com/github/Misenko/oneacct_export)
|
8
|
+
|
9
|
+
|
10
|
+
##Requirements
|
11
|
+
* Ruby >= 2.0
|
12
|
+
* Rubygems
|
13
|
+
* Redis server (doesn't have to be present on the same machine)
|
14
|
+
* OpenNebula >= 4.4 (doesn't have to be present on the same machine)
|
15
|
+
|
16
|
+
##Installation
|
17
|
+
###From distribution specific packages
|
18
|
+
Distribution specific packages can be created with [omnibus packaging for OneacctExport](https://github.com/Misenko/omnibus-oneacct-export). When installing via packages you don't have to install neither ruby nor
|
19
|
+
rubygems. Packages contain embedded ruby and all the necessary gems and libraries witch will not effect your system ruby, gems and libraries.
|
20
|
+
|
21
|
+
Currently supported distributions:
|
22
|
+
|
23
|
+
* Ubuntu 10.04
|
24
|
+
* Ubuntu 12.04
|
25
|
+
* Ubuntu 14.04
|
26
|
+
* Debian 6.0.10
|
27
|
+
* Debian 7.6
|
28
|
+
* CentOS 5.10
|
29
|
+
* CentOS 6.5
|
30
|
+
|
31
|
+
###From RubyGems.org
|
32
|
+
To install the most recent stable version
|
33
|
+
```bash
|
34
|
+
gem install oneacct-export
|
35
|
+
```
|
36
|
+
|
37
|
+
###From source (dev)
|
38
|
+
**Installation from source should never be your first choice! Especially, if you are not familiar with RVM, Bundler, Rake and other dev tools for Ruby!**
|
39
|
+
|
40
|
+
**However, if you wish to contribute to our project, this is the right way to start.**
|
41
|
+
|
42
|
+
To build and install the bleeding edge version from master
|
43
|
+
|
44
|
+
```bash
|
45
|
+
git clone git://github.com/Misenko/oneacct_export.git
|
46
|
+
cd oneacct_export
|
47
|
+
gem install bundler
|
48
|
+
bundle install
|
49
|
+
bundle exec rake spec
|
50
|
+
rake install
|
51
|
+
```
|
52
|
+
##Configuration
|
53
|
+
###Create a configuration file for OneacctExport
|
54
|
+
Configuration file can be read by OneacctExport from these three locations:
|
55
|
+
|
56
|
+
* ~/.oneacct-export/conf.yml
|
57
|
+
* /etc/oneacct-export/conf.yml
|
58
|
+
* <PATH_TO_GEM_DIR>/config/conf.yml
|
59
|
+
|
60
|
+
The example configuration file can be found at the last location <PATH_TO_GEM_DIR>/config/conf.yml. When editing a configuration file you have to follow the division into three environments: production,
|
61
|
+
development and test. All the configuration options are described in the example configuration file.
|
62
|
+
|
63
|
+
###Create a configuration file for Sidekiq
|
64
|
+
Sidekiq configuration file can be placed anywhere you want since you will provide path to the configuration later during the Sidekiq start. How the Sidekiq configuration should look like and what options you can use
|
65
|
+
can be found on its [wiki page](https://github.com/mperham/sidekiq/wiki/Advanced-Options).
|
66
|
+
|
67
|
+
The important thing is to set the same queue name in both OneacctExport and Sidekiq configuration files. OneacctExport is currently supporting adding jobs to only one queue.
|
68
|
+
|
69
|
+
###Configure RPC connection
|
70
|
+
RPC connection for OpenNebula can be configured in two ways:
|
6
71
|
|
72
|
+
* Via OneacctExport configuration file, option xml_rpc and its suboptions
|
73
|
+
* Via Opennebula configuration mechanism:
|
74
|
+
|
75
|
+
System environment variable ONE_AUTH contains path to the file containing string in format username:password to authenticate against OpenNebula. If the variable is empty, default file location is ~/.one/one_auth.
|
76
|
+
|
77
|
+
System environment variable ONE_XMLRPC contains URL of OpenNebula RPC gate. If empty, the same information can be stored in ~/.one/one_endpoint
|
78
|
+
|
79
|
+
###Set Rails environment variable according to your environment
|
80
|
+
You have to set system environment variable RAILS_ENV to one of the values production, development or test. OneacctExport is not a Rails application but we chose the Rails variable for easier possible integration in
|
81
|
+
the future.
|
82
|
+
|
83
|
+
##Usage
|
84
|
+
|
85
|
+
**Both Opennebula and Redis server must be running prior the next steps.**
|
86
|
+
|
87
|
+
###Start sidekiq
|
88
|
+
First you have to start Sidekiq so it can run the jobs from the queue. Since OneacctExport is not a Rails application Sidekiq has to be started with OneacctExport's worker class as an argument. For example:
|
89
|
+
|
90
|
+
```bash
|
91
|
+
sidekiq -r <PATH_TO_GEM_DIR>/lib/one_worker.rb -C <PATH_TO_SIDEKIQ_CONF>/sidekiq.yml
|
92
|
+
```
|
93
|
+
|
94
|
+
###Start OneacctExport
|
95
|
+
|
96
|
+
OneacctExport is run with executable `oneacct-export`. For a list of all available options run `oneacct-export -h`:
|
97
|
+
|
98
|
+
```
|
99
|
+
$ oneacct-export -h
|
100
|
+
|
101
|
+
Usage oneacct-export [options]
|
102
|
+
|
103
|
+
--records-from TIME Retrieves only records newer than TIME
|
104
|
+
--records-to TIME Retrieves only records older than TIME
|
105
|
+
--include-groups GROUP1[,GROUP2,...]
|
106
|
+
Retrieves only records of virtual machines which belong to the specified groups
|
107
|
+
--exclude-groups GROUP1[,GROUP2,...]
|
108
|
+
Retrieves only records of virtual machines which don't belong to the specified groups
|
109
|
+
--group-file FILE If --include-groups or --exclude-groups specified, loads groups from file FILE
|
110
|
+
-b, --[no-]blocking Run in a blocking mode - wait until all submitted jobs are processed
|
111
|
+
-t, --timeout N Timeout for blocking mode in seconds. Default is 1 hour.
|
112
|
+
-c, --[no-]compatibility-mode Run in compatibility mode - supports OpenNebula 4.4.x
|
113
|
+
-h, --help Shows this message
|
114
|
+
-v, --version Shows version
|
115
|
+
```
|
116
|
+
|
117
|
+
##Code Documentation
|
118
|
+
[Code Documentation for OneacctExport by YARD](http://rubydoc.info/github/Misenko/oneacct_export/)
|
119
|
+
|
120
|
+
##Continuous integration
|
121
|
+
[Continuous integration for OneacctExport by Travis-CI](http://travis-ci.org/Misenko/oneacct_export/)
|
122
|
+
|
123
|
+
## Contributing
|
7
124
|
1. Fork it ( https://github.com/Misenko/oneacct_export/fork )
|
8
125
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
9
126
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
data/bin/oneacct-export
CHANGED
@@ -8,16 +8,19 @@ require 'settings'
|
|
8
8
|
require 'fileutils'
|
9
9
|
require 'oneacct_opts'
|
10
10
|
|
11
|
+
#parse options from command line
|
11
12
|
options = OneacctOpts.parse(ARGV)
|
12
13
|
|
14
|
+
#initialize default logger
|
13
15
|
log = Logger.new(STDOUT)
|
14
16
|
|
17
|
+
#initialize specific logger according to the configuration
|
15
18
|
if Settings['logging'] && Settings['logging']['log_type'] == 'file'
|
16
19
|
begin
|
17
20
|
log_file = File.open(Settings['logging']['log_file'], File::WRONLY | File::CREAT | File::APPEND)
|
18
21
|
log = Logger.new(log_file)
|
19
22
|
rescue => e
|
20
|
-
OneacctExporter::Log.
|
23
|
+
OneacctExporter::Log.setup_log_level(log)
|
21
24
|
log.warn("Unable to create log file #{Settings['logging']['log_file']}: #{e.message}.\
|
22
25
|
Falling back to STDOUT.")
|
23
26
|
end
|
@@ -35,6 +38,7 @@ groups = {}
|
|
35
38
|
groups[:include] = options.include_groups if options.include_groups
|
36
39
|
groups[:exclude] = options.exclude_groups if options.exclude_groups
|
37
40
|
|
41
|
+
#read groups restriction from file if chosen
|
38
42
|
if options.groups_file
|
39
43
|
log.debug('Reading groups from file...')
|
40
44
|
if File.exist?(options.groups_file) && File.readable?(options.groups_file)
|
@@ -50,6 +54,7 @@ if options.groups_file
|
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
57
|
+
#create output directory
|
53
58
|
begin
|
54
59
|
FileUtils.mkdir_p Settings.output['output_dir']
|
55
60
|
rescue SystemCallError => e
|
@@ -68,5 +73,6 @@ opts[:compatibility] = options.compatibility
|
|
68
73
|
|
69
74
|
log.debug(opts)
|
70
75
|
|
76
|
+
#run the export
|
71
77
|
oneacct_exporter = OneacctExporter.new(opts, log)
|
72
78
|
oneacct_exporter.export
|
data/config/conf.yml
CHANGED
@@ -1,30 +1,31 @@
|
|
1
|
-
#
|
1
|
+
#There are six mandatory parameters:
|
2
2
|
# site_name
|
3
3
|
# cloud_type
|
4
4
|
# endpoint
|
5
5
|
# output -> output_dir
|
6
6
|
# output -> output_type
|
7
|
+
# sidekiq -> queue
|
7
8
|
defaults: &defaults
|
8
|
-
site_name: <fill in>
|
9
|
-
cloud_type:
|
10
|
-
endpoint: <fill in>
|
9
|
+
site_name: <fill in> #Usually a shor provider name, e.g. CESNET
|
10
|
+
cloud_type: OpenNebula
|
11
|
+
endpoint: <fill in> #URL of your OCCI endpoint, e.g. https://fqdn.example.com:80
|
11
12
|
output:
|
12
|
-
output_dir: <fill in>
|
13
|
-
output_type: apel-0.2 #currently only option
|
14
|
-
#
|
13
|
+
output_dir: <fill in> #Directory for outgoing messages
|
14
|
+
output_type: apel-0.2 #Format of outgoing messages. apel-0.2 is currently the only option.
|
15
|
+
# num_of_vms_per_file: 500 #Maximum number of virtual machine records per one output file.
|
15
16
|
#
|
16
|
-
#logging:
|
17
|
-
#
|
18
|
-
#
|
17
|
+
# logging:
|
18
|
+
# log_type: file #Two options: file, syslog. Defaults to stdout
|
19
|
+
# log_file: /var/log/oneacct-export.log #Used when type file selected
|
19
20
|
#
|
20
|
-
#xml_rpc:
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# xml_rpc:
|
22
|
+
# secret: username:password #If not specified looking for secret in ONE_AUTH and ~/.one/one_auth.
|
23
|
+
# endpoint: http://localhost:2633/RPC2 #Defaults to content of ONE_XMLRPC or content of ~/.one/one_endpoint or http://localhost:2633/RPC2.
|
23
24
|
#
|
24
|
-
#redis:
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
25
|
+
# redis:
|
26
|
+
# namespace: oneacct_export
|
27
|
+
# url: redis://localhost:6379 #URL of redis server, defaults to redis://localhost:6379.
|
28
|
+
# password: password #Password to access redis server if needed.
|
28
29
|
sidekiq:
|
29
30
|
queue: oneacct_export
|
30
31
|
production:
|
data/lib/input_validator.rb
CHANGED
data/lib/one_data_accessor.rb
CHANGED
@@ -4,6 +4,12 @@ require 'errors'
|
|
4
4
|
require 'logger'
|
5
5
|
require 'input_validator'
|
6
6
|
|
7
|
+
# Class for accessing OpenNebula via XML RPC and requesting accounting data
|
8
|
+
#
|
9
|
+
# @attr_reader [any logger] logger
|
10
|
+
# @attr_reader [Integer] batch_size number of vm records to request
|
11
|
+
# @attr_reader [OpenNebula::Client] client client for communicaton with OpenNebula
|
12
|
+
# @attr_reader [TrueClass, FalseClass] compatibility whether or not communicate in compatibility mode (omit some newer API functions)
|
7
13
|
class OneDataAccessor
|
8
14
|
include Errors
|
9
15
|
include InputValidator
|
@@ -24,6 +30,7 @@ class OneDataAccessor
|
|
24
30
|
initialize_client
|
25
31
|
end
|
26
32
|
|
33
|
+
# Initialize OpenNebula client for further connection
|
27
34
|
def initialize_client
|
28
35
|
secret = Settings['xml_rpc'] ? Settings.xml_rpc['secret'] : nil
|
29
36
|
endpoint = Settings['xml_rpc'] ? Settings.xml_rpc['endpoint'] : nil
|
@@ -32,9 +39,16 @@ class OneDataAccessor
|
|
32
39
|
@client = OpenNebula::Client.new(secret, endpoint)
|
33
40
|
end
|
34
41
|
|
42
|
+
# Create mapping from element's ID to its xpath value
|
43
|
+
#
|
44
|
+
# @param [one of OpenNebula pool classes] pool_class pool to read elements from
|
45
|
+
# @param [String] xpath xpath pointing to value within the element
|
46
|
+
#
|
47
|
+
# @return [Hash] generated map
|
35
48
|
def mapping(pool_class, xpath)
|
36
49
|
@log.debug("Generating mapping for class: #{pool_class} and xpath: '#{xpath}'.")
|
37
50
|
pool = pool_class.new(@client)
|
51
|
+
#call info_all method instead of info on pools that support it
|
38
52
|
if pool.respond_to? 'info_all'
|
39
53
|
rc = pool.info_all
|
40
54
|
check_retval(rc, Errors::ResourceRetrievalError)
|
@@ -43,6 +57,7 @@ class OneDataAccessor
|
|
43
57
|
check_retval(rc, Errors::ResourceRetrievalError)
|
44
58
|
end
|
45
59
|
|
60
|
+
#generate mapping
|
46
61
|
map = {}
|
47
62
|
pool.each do |item|
|
48
63
|
unless item['ID']
|
@@ -55,6 +70,11 @@ class OneDataAccessor
|
|
55
70
|
map
|
56
71
|
end
|
57
72
|
|
73
|
+
# Retrieve virtual machine
|
74
|
+
#
|
75
|
+
# @param [Integer] vm_id ID of vm to retrieve
|
76
|
+
#
|
77
|
+
# @return [OpenNebula::VirtualMachine] virtual machine
|
58
78
|
def vm(vm_id)
|
59
79
|
fail ArgumentError, "#{vm_id} is not a valid id." unless is_number?(vm_id)
|
60
80
|
@log.debug("Retrieving virtual machine with id: #{vm_id}.")
|
@@ -64,8 +84,16 @@ class OneDataAccessor
|
|
64
84
|
vm
|
65
85
|
end
|
66
86
|
|
87
|
+
# Retriev IDs of specified virtual machines
|
88
|
+
#
|
89
|
+
# @param [Integer] batch_number
|
90
|
+
# @param [Hash] range date range into which virtual machine has to belong
|
91
|
+
# @param [Hash] groups groups into one of which owner of the virtual machine has to belong
|
92
|
+
#
|
93
|
+
# @return [Array] array with virtual machines' IDs
|
67
94
|
def vms(batch_number, range, groups)
|
68
95
|
vms = []
|
96
|
+
#load specific batch
|
69
97
|
vm_pool = load_vm_pool(batch_number)
|
70
98
|
return nil if vm_pool.count == 0
|
71
99
|
|
@@ -76,6 +104,7 @@ class OneDataAccessor
|
|
76
104
|
next
|
77
105
|
end
|
78
106
|
|
107
|
+
#skip unsuitable virtual machines
|
79
108
|
next unless want?(vm, range, groups)
|
80
109
|
|
81
110
|
vms << vm['ID'].to_i
|
@@ -85,6 +114,13 @@ class OneDataAccessor
|
|
85
114
|
vms
|
86
115
|
end
|
87
116
|
|
117
|
+
# Check whether obtained vm meets requierements
|
118
|
+
#
|
119
|
+
# @param [OpenNebula::VirtualMachine] vm virtual machine instance to check
|
120
|
+
# @param [Hash] range date range into which virtual machine has to belong
|
121
|
+
# @param [Hash] groups groups into one of which owner of the virtual machine has to belong
|
122
|
+
#
|
123
|
+
# @return [TrueClass, FalseClass] true if virtual machine meets requirements, false otherwise
|
88
124
|
def want?(vm, range, groups)
|
89
125
|
if vm.nil?
|
90
126
|
@log.warn('Obtained nil vm from vm pool.')
|
@@ -105,12 +141,16 @@ class OneDataAccessor
|
|
105
141
|
true
|
106
142
|
end
|
107
143
|
|
144
|
+
# Load part of virtual machine pool
|
145
|
+
#
|
146
|
+
# @param [Integer] batch_number
|
108
147
|
def load_vm_pool(batch_number)
|
109
148
|
fail ArgumentError, "#{batch_number} is not a valid number" unless is_number?(batch_number)
|
110
149
|
@log.debug("Loading vm pool with batch number: #{batch_number}.")
|
111
150
|
from = batch_number * @batch_size
|
112
151
|
to = (batch_number + 1) * @batch_size - 1
|
113
152
|
|
153
|
+
#if in compatibility mode, whole virtual machine pool has to be loaded for the first time
|
114
154
|
if @compatibility
|
115
155
|
unless @compatibility_vm_pool
|
116
156
|
vm_pool = OpenNebula::VirtualMachinePool.new(@client)
|
@@ -129,6 +169,7 @@ class OneDataAccessor
|
|
129
169
|
end
|
130
170
|
end
|
131
171
|
|
172
|
+
# Check OpenNebula return codes
|
132
173
|
def check_retval(rc, e_klass)
|
133
174
|
return true unless OpenNebula.is_error?(rc)
|
134
175
|
case rc.errno
|
data/lib/one_worker.rb
CHANGED
@@ -8,6 +8,7 @@ require 'sidekiq_conf'
|
|
8
8
|
require 'oneacct_exporter/log'
|
9
9
|
require 'settings'
|
10
10
|
|
11
|
+
# Sidekiq worker class
|
11
12
|
class OneWorker
|
12
13
|
include Sidekiq::Worker
|
13
14
|
|
@@ -21,6 +22,7 @@ class OneWorker
|
|
21
22
|
NON_ZERO = /[1-9][[:digit:]]*/
|
22
23
|
STATES = %w(started started suspended started suspended suspended completed completed suspended)
|
23
24
|
|
25
|
+
# Prepare data that are common for every virtual machine
|
24
26
|
def common_data
|
25
27
|
common_data = {}
|
26
28
|
common_data['endpoint'] = Settings['endpoint']
|
@@ -30,25 +32,35 @@ class OneWorker
|
|
30
32
|
common_data
|
31
33
|
end
|
32
34
|
|
35
|
+
# Create mapping of user ID and specified element
|
36
|
+
#
|
37
|
+
# @return [Hash] created map
|
33
38
|
def create_user_map(oda)
|
34
39
|
logger.debug('Creating user map.')
|
35
40
|
create_map(OpenNebula::UserPool, 'TEMPLATE/X509_DN', oda)
|
36
41
|
end
|
37
42
|
|
43
|
+
# Create mapping of image ID and specified element
|
44
|
+
#
|
45
|
+
# @return [Hash] created map
|
38
46
|
def create_image_map(oda)
|
39
47
|
logger.debug('Creating image map.')
|
40
|
-
create_map(OpenNebula::ImagePool, '
|
48
|
+
create_map(OpenNebula::ImagePool, 'TEMPLATE/VMCATCHER_EVENT_AD_MPURI', oda)
|
41
49
|
end
|
42
50
|
|
51
|
+
# Generic method for mapping creation
|
43
52
|
def create_map(pool_type, mapping, oda)
|
44
53
|
oda.mapping(pool_type, mapping)
|
45
54
|
rescue => e
|
46
55
|
msg = "Couldn't create map: #{e.message}. "\
|
47
|
-
|
56
|
+
'Stopping to avoid malformed records.'
|
48
57
|
logger.error(msg)
|
49
58
|
raise msg
|
50
59
|
end
|
51
60
|
|
61
|
+
# Load virtual machine with specified ID
|
62
|
+
#
|
63
|
+
# @return [OpenNebula::VirtualMachine] virtual machine
|
52
64
|
def load_vm(vm_id, oda)
|
53
65
|
oda.vm(vm_id)
|
54
66
|
rescue => e
|
@@ -56,6 +68,9 @@ class OneWorker
|
|
56
68
|
return nil
|
57
69
|
end
|
58
70
|
|
71
|
+
# Obtain and parse required data from vm
|
72
|
+
#
|
73
|
+
# @return [Hash] required data from virtual machine
|
59
74
|
def process_vm(vm, user_map, image_map)
|
60
75
|
data = common_data.clone
|
61
76
|
|
@@ -66,16 +81,16 @@ class OneWorker
|
|
66
81
|
return nil
|
67
82
|
end
|
68
83
|
|
69
|
-
data['start_time'] = parse(vm['STIME'], NUMBER)
|
84
|
+
data['start_time'] = Time.at(parse(vm['STIME'], NUMBER).to_i)
|
70
85
|
start_time = data['start_time'].to_i
|
71
86
|
if start_time == 0
|
72
87
|
logger.error('Skipping a malformed record. '\
|
73
88
|
"VM with id #{data['vm_uuid']} has malformed StartTime.")
|
74
89
|
return nil
|
75
90
|
end
|
76
|
-
data['start_time_readable'] = parse(Time.at(start_time).strftime('%F %T%:z'), STRING)
|
77
91
|
data['end_time'] = parse(vm['ETIME'], NON_ZERO)
|
78
92
|
end_time = data['end_time'].to_i
|
93
|
+
data['end_time'] = Time.at(end_time) if end_time != 0
|
79
94
|
|
80
95
|
if end_time != 0 && start_time > end_time
|
81
96
|
logger.error('Skipping malformed record. '\
|
@@ -86,7 +101,8 @@ class OneWorker
|
|
86
101
|
data['machine_name'] = parse(vm['DEPLOY_ID'], STRING, "one-#{data['vm_uuid']}")
|
87
102
|
data['user_id'] = parse(vm['UID'], STRING)
|
88
103
|
data['group_id'] = parse(vm['GID'], STRING)
|
89
|
-
data['user_name'] = parse(
|
104
|
+
data['user_name'] = parse(vm['USER_TEMPLATE/USER_X509_DN'], STRING, nil)
|
105
|
+
data['user_name'] = parse(user_map[data['user_id']], STRING) unless data['user_name']
|
90
106
|
data['fqan'] = parse(vm['GNAME'], STRING, nil)
|
91
107
|
|
92
108
|
if vm['STATE']
|
@@ -118,11 +134,37 @@ class OneWorker
|
|
118
134
|
data['network_outbound'] = (net_rx.to_i / B_IN_GB).round
|
119
135
|
|
120
136
|
data['memory'] = parse(vm['MEMORY'], NUMBER, '0')
|
121
|
-
|
137
|
+
|
138
|
+
data['image_name'] = parse(image_map[vm['TEMPLATE/DISK[1]/IMAGE_ID']], STRING, nil)
|
139
|
+
data['image_name'] = parse(mixin(vm), STRING) unless data['image_name']
|
122
140
|
|
123
141
|
data
|
124
142
|
end
|
125
143
|
|
144
|
+
# Look for 'os_tpl' OCCI mixin to better identifie virtual machine's image
|
145
|
+
#
|
146
|
+
# @param [OpenNebula::VirtualMachine] vm virtual machine
|
147
|
+
#
|
148
|
+
# @return [NilClass, String] if found, mixin identifying string, nil otherwise
|
149
|
+
def mixin(vm)
|
150
|
+
mixin_locations = %w(USER_TEMPLATE/OCCI_COMPUTE_MIXINS USER_TEMPLATE/OCCI_MIXIN TEMPLATE/OCCI_MIXIN)
|
151
|
+
|
152
|
+
mixin_locations.each do |mixin_location|
|
153
|
+
vm.each mixin_location do |mixin|
|
154
|
+
mixin = mixin.text.split
|
155
|
+
mixin.select! { |line| line.include? '/occi/infrastructure/os_tpl#' }
|
156
|
+
return mixin.first unless mixin.empty?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
nil # nothing found
|
161
|
+
end
|
162
|
+
|
163
|
+
# Sums RSTIME (time when virtual machine was actually running)
|
164
|
+
#
|
165
|
+
# @param [OpenNebula::VirtualMachine] vm virtual machine
|
166
|
+
#
|
167
|
+
# @return [Integer] RSTIME
|
126
168
|
def sum_rstime(vm)
|
127
169
|
rstime = 0
|
128
170
|
vm.each 'HISTORY_RECORDS/HISTORY' do |h|
|
@@ -139,6 +181,10 @@ class OneWorker
|
|
139
181
|
rstime
|
140
182
|
end
|
141
183
|
|
184
|
+
# Sidekiq specific method, specifies the purpose of the worker
|
185
|
+
#
|
186
|
+
# @param [String] vms IDs of virtual machines to process in form of numbers separated by '|' (easier for cooperation with redis)
|
187
|
+
# @param [String] output output directory
|
142
188
|
def perform(vms, output)
|
143
189
|
OneacctExporter::Log.setup_log_level(logger)
|
144
190
|
|
@@ -165,12 +211,13 @@ class OneWorker
|
|
165
211
|
write_data(data, output)
|
166
212
|
end
|
167
213
|
|
214
|
+
# Write processed data into output directory
|
168
215
|
def write_data(data, output)
|
169
216
|
logger.debug('Creating writer...')
|
170
217
|
ow = OneWriter.new(data, output, logger)
|
171
218
|
ow.write
|
172
219
|
rescue => e
|
173
|
-
msg = "
|
220
|
+
msg = "Cannot write result to #{output}: #{e.message}"
|
174
221
|
logger.error(msg)
|
175
222
|
raise msg
|
176
223
|
end
|
data/lib/one_writer.rb
CHANGED
@@ -4,8 +4,15 @@ require 'fileutils'
|
|
4
4
|
require 'settings'
|
5
5
|
require 'logger'
|
6
6
|
|
7
|
+
# Class responsible for writing data into files in specific format
|
8
|
+
#
|
9
|
+
# @attr_reader [Hash] data vm data
|
10
|
+
# @attr_reader [String] output path to the output directory
|
11
|
+
# @attr_reader [any logger] logger
|
7
12
|
class OneWriter
|
13
|
+
|
8
14
|
attr_reader :data, :output, :log
|
15
|
+
|
9
16
|
def initialize(data, output, log = Logger.new(STDOUT))
|
10
17
|
fail ArgumentError, 'Data and output cannot be nil' if data.nil? || output.nil?
|
11
18
|
|
@@ -17,6 +24,7 @@ class OneWriter
|
|
17
24
|
@log = log
|
18
25
|
end
|
19
26
|
|
27
|
+
# Write data to file in output directory
|
20
28
|
def write
|
21
29
|
@log.debug('Creating temporary file...')
|
22
30
|
tmp = Tempfile.new('oneacct_export')
|
@@ -38,6 +46,9 @@ class OneWriter
|
|
38
46
|
FileUtils.cp(from, to)
|
39
47
|
end
|
40
48
|
|
49
|
+
# Prepare file content according to ERB template
|
50
|
+
#
|
51
|
+
# @return [String] transformed content
|
41
52
|
def fill_template
|
42
53
|
@log.debug("Reading erb template from file: '#{@template}'.")
|
43
54
|
erb = ERB.new(File.read(@template), nil, '-')
|
@@ -45,6 +56,9 @@ class OneWriter
|
|
45
56
|
erb.result(binding)
|
46
57
|
end
|
47
58
|
|
59
|
+
# Load template for data conversion
|
60
|
+
#
|
61
|
+
# @param [String] template_name name of the template to look for
|
48
62
|
def self.template_filename(template_name)
|
49
63
|
"#{File.dirname(__FILE__)}/templates/#{template_name}.erb"
|
50
64
|
end
|
data/lib/oneacct_exporter.rb
CHANGED
@@ -4,6 +4,14 @@ require 'one_worker'
|
|
4
4
|
require 'settings'
|
5
5
|
require 'sidekiq/api'
|
6
6
|
|
7
|
+
# Class managing the export
|
8
|
+
#
|
9
|
+
# @attr_reader [any logger] log logger for the class
|
10
|
+
# @attr_reader [Hash] range range of dates, requesting only virtual machines within the range
|
11
|
+
# @attr_reader [Hash] groups user groups, requesting only virtual machines with owners that belong to one of the group
|
12
|
+
# @attr_reader [TrueClass, FalseClass] blocking says whether to run export in blocking mode or not
|
13
|
+
# @attr_reader [Integer] timeout timeout for blocking mode
|
14
|
+
# @attr_reader [TrueClass, FalseClass] compatibility says whether to run export in compatibility mode or not
|
7
15
|
class OneacctExporter
|
8
16
|
CONVERT_FORMAT = '%014d'
|
9
17
|
|
@@ -18,6 +26,7 @@ class OneacctExporter
|
|
18
26
|
@compatibility = options[:compatibility]
|
19
27
|
end
|
20
28
|
|
29
|
+
# Start export the records
|
21
30
|
def export
|
22
31
|
@log.debug('Starting export...')
|
23
32
|
|
@@ -28,10 +37,12 @@ class OneacctExporter
|
|
28
37
|
oda = OneDataAccessor.new(@compatibility, @log)
|
29
38
|
|
30
39
|
vms = []
|
40
|
+
#load records of virtual machines in batches
|
31
41
|
while vms = oda.vms(batch_number, @range, @groups)
|
32
42
|
output_file = CONVERT_FORMAT % new_file_number
|
33
43
|
@log.info("Starting worker with batch number: #{batch_number}.")
|
34
44
|
unless vms.empty?
|
45
|
+
#add a new job for every batch to the Sidekiq's queue
|
35
46
|
OneWorker.perform_async(vms.join('|'), "#{Settings.output['output_dir']}/#{output_file}")
|
36
47
|
new_file_number += 1
|
37
48
|
end
|
@@ -50,6 +61,7 @@ class OneacctExporter
|
|
50
61
|
"failed with error: #{e.message}. Exiting.")
|
51
62
|
end
|
52
63
|
|
64
|
+
# When in blocking mode, wait for processing of records to finish
|
53
65
|
def wait_for_processing
|
54
66
|
@log.info('Processing...')
|
55
67
|
|
@@ -66,6 +78,7 @@ class OneacctExporter
|
|
66
78
|
@log.info('All processing ended.')
|
67
79
|
end
|
68
80
|
|
81
|
+
# Check whether Sidekiq's queue is empty
|
69
82
|
def queue_empty?
|
70
83
|
queue = (Settings['sidekiq'] && Settings.sidekiq['queue']) ? Settings.sidekiq['queue'] : 'default'
|
71
84
|
Sidekiq::Stats.new.queues.each_pair do |queue_name, items_in_queue|
|
@@ -75,10 +88,12 @@ class OneacctExporter
|
|
75
88
|
true
|
76
89
|
end
|
77
90
|
|
91
|
+
# Check whether all Sidekiq workers have finished thair work
|
78
92
|
def all_workers_done?
|
79
93
|
Sidekiq::Workers.new.size == 0
|
80
94
|
end
|
81
95
|
|
96
|
+
# Clean output directory of previous entries
|
82
97
|
def clean_output_dir
|
83
98
|
output_dir = Dir.new(Settings.output['output_dir'])
|
84
99
|
output_dir.entries.each do |entry|
|
data/lib/oneacct_opts.rb
CHANGED
@@ -4,6 +4,7 @@ require 'ostruct'
|
|
4
4
|
require 'oneacct_exporter'
|
5
5
|
require 'settings'
|
6
6
|
|
7
|
+
# Class for parsing command line arguments
|
7
8
|
class OneacctOpts
|
8
9
|
BLOCKING_DEFAULT = false
|
9
10
|
TIMEOUT_DEFAULT = 60 * 60
|
@@ -78,6 +79,7 @@ class OneacctOpts
|
|
78
79
|
options
|
79
80
|
end
|
80
81
|
|
82
|
+
# Set default values for not specified options
|
81
83
|
def self.set_defaults(options)
|
82
84
|
options.blocking = BLOCKING_DEFAULT unless options.blocking
|
83
85
|
unless options.timeout
|
@@ -91,38 +93,47 @@ class OneacctOpts
|
|
91
93
|
check_settings_restrictions
|
92
94
|
end
|
93
95
|
|
96
|
+
# Make sure command line parameters are sane
|
94
97
|
def self.check_options_restrictions(options)
|
98
|
+
#make sure date range make sense
|
95
99
|
if options.records_from && options.records_to && options.records_from >= options.records_to
|
96
100
|
fail ArgumentError, 'Wrong time range for records retrieval.'
|
97
101
|
end
|
98
102
|
|
103
|
+
#make sure only one group restriction is used
|
99
104
|
if options.include_groups && options.exclude_groups
|
100
105
|
fail ArgumentError, 'Mixing of group options is not possible.'
|
101
106
|
end
|
102
107
|
|
108
|
+
#make sure group file option is not used without specifying group restriction type
|
103
109
|
unless options.include_groups || options.exclude_groups
|
104
110
|
if options.groups_file
|
105
111
|
fail ArgumentError, 'Cannot use group file without specifying group restriction type.'
|
106
112
|
end
|
107
113
|
end
|
108
114
|
|
115
|
+
#make sure that timeout option is not used without blocking option
|
109
116
|
if options.timeout && !options.blocking
|
110
117
|
fail ArgumentError, 'Cannot set timeout without a blocking mode.'
|
111
118
|
end
|
112
119
|
end
|
113
120
|
|
121
|
+
# Make sure configuration is sane
|
114
122
|
def self.check_settings_restrictions
|
123
|
+
#make sure all mandatory parameters are set
|
115
124
|
unless Settings['site_name'] && Settings['cloud_type'] && Settings['endpoint'] &&
|
116
125
|
Settings['output'] && Settings.output['output_dir'] && Settings.output['output_type']
|
117
126
|
fail ArgumentError, 'Missing some mandatory parameters. Check your configuration file.'
|
118
127
|
end
|
119
128
|
Settings['endpoint'].chop! if Settings['endpoint'].end_with?('/')
|
120
129
|
|
130
|
+
#make sure log file is specified while loggin to file
|
121
131
|
if Settings['logging'] && Settings.logging['log_type'] == 'file' &&
|
122
132
|
!Settings.logging['log_file']
|
123
133
|
fail ArgumentError, 'Missing file for logging. Check your configuration file.'
|
124
134
|
end
|
125
135
|
|
136
|
+
#make sure specified template really exists
|
126
137
|
template_filename = OneWriter.template_filename(Settings.output['output_type'])
|
127
138
|
unless File.exist?(template_filename)
|
128
139
|
fail ArgumentError, "Non-existing template #{Settings.output['output_type']}."
|
data/lib/redis_conf.rb
CHANGED
@@ -2,9 +2,13 @@ require 'settings'
|
|
2
2
|
require 'uri'
|
3
3
|
require 'input_validator'
|
4
4
|
|
5
|
+
# Class that deals with Redis server configuration options
|
5
6
|
class RedisConf
|
6
7
|
extend InputValidator
|
7
8
|
|
9
|
+
# Read and parse Redis server configuration options
|
10
|
+
#
|
11
|
+
# @return [Hash] redis server options ready for use
|
8
12
|
def self.options
|
9
13
|
options = {}
|
10
14
|
if Settings['redis']
|
data/lib/settings.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'settingslogic'
|
2
2
|
|
3
|
+
# Class representing OneacctExport settings
|
3
4
|
class Settings < Settingslogic
|
4
5
|
CONF_NAME = 'conf.yml'
|
5
6
|
|
7
|
+
#three possible configuration file locations in order by preference
|
8
|
+
#if configuration file is found rest of the locations are ignored
|
6
9
|
source "#{ENV['HOME']}/.oneacct-export/#{CONF_NAME}"\
|
7
10
|
if File.exist?("#{ENV['HOME']}/.oneacct-export/#{CONF_NAME}")
|
8
11
|
source "/etc/oneacct-export/#{CONF_NAME}"\
|
data/lib/sidekiq_conf.rb
CHANGED
data/lib/templates/apel-0.2.erb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
APEL-cloud-message: v0.2
|
2
2
|
<% for vm in @data -%>
|
3
|
-
VMUUID: <%= vm['endpoint'] %>/compute/<%= vm['vm_uuid'] %> <%= vm['
|
3
|
+
VMUUID: <%= vm['endpoint'] %>/compute/<%= vm['vm_uuid'] %> <%= vm['start_time'].strftime('%F %T%:z') %>
|
4
4
|
SiteName: <%= vm['site_name']%>
|
5
5
|
MachineName: <%= vm['machine_name']%>
|
6
6
|
LocalUserId: <%= vm['user_id']%>
|
@@ -12,8 +12,8 @@ FQAN: /<%= vm['fqan']%>/Role=NULL/Capability=NULL
|
|
12
12
|
FQAN: NULL
|
13
13
|
<% end -%>
|
14
14
|
Status: <%= vm['status']%>
|
15
|
-
StartTime: <%= vm['start_time']%>
|
16
|
-
EndTime: <%= vm['end_time']%>
|
15
|
+
StartTime: <%= vm['start_time'].to_i%>
|
16
|
+
EndTime: <%= vm['end_time'].to_i%>
|
17
17
|
SuspendDuration: <%= vm['suspend']%>
|
18
18
|
WallDuration: <%= vm['duration']%>
|
19
19
|
CpuDuration: <%= vm['duration']%>
|
@@ -0,0 +1,106 @@
|
|
1
|
+
<VM>
|
2
|
+
<ID>46083</ID>
|
3
|
+
<UID>120</UID>
|
4
|
+
<GID>103</GID>
|
5
|
+
<UNAME>local_ops</UNAME>
|
6
|
+
<GNAME>ops</GNAME>
|
7
|
+
<NAME>EGI-SAM</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>1414588018</LAST_POLL>
|
20
|
+
<STATE>3</STATE>
|
21
|
+
<LCM_STATE>12</LCM_STATE>
|
22
|
+
<RESCHED>0</RESCHED>
|
23
|
+
<STIME>1414587675</STIME>
|
24
|
+
<ETIME>0</ETIME>
|
25
|
+
<DEPLOY_ID>one-46083</DEPLOY_ID>
|
26
|
+
<MEMORY>2097152</MEMORY>
|
27
|
+
<CPU>0</CPU>
|
28
|
+
<NET_TX>3072</NET_TX>
|
29
|
+
<NET_RX>0</NET_RX>
|
30
|
+
<TEMPLATE>
|
31
|
+
<AUTOMATIC_REQUIREMENTS><![CDATA[CLUSTER_ID = 100 & !(PUBLIC_CLOUD = YES)]]></AUTOMATIC_REQUIREMENTS>
|
32
|
+
<CPU><![CDATA[1]]></CPU>
|
33
|
+
<DISK>
|
34
|
+
<BUS><![CDATA[ide]]></BUS>
|
35
|
+
<CLONE><![CDATA[YES]]></CLONE>
|
36
|
+
<CLONE_TARGET><![CDATA[SYSTEM]]></CLONE_TARGET>
|
37
|
+
<CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
|
38
|
+
<DATASTORE><![CDATA[default]]></DATASTORE>
|
39
|
+
<DATASTORE_ID><![CDATA[1]]></DATASTORE_ID>
|
40
|
+
<DEV_PREFIX><![CDATA[xvd]]></DEV_PREFIX>
|
41
|
+
<DISK_ID><![CDATA[0]]></DISK_ID>
|
42
|
+
<DRIVER><![CDATA[tap2:tapdisk:aio:]]></DRIVER>
|
43
|
+
<IMAGE><![CDATA[monitoring]]></IMAGE>
|
44
|
+
<IMAGE_ID><![CDATA[31]]></IMAGE_ID>
|
45
|
+
<LN_TARGET><![CDATA[SYSTEM]]></LN_TARGET>
|
46
|
+
<READONLY><![CDATA[NO]]></READONLY>
|
47
|
+
<SAVE><![CDATA[NO]]></SAVE>
|
48
|
+
<SIZE><![CDATA[106]]></SIZE>
|
49
|
+
<SOURCE><![CDATA[/opt/opennebula/var/datastores/1/a8e87be2fdafadadd5575d73d4946d47]]></SOURCE>
|
50
|
+
<TARGET><![CDATA[xvda]]></TARGET>
|
51
|
+
<TM_MAD><![CDATA[ssh]]></TM_MAD>
|
52
|
+
<TYPE><![CDATA[FILE]]></TYPE>
|
53
|
+
</DISK>
|
54
|
+
<MEMORY><![CDATA[2048]]></MEMORY>
|
55
|
+
<NAME><![CDATA[one-46083]]></NAME>
|
56
|
+
<NIC>
|
57
|
+
<BRIDGE><![CDATA[xenbr0]]></BRIDGE>
|
58
|
+
<CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
|
59
|
+
<IP><![CDATA[192.168.254.26]]></IP>
|
60
|
+
<IP6_LINK><![CDATA[fe80::400:c0ff:fea8:fe1a]]></IP6_LINK>
|
61
|
+
<MAC><![CDATA[02:00:c0:a8:fe:1a]]></MAC>
|
62
|
+
<NETWORK><![CDATA[monitoring]]></NETWORK>
|
63
|
+
<NETWORK_ID><![CDATA[6]]></NETWORK_ID>
|
64
|
+
<NIC_ID><![CDATA[0]]></NIC_ID>
|
65
|
+
<VLAN><![CDATA[NO]]></VLAN>
|
66
|
+
</NIC>
|
67
|
+
<OS>
|
68
|
+
<BOOTLOADER><![CDATA[pygrub]]></BOOTLOADER>
|
69
|
+
</OS>
|
70
|
+
<RAW>
|
71
|
+
<TYPE><![CDATA[xen]]></TYPE>
|
72
|
+
</RAW>
|
73
|
+
<VCPU><![CDATA[1]]></VCPU>
|
74
|
+
<VMID><![CDATA[46083]]></VMID>
|
75
|
+
</TEMPLATE>
|
76
|
+
<USER_TEMPLATE>
|
77
|
+
<ARCHITECTURE><![CDATA[x86]]></ARCHITECTURE>
|
78
|
+
<DESCRIPTION><![CDATA[Instantiated with rOCCI-server on Wed, 29 Oct 2014 14:01:15 +0100.]]></DESCRIPTION>
|
79
|
+
<OCCI_COMPUTE_MIXINS><![CDATA[http://occi.localhost/occi/infrastructure/os_tpl#uuid_monitoring_20 http://schema.fedcloud.egi.eu/occi/infrastructure/resource_tpl#small http://opennebula.org/occi/infrastructure#compute]]></OCCI_COMPUTE_MIXINS>
|
80
|
+
<OCCI_ID><![CDATA[46083]]></OCCI_ID>
|
81
|
+
</USER_TEMPLATE>
|
82
|
+
<HISTORY_RECORDS>
|
83
|
+
<HISTORY>
|
84
|
+
<OID>46083</OID>
|
85
|
+
<SEQ>0</SEQ>
|
86
|
+
<HOSTNAME>localhost</HOSTNAME>
|
87
|
+
<HID>17</HID>
|
88
|
+
<CID>100</CID>
|
89
|
+
<STIME>1414587683</STIME>
|
90
|
+
<ETIME>0</ETIME>
|
91
|
+
<VMMMAD>xen</VMMMAD>
|
92
|
+
<VNMMAD>802.1Q</VNMMAD>
|
93
|
+
<TMMAD>ssh</TMMAD>
|
94
|
+
<DS_LOCATION>/opt/opennebula/var/datastores</DS_LOCATION>
|
95
|
+
<DS_ID>0</DS_ID>
|
96
|
+
<PSTIME>1414587683</PSTIME>
|
97
|
+
<PETIME>1414587685</PETIME>
|
98
|
+
<RSTIME>1414587685</RSTIME>
|
99
|
+
<RETIME>0</RETIME>
|
100
|
+
<ESTIME>0</ESTIME>
|
101
|
+
<EETIME>0</EETIME>
|
102
|
+
<REASON>0</REASON>
|
103
|
+
<ACTION>3</ACTION>
|
104
|
+
</HISTORY>
|
105
|
+
</HISTORY_RECORDS>
|
106
|
+
</VM>
|
@@ -0,0 +1,106 @@
|
|
1
|
+
<VM>
|
2
|
+
<ID>46083</ID>
|
3
|
+
<UID>120</UID>
|
4
|
+
<GID>103</GID>
|
5
|
+
<UNAME>local_ops</UNAME>
|
6
|
+
<GNAME>ops</GNAME>
|
7
|
+
<NAME>EGI-SAM</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>1414588018</LAST_POLL>
|
20
|
+
<STATE>3</STATE>
|
21
|
+
<LCM_STATE>12</LCM_STATE>
|
22
|
+
<RESCHED>0</RESCHED>
|
23
|
+
<STIME>1414587675</STIME>
|
24
|
+
<ETIME>0</ETIME>
|
25
|
+
<DEPLOY_ID>one-46083</DEPLOY_ID>
|
26
|
+
<MEMORY>2097152</MEMORY>
|
27
|
+
<CPU>0</CPU>
|
28
|
+
<NET_TX>3072</NET_TX>
|
29
|
+
<NET_RX>0</NET_RX>
|
30
|
+
<TEMPLATE>
|
31
|
+
<AUTOMATIC_REQUIREMENTS><![CDATA[CLUSTER_ID = 100 & !(PUBLIC_CLOUD = YES)]]></AUTOMATIC_REQUIREMENTS>
|
32
|
+
<CPU><![CDATA[1]]></CPU>
|
33
|
+
<DISK>
|
34
|
+
<BUS><![CDATA[ide]]></BUS>
|
35
|
+
<CLONE><![CDATA[YES]]></CLONE>
|
36
|
+
<CLONE_TARGET><![CDATA[SYSTEM]]></CLONE_TARGET>
|
37
|
+
<CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
|
38
|
+
<DATASTORE><![CDATA[default]]></DATASTORE>
|
39
|
+
<DATASTORE_ID><![CDATA[1]]></DATASTORE_ID>
|
40
|
+
<DEV_PREFIX><![CDATA[xvd]]></DEV_PREFIX>
|
41
|
+
<DISK_ID><![CDATA[0]]></DISK_ID>
|
42
|
+
<DRIVER><![CDATA[tap2:tapdisk:aio:]]></DRIVER>
|
43
|
+
<IMAGE><![CDATA[monitoring]]></IMAGE>
|
44
|
+
<IMAGE_ID><![CDATA[31]]></IMAGE_ID>
|
45
|
+
<LN_TARGET><![CDATA[SYSTEM]]></LN_TARGET>
|
46
|
+
<READONLY><![CDATA[NO]]></READONLY>
|
47
|
+
<SAVE><![CDATA[NO]]></SAVE>
|
48
|
+
<SIZE><![CDATA[106]]></SIZE>
|
49
|
+
<SOURCE><![CDATA[/opt/opennebula/var/datastores/1/a8e87be2fdafadadd5575d73d4946d47]]></SOURCE>
|
50
|
+
<TARGET><![CDATA[xvda]]></TARGET>
|
51
|
+
<TM_MAD><![CDATA[ssh]]></TM_MAD>
|
52
|
+
<TYPE><![CDATA[FILE]]></TYPE>
|
53
|
+
</DISK>
|
54
|
+
<MEMORY><![CDATA[2048]]></MEMORY>
|
55
|
+
<NAME><![CDATA[one-46083]]></NAME>
|
56
|
+
<NIC>
|
57
|
+
<BRIDGE><![CDATA[xenbr0]]></BRIDGE>
|
58
|
+
<CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
|
59
|
+
<IP><![CDATA[192.168.254.26]]></IP>
|
60
|
+
<IP6_LINK><![CDATA[fe80::400:c0ff:fea8:fe1a]]></IP6_LINK>
|
61
|
+
<MAC><![CDATA[02:00:c0:a8:fe:1a]]></MAC>
|
62
|
+
<NETWORK><![CDATA[monitoring]]></NETWORK>
|
63
|
+
<NETWORK_ID><![CDATA[6]]></NETWORK_ID>
|
64
|
+
<NIC_ID><![CDATA[0]]></NIC_ID>
|
65
|
+
<VLAN><![CDATA[NO]]></VLAN>
|
66
|
+
</NIC>
|
67
|
+
<OS>
|
68
|
+
<BOOTLOADER><![CDATA[pygrub]]></BOOTLOADER>
|
69
|
+
</OS>
|
70
|
+
<RAW>
|
71
|
+
<TYPE><![CDATA[xen]]></TYPE>
|
72
|
+
</RAW>
|
73
|
+
<VCPU><![CDATA[1]]></VCPU>
|
74
|
+
<VMID><![CDATA[46083]]></VMID>
|
75
|
+
</TEMPLATE>
|
76
|
+
<USER_TEMPLATE>
|
77
|
+
<ARCHITECTURE><![CDATA[x86]]></ARCHITECTURE>
|
78
|
+
<OCCI_ID><![CDATA[4040029c-9c13-4b29-9d01-1deb179ee925]]></OCCI_ID>
|
79
|
+
<OCCI_MIXIN><![CDATA[https://occi.localhost/occi/infrastructure/resource_tpl#small]]></OCCI_MIXIN>
|
80
|
+
<OCCI_MIXIN><![CDATA[https://occi.localhost/occi/infrastructure/os_tpl#omr_worker_x86_64_ide_1_0]]></OCCI_MIXIN>
|
81
|
+
</USER_TEMPLATE>
|
82
|
+
<HISTORY_RECORDS>
|
83
|
+
<HISTORY>
|
84
|
+
<OID>46083</OID>
|
85
|
+
<SEQ>0</SEQ>
|
86
|
+
<HOSTNAME>localhost</HOSTNAME>
|
87
|
+
<HID>17</HID>
|
88
|
+
<CID>100</CID>
|
89
|
+
<STIME>1414587683</STIME>
|
90
|
+
<ETIME>0</ETIME>
|
91
|
+
<VMMMAD>xen</VMMMAD>
|
92
|
+
<VNMMAD>802.1Q</VNMMAD>
|
93
|
+
<TMMAD>ssh</TMMAD>
|
94
|
+
<DS_LOCATION>/opt/opennebula/var/datastores</DS_LOCATION>
|
95
|
+
<DS_ID>0</DS_ID>
|
96
|
+
<PSTIME>1414587683</PSTIME>
|
97
|
+
<PETIME>1414587685</PETIME>
|
98
|
+
<RSTIME>1414587685</RSTIME>
|
99
|
+
<RETIME>0</RETIME>
|
100
|
+
<ESTIME>0</ESTIME>
|
101
|
+
<EETIME>0</EETIME>
|
102
|
+
<REASON>0</REASON>
|
103
|
+
<ACTION>3</ACTION>
|
104
|
+
</HISTORY>
|
105
|
+
</HISTORY_RECORDS>
|
106
|
+
</VM>
|
@@ -0,0 +1,107 @@
|
|
1
|
+
<VM>
|
2
|
+
<ID>46083</ID>
|
3
|
+
<UID>120</UID>
|
4
|
+
<GID>103</GID>
|
5
|
+
<UNAME>local_ops</UNAME>
|
6
|
+
<GNAME>ops</GNAME>
|
7
|
+
<NAME>EGI-SAM</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>1414588018</LAST_POLL>
|
20
|
+
<STATE>3</STATE>
|
21
|
+
<LCM_STATE>12</LCM_STATE>
|
22
|
+
<RESCHED>0</RESCHED>
|
23
|
+
<STIME>1414587675</STIME>
|
24
|
+
<ETIME>0</ETIME>
|
25
|
+
<DEPLOY_ID>one-46083</DEPLOY_ID>
|
26
|
+
<MEMORY>2097152</MEMORY>
|
27
|
+
<CPU>0</CPU>
|
28
|
+
<NET_TX>3072</NET_TX>
|
29
|
+
<NET_RX>0</NET_RX>
|
30
|
+
<TEMPLATE>
|
31
|
+
<AUTOMATIC_REQUIREMENTS><![CDATA[CLUSTER_ID = 100 & !(PUBLIC_CLOUD = YES)]]></AUTOMATIC_REQUIREMENTS>
|
32
|
+
<CPU><![CDATA[1]]></CPU>
|
33
|
+
<DISK>
|
34
|
+
<BUS><![CDATA[ide]]></BUS>
|
35
|
+
<CLONE><![CDATA[YES]]></CLONE>
|
36
|
+
<CLONE_TARGET><![CDATA[SYSTEM]]></CLONE_TARGET>
|
37
|
+
<CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
|
38
|
+
<DATASTORE><![CDATA[default]]></DATASTORE>
|
39
|
+
<DATASTORE_ID><![CDATA[1]]></DATASTORE_ID>
|
40
|
+
<DEV_PREFIX><![CDATA[xvd]]></DEV_PREFIX>
|
41
|
+
<DISK_ID><![CDATA[0]]></DISK_ID>
|
42
|
+
<DRIVER><![CDATA[tap2:tapdisk:aio:]]></DRIVER>
|
43
|
+
<IMAGE><![CDATA[monitoring]]></IMAGE>
|
44
|
+
<IMAGE_ID><![CDATA[31]]></IMAGE_ID>
|
45
|
+
<LN_TARGET><![CDATA[SYSTEM]]></LN_TARGET>
|
46
|
+
<READONLY><![CDATA[NO]]></READONLY>
|
47
|
+
<SAVE><![CDATA[NO]]></SAVE>
|
48
|
+
<SIZE><![CDATA[106]]></SIZE>
|
49
|
+
<SOURCE><![CDATA[/opt/opennebula/var/datastores/1/a8e87be2fdafadadd5575d73d4946d47]]></SOURCE>
|
50
|
+
<TARGET><![CDATA[xvda]]></TARGET>
|
51
|
+
<TM_MAD><![CDATA[ssh]]></TM_MAD>
|
52
|
+
<TYPE><![CDATA[FILE]]></TYPE>
|
53
|
+
</DISK>
|
54
|
+
<MEMORY><![CDATA[2048]]></MEMORY>
|
55
|
+
<NAME><![CDATA[one-46083]]></NAME>
|
56
|
+
<NIC>
|
57
|
+
<BRIDGE><![CDATA[xenbr0]]></BRIDGE>
|
58
|
+
<CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
|
59
|
+
<IP><![CDATA[192.168.254.26]]></IP>
|
60
|
+
<IP6_LINK><![CDATA[fe80::400:c0ff:fea8:fe1a]]></IP6_LINK>
|
61
|
+
<MAC><![CDATA[02:00:c0:a8:fe:1a]]></MAC>
|
62
|
+
<NETWORK><![CDATA[monitoring]]></NETWORK>
|
63
|
+
<NETWORK_ID><![CDATA[6]]></NETWORK_ID>
|
64
|
+
<NIC_ID><![CDATA[0]]></NIC_ID>
|
65
|
+
<VLAN><![CDATA[NO]]></VLAN>
|
66
|
+
</NIC>
|
67
|
+
<OS>
|
68
|
+
<BOOTLOADER><![CDATA[pygrub]]></BOOTLOADER>
|
69
|
+
</OS>
|
70
|
+
<RAW>
|
71
|
+
<TYPE><![CDATA[xen]]></TYPE>
|
72
|
+
</RAW>
|
73
|
+
<VCPU><![CDATA[1]]></VCPU>
|
74
|
+
<VMID><![CDATA[46083]]></VMID>
|
75
|
+
</TEMPLATE>
|
76
|
+
<USER_TEMPLATE>
|
77
|
+
<ARCHITECTURE><![CDATA[x86]]></ARCHITECTURE>
|
78
|
+
<DESCRIPTION><![CDATA[Instantiated with rOCCI-server on Wed, 29 Oct 2014 14:01:15 +0100.]]></DESCRIPTION>
|
79
|
+
<OCCI_COMPUTE_MIXINS><![CDATA[http://occi.localhost/occi/infrastructure/os_tpl#uuid_monitoring_20 http://schema.fedcloud.egi.eu/occi/infrastructure/resource_tpl#small http://opennebula.org/occi/infrastructure#compute]]></OCCI_COMPUTE_MIXINS>
|
80
|
+
<OCCI_ID><![CDATA[46083]]></OCCI_ID>
|
81
|
+
<USER_X509_DN><![CDATA[/MY=STuPID/CN=DN/CN=HERE]]></USER_X509_DN>
|
82
|
+
</USER_TEMPLATE>
|
83
|
+
<HISTORY_RECORDS>
|
84
|
+
<HISTORY>
|
85
|
+
<OID>46083</OID>
|
86
|
+
<SEQ>0</SEQ>
|
87
|
+
<HOSTNAME>localhost</HOSTNAME>
|
88
|
+
<HID>17</HID>
|
89
|
+
<CID>100</CID>
|
90
|
+
<STIME>1414587683</STIME>
|
91
|
+
<ETIME>0</ETIME>
|
92
|
+
<VMMMAD>xen</VMMMAD>
|
93
|
+
<VNMMAD>802.1Q</VNMMAD>
|
94
|
+
<TMMAD>ssh</TMMAD>
|
95
|
+
<DS_LOCATION>/opt/opennebula/var/datastores</DS_LOCATION>
|
96
|
+
<DS_ID>0</DS_ID>
|
97
|
+
<PSTIME>1414587683</PSTIME>
|
98
|
+
<PETIME>1414587685</PETIME>
|
99
|
+
<RSTIME>1414587685</RSTIME>
|
100
|
+
<RETIME>0</RETIME>
|
101
|
+
<ESTIME>0</ESTIME>
|
102
|
+
<EETIME>0</EETIME>
|
103
|
+
<REASON>0</REASON>
|
104
|
+
<ACTION>3</ACTION>
|
105
|
+
</HISTORY>
|
106
|
+
</HISTORY_RECORDS>
|
107
|
+
</VM>
|
data/oneacct-export.gemspec
CHANGED
@@ -13,10 +13,11 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = ''
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
|
-
spec.files
|
17
|
-
spec.executables
|
18
|
-
spec.test_files
|
19
|
-
spec.require_paths
|
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
|
+
spec.required_ruby_version = '>= 2.0.0'
|
20
21
|
|
21
22
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
23
|
spec.add_development_dependency 'rake', '~> 10.0'
|
@@ -26,6 +27,6 @@ Gem::Specification.new do |spec|
|
|
26
27
|
|
27
28
|
spec.add_runtime_dependency 'opennebula', '~> 4.6.0'
|
28
29
|
spec.add_runtime_dependency 'syslogger', '~> 1.6.0'
|
29
|
-
spec.add_runtime_dependency 'sidekiq', '
|
30
|
+
spec.add_runtime_dependency 'sidekiq', '~> 3.2.6'
|
30
31
|
spec.add_runtime_dependency 'settingslogic', '~> 2.0.9'
|
31
32
|
end
|
data/spec/one_worker_spec.rb
CHANGED
@@ -163,9 +163,8 @@ describe OneWorker do
|
|
163
163
|
data['site_name'] = 'Hogwarts'
|
164
164
|
data['cloud_type'] = 'OpenNebula'
|
165
165
|
data['vm_uuid'] = '36551'
|
166
|
-
data['start_time'] =
|
167
|
-
data['
|
168
|
-
data['end_time'] = '1383742270'
|
166
|
+
data['start_time'] = Time.at(1383741160)
|
167
|
+
data['end_time'] = Time.at(1383742270)
|
169
168
|
data['machine_name'] = 'one-36551'
|
170
169
|
data['user_id'] = '120'
|
171
170
|
data['group_id'] = '0'
|
@@ -535,6 +534,45 @@ describe OneWorker do
|
|
535
534
|
expect(subject.process_vm(vm, user_map, image_map)).to eq(data)
|
536
535
|
end
|
537
536
|
end
|
537
|
+
|
538
|
+
context 'vm with USER_TEMPLATE/OCCI_COMPUTE_MIXINS' do
|
539
|
+
let(:filename) { 'one_worker_vm4.xml' }
|
540
|
+
let(:image_name) { 'http://occi.localhost/occi/infrastructure/os_tpl#uuid_monitoring_20' }
|
541
|
+
|
542
|
+
it 'w/o map info uses os_tpl mixin' do
|
543
|
+
expect(subject.process_vm(vm, user_map, {})['image_name']).to eq(image_name)
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'w/ map info uses map info' do
|
547
|
+
expect(subject.process_vm(vm, user_map, image_map)['image_name']).to eq(data['image_name'])
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
context 'vm with USER_TEMPLATE/OCCI_MIXIN' do
|
552
|
+
let(:filename) { 'one_worker_vm5.xml' }
|
553
|
+
let(:image_name) { 'https://occi.localhost/occi/infrastructure/os_tpl#omr_worker_x86_64_ide_1_0' }
|
554
|
+
|
555
|
+
it 'w/o map info uses os_tpl mixin' do
|
556
|
+
expect(subject.process_vm(vm, user_map, {})['image_name']).to eq(image_name)
|
557
|
+
end
|
558
|
+
|
559
|
+
it 'w/ map info uses map info' do
|
560
|
+
expect(subject.process_vm(vm, user_map, image_map)['image_name']).to eq(data['image_name'])
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context 'vm with USER_TEMPLATE/USER_X509_DN' do
|
565
|
+
let(:filename) { 'one_worker_vm6.xml' }
|
566
|
+
let(:user_name) { '/MY=STuPID/CN=DN/CN=HERE' }
|
567
|
+
|
568
|
+
it 'w/o map info uses USER_X509_DN' do
|
569
|
+
expect(subject.process_vm(vm, user_map, {})['user_name']).to eq(user_name)
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'w/ map info uses USER_X509_DN' do
|
573
|
+
expect(subject.process_vm(vm, user_map, image_map)['user_name']).to eq(user_name)
|
574
|
+
end
|
575
|
+
end
|
538
576
|
end
|
539
577
|
|
540
578
|
describe '.sum_rstime' do
|
@@ -617,9 +655,8 @@ describe OneWorker do
|
|
617
655
|
data['site_name'] = 'Hogwarts'
|
618
656
|
data['cloud_type'] = 'OpenNebula'
|
619
657
|
data['vm_uuid'] = '36551'
|
620
|
-
data['start_time'] =
|
621
|
-
data['
|
622
|
-
data['end_time'] = '1383742270'
|
658
|
+
data['start_time'] = Time.at(1383741160)
|
659
|
+
data['end_time'] = Time.at(1383742270)
|
623
660
|
data['machine_name'] = 'one-36551'
|
624
661
|
data['user_id'] = '120'
|
625
662
|
data['group_id'] = '0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oneacct-export
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michal Kimle
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -112,16 +112,16 @@ dependencies:
|
|
112
112
|
name: sidekiq
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 3.2.
|
117
|
+
version: 3.2.6
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- -
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 3.2.
|
124
|
+
version: 3.2.6
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: settingslogic
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +145,8 @@ extensions: []
|
|
145
145
|
extra_rdoc_files: []
|
146
146
|
files:
|
147
147
|
- ".gitignore"
|
148
|
+
- ".travis.yml"
|
149
|
+
- ".yardopts"
|
148
150
|
- Gemfile
|
149
151
|
- LICENSE.txt
|
150
152
|
- README.md
|
@@ -211,6 +213,9 @@ files:
|
|
211
213
|
- mock/one_worker_vm1.xml
|
212
214
|
- mock/one_worker_vm2.xml
|
213
215
|
- mock/one_worker_vm3.xml
|
216
|
+
- mock/one_worker_vm4.xml
|
217
|
+
- mock/one_worker_vm5.xml
|
218
|
+
- mock/one_worker_vm6.xml
|
214
219
|
- mock/one_writer_testfile
|
215
220
|
- oneacct-export.gemspec
|
216
221
|
- spec/one_data_accessor_spec.rb
|
@@ -232,7 +237,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
232
237
|
requirements:
|
233
238
|
- - ">="
|
234
239
|
- !ruby/object:Gem::Version
|
235
|
-
version:
|
240
|
+
version: 2.0.0
|
236
241
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
237
242
|
requirements:
|
238
243
|
- - ">="
|
@@ -252,3 +257,4 @@ test_files:
|
|
252
257
|
- spec/oneacct_opts_spec.rb
|
253
258
|
- spec/redis_conf_spec.rb
|
254
259
|
- spec/spec_helper.rb
|
260
|
+
has_rdoc:
|