thinp_xml 0.0.1
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 +15 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +13 -0
- data/bin/thinp_xml +103 -0
- data/features/create.feature +57 -0
- data/features/step_definitions/thinp_xml.rb +12 -0
- data/features/support/env.rb +5 -0
- data/features/usage.feature +18 -0
- data/lib/thinp_xml/builder.rb +39 -0
- data/lib/thinp_xml/distribution.rb +28 -0
- data/lib/thinp_xml/emit.rb +69 -0
- data/lib/thinp_xml/metadata.rb +28 -0
- data/lib/thinp_xml/parse.rb +85 -0
- data/lib/thinp_xml/utils.rb +115 -0
- data/lib/thinp_xml/version.rb +3 -0
- data/lib/thinp_xml/xml_format.rb +286 -0
- data/lib/thinp_xml.rb +18 -0
- data/spec/builder_spec.rb +96 -0
- data/spec/distribution_spec.rb +63 -0
- data/thinp_xml.gemspec +29 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MWQzNjRkODNhYmI1NjMzZTUzNjk3OGRiNjIxY2M0NTI1ODdjYTcwYQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmZiYjljNmJkNmRiYzQ4MDQ2M2Q5MTZkMzVhODI3ZGZiMjkwMjk5MQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmYzZDJjYTZmNGQ1NDkyOGNiODBlOGIzOWJiZGE5NzM3NDZkNTkxYjlmMjdh
|
10
|
+
MWQ2ODlhNTkzZjQwYzE5MzQ3MjVhMjA5OTJjNTA4NDU5ZTNjNjI0N2NiYzk2
|
11
|
+
YmI0ZmRmZWVlMDY3YWYzNzQwZWE3NjdmMzBlMTlkMzgzYjljODA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZWIxYzZmMWJkMTNiMDAwZDE5ZjA4ZDdkM2U3NWM3ZWU5MGMwNTQxOGRjYjMz
|
14
|
+
OGVjNzUwMGM3NTVlMTU2MjhhNTkyYjg2NDkxMDRlYjY0MTFiYzAwOGJkMzU0
|
15
|
+
YTc3OWZkNjIxZGJkZmJiOThiZGE0NDBhYzA4NTk3YzllNDJlYzc=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Joe Thornber
|
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,29 @@
|
|
1
|
+
# ThinpXml
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'thinp_xml'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install thinp_xml
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'cucumber'
|
3
|
+
require 'cucumber/rake/task'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
7
|
+
t.cucumber_opts = "features --format pretty"
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new do |t|
|
11
|
+
t.rspec_opts = ["--color"]
|
12
|
+
end
|
13
|
+
|
data/bin/thinp_xml
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thinp_xml'
|
4
|
+
require 'thinp_xml/distribution'
|
5
|
+
require 'ejt_command_line'
|
6
|
+
|
7
|
+
#----------------------------------------------------------------
|
8
|
+
|
9
|
+
XMLCommandLine = CommandLine::Parser.new do
|
10
|
+
value_type :string do |str|
|
11
|
+
str
|
12
|
+
end
|
13
|
+
|
14
|
+
value_type :int do |str|
|
15
|
+
Integer(str)
|
16
|
+
end
|
17
|
+
|
18
|
+
UNIFORM_REGEX = /uniform\[(\d+)..(\d+)\]/
|
19
|
+
|
20
|
+
value_type :int_distribution do |str|
|
21
|
+
m = UNIFORM_REGEX.match(str)
|
22
|
+
if m
|
23
|
+
ThinpXML::UniformDistribution.new(m[1].to_i, m[2].to_i + 1)
|
24
|
+
else
|
25
|
+
Integer(str)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
simple_switch :help, '--help', '-h'
|
30
|
+
value_switch :uuid, :string, '--uuid'
|
31
|
+
value_switch :nr_thins, :int_distribution, '--nr-thins'
|
32
|
+
value_switch :nr_mappings, :int_distribution, '--nr-mappings'
|
33
|
+
|
34
|
+
global do
|
35
|
+
switches :help
|
36
|
+
end
|
37
|
+
|
38
|
+
command :create do
|
39
|
+
switches :uuid, :nr_thins, :nr_mappings
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Dispatcher
|
44
|
+
include ThinpXML
|
45
|
+
|
46
|
+
def global_command(opts, args)
|
47
|
+
if args.size > 0
|
48
|
+
die "unknown command '#{args[0]}'"
|
49
|
+
else
|
50
|
+
if opts[:help]
|
51
|
+
help(STDOUT)
|
52
|
+
else
|
53
|
+
die "no command given"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def create(opts, args)
|
59
|
+
b = ThinpXML::Builder.new
|
60
|
+
b.uuid = opts.fetch(:uuid, '')
|
61
|
+
b.nr_thins = opts.fetch(:nr_thins, 0)
|
62
|
+
b.nr_mappings = opts.fetch(:nr_mappings, 0)
|
63
|
+
md = b.generate
|
64
|
+
write_xml(md, STDOUT)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def die(msg)
|
69
|
+
STDERR.puts msg
|
70
|
+
exit(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
def help(out)
|
74
|
+
out.write <<EOF
|
75
|
+
Manipulation of thin provisioning xml format metadata
|
76
|
+
--help, -h: Show this message
|
77
|
+
EOF
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_command_line(dispatcher, *args)
|
82
|
+
XMLCommandLine.parse(dispatcher, *args)
|
83
|
+
end
|
84
|
+
|
85
|
+
def top_level_handler(&block)
|
86
|
+
begin
|
87
|
+
block.call
|
88
|
+
rescue => e
|
89
|
+
STDERR.puts e.message
|
90
|
+
exit 1
|
91
|
+
end
|
92
|
+
|
93
|
+
exit 0
|
94
|
+
end
|
95
|
+
|
96
|
+
#----------------------------------------------------------------
|
97
|
+
|
98
|
+
top_level_handler do
|
99
|
+
dispatcher = Dispatcher.new
|
100
|
+
parse_command_line(dispatcher, *ARGV)
|
101
|
+
end
|
102
|
+
|
103
|
+
#----------------------------------------------------------------
|
@@ -0,0 +1,57 @@
|
|
1
|
+
Feature: I can create new metadata
|
2
|
+
|
3
|
+
Scenario: Create a valid superblock with no devices
|
4
|
+
When I thinp_xml create
|
5
|
+
Then the stdout should contain:
|
6
|
+
"""
|
7
|
+
<superblock uuid="" time="0" transaction="1" data_block_size="128" nr_data_blocks="100">
|
8
|
+
</superblock>
|
9
|
+
"""
|
10
|
+
|
11
|
+
Scenario: Create a valid superblock with specified uuid
|
12
|
+
When I thinp_xml create --uuid 'one two three'
|
13
|
+
Then the stdout should contain:
|
14
|
+
"""
|
15
|
+
<superblock uuid="one two three" time="0" transaction="1" data_block_size="128" nr_data_blocks="100">
|
16
|
+
</superblock>
|
17
|
+
"""
|
18
|
+
|
19
|
+
Scenario: Take an expression for the number of devices
|
20
|
+
When I thinp_xml create --nr-thins 3
|
21
|
+
Then the stdout should contain:
|
22
|
+
"""
|
23
|
+
<superblock uuid="" time="0" transaction="1" data_block_size="128" nr_data_blocks="100">
|
24
|
+
<device dev_id="0" mapped_blocks="0" transaction="0" creation_time="0" snap_time="0">
|
25
|
+
</device>
|
26
|
+
<device dev_id="1" mapped_blocks="0" transaction="0" creation_time="0" snap_time="0">
|
27
|
+
</device>
|
28
|
+
<device dev_id="2" mapped_blocks="0" transaction="0" creation_time="0" snap_time="0">
|
29
|
+
</device>
|
30
|
+
</superblock>
|
31
|
+
"""
|
32
|
+
|
33
|
+
Scenario: Take an expression for the number of mappings per device
|
34
|
+
When I thinp_xml create --nr-thins 1 --nr-mappings 67
|
35
|
+
Then the stdout should contain:
|
36
|
+
"""
|
37
|
+
<superblock uuid="" time="0" transaction="1" data_block_size="128" nr_data_blocks="100">
|
38
|
+
<device dev_id="0" mapped_blocks="67" transaction="0" creation_time="0" snap_time="0">
|
39
|
+
<range_mapping origin_begin="0" data_begin="0" length="67" time="1"/>
|
40
|
+
</device>
|
41
|
+
</superblock>
|
42
|
+
"""
|
43
|
+
|
44
|
+
Scenario: A uniform distribution can be given for nr thins
|
45
|
+
When I thinp_xml create --nr-thins uniform[4..7]
|
46
|
+
Then it should pass
|
47
|
+
|
48
|
+
Scenario: An unknown distrubution should fail for the nr thins
|
49
|
+
When I thinp_xml create --nr-thins fred[1..2]
|
50
|
+
Then it should fail
|
51
|
+
|
52
|
+
Scenario: A uniform distribution can be given for nr mappings
|
53
|
+
When I thinp_xml create --nr-thins 1 --nr-mappings uniform[40..100]
|
54
|
+
Then it should pass
|
55
|
+
|
56
|
+
|
57
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: The tool should be helpful
|
2
|
+
|
3
|
+
Scenario: --help prints usage to stdout
|
4
|
+
When I thinp_xml --help
|
5
|
+
Then the stdout should contain:
|
6
|
+
"""
|
7
|
+
Manipulation of thin provisioning xml format metadata
|
8
|
+
--help, -h: Show this message
|
9
|
+
"""
|
10
|
+
|
11
|
+
Scenario: Unknown sub commands cause fail
|
12
|
+
When I thinp_xml unleashtheearwigs
|
13
|
+
Then it should fail
|
14
|
+
And the stderr should contain:
|
15
|
+
"""
|
16
|
+
unknown command 'unleashtheearwigs'
|
17
|
+
"""
|
18
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thinp_xml/metadata'
|
2
|
+
|
3
|
+
#----------------------------------------------------------------
|
4
|
+
|
5
|
+
module ThinpXML
|
6
|
+
class Builder
|
7
|
+
attr_accessor :uuid, :nr_thins, :nr_mappings
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@uuid = ''
|
11
|
+
@nr_thins = 0
|
12
|
+
@nr_mappings = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate
|
16
|
+
nr_thins = @nr_thins.to_i
|
17
|
+
nr_mappings = @nr_mappings.to_i
|
18
|
+
|
19
|
+
superblock = Superblock.new(@uuid, 0, 1, 128, 100)
|
20
|
+
|
21
|
+
devices = Array.new
|
22
|
+
offset = 0
|
23
|
+
0.upto(nr_thins - 1) do |dev|
|
24
|
+
mappings = Array.new
|
25
|
+
|
26
|
+
if nr_mappings > 0
|
27
|
+
mappings << Mapping.new(0, offset, nr_mappings, 1)
|
28
|
+
offset += nr_mappings
|
29
|
+
end
|
30
|
+
|
31
|
+
devices << Device.new(dev, nr_mappings, 0, 0, 0, mappings)
|
32
|
+
end
|
33
|
+
|
34
|
+
Metadata.new(superblock, devices)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#----------------------------------------------------------------
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#----------------------------------------------------------------
|
2
|
+
|
3
|
+
module ThinpXML
|
4
|
+
class Distribution
|
5
|
+
def to_i
|
6
|
+
generate
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UniformDistribution
|
11
|
+
attr_accessor :start, :stop
|
12
|
+
|
13
|
+
def initialize(start, stop)
|
14
|
+
@start = start
|
15
|
+
@stop = stop
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate
|
19
|
+
@start + rand(@stop - @start)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_i
|
23
|
+
generate
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#----------------------------------------------------------------
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'thinp_xml/metadata'
|
2
|
+
|
3
|
+
#----------------------------------------------------------------
|
4
|
+
|
5
|
+
module ThinpXML
|
6
|
+
class Emitter
|
7
|
+
def initialize(out)
|
8
|
+
@out = out
|
9
|
+
@indent = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def emit_tag(obj, tag, *fields, &block)
|
13
|
+
expanded = fields.map {|fld| "#{fld}=\"#{obj.send(fld)}\""}
|
14
|
+
if block.nil?
|
15
|
+
emit_line "<#{tag} #{expanded.join(' ')}/>"
|
16
|
+
else
|
17
|
+
emit_line "<#{tag} #{expanded.join(' ')}>"
|
18
|
+
push
|
19
|
+
yield unless block.nil?
|
20
|
+
pop
|
21
|
+
emit_line "</#{tag}>"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def emit_line(str)
|
26
|
+
@out.puts((' ' * @indent) + str)
|
27
|
+
end
|
28
|
+
|
29
|
+
def push
|
30
|
+
@indent += 2
|
31
|
+
end
|
32
|
+
|
33
|
+
def pop
|
34
|
+
@indent -= 2
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def emit_superblock(e, sb, &block)
|
39
|
+
e.emit_tag(sb, 'superblock', :uuid, :time, :transaction, :data_block_size, :nr_data_blocks, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def emit_device(e, dev, &block)
|
43
|
+
e.emit_tag(dev, 'device', :dev_id, :mapped_blocks, :transaction, :creation_time, :snap_time, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def emit_mapping(e, m)
|
47
|
+
if m.length == 1
|
48
|
+
e.emit_line("<single_mapping origin_block=\"#{m.origin_begin}\" data_block=\"#{m.data_begin}\" time=\"#{m.time}\"/>")
|
49
|
+
else
|
50
|
+
e.emit_tag(m, 'range_mapping', :origin_begin, :data_begin, :length, :time)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_xml(metadata, io)
|
55
|
+
e = Emitter.new(io)
|
56
|
+
|
57
|
+
emit_superblock(e, metadata.superblock) do
|
58
|
+
metadata.devices.each do |dev|
|
59
|
+
emit_device(e, dev) do
|
60
|
+
dev.mappings.each do |m|
|
61
|
+
emit_mapping(e, m)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#----------------------------------------------------------------
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ThinpXML
|
2
|
+
SUPERBLOCK_FIELDS = [[:uuid, :string],
|
3
|
+
[:time, :int],
|
4
|
+
[:transaction, :int],
|
5
|
+
[:data_block_size, :int],
|
6
|
+
[:nr_data_blocks, :int]]
|
7
|
+
|
8
|
+
MAPPING_FIELDS = [[:origin_begin, :int],
|
9
|
+
[:data_begin, :int],
|
10
|
+
[:length, :int],
|
11
|
+
[:time, :int]]
|
12
|
+
|
13
|
+
DEVICE_FIELDS = [[:dev_id, :int],
|
14
|
+
[:mapped_blocks, :int],
|
15
|
+
[:transaction, :int],
|
16
|
+
[:creation_time, :int],
|
17
|
+
[:snap_time, :int],
|
18
|
+
[:mappings, :object]]
|
19
|
+
|
20
|
+
def self.field_names(flds)
|
21
|
+
flds.map {|p| p[0]}
|
22
|
+
end
|
23
|
+
|
24
|
+
Superblock = Struct.new(*field_names(SUPERBLOCK_FIELDS))
|
25
|
+
Mapping = Struct.new(*field_names(MAPPING_FIELDS))
|
26
|
+
Device = Struct.new(*field_names(DEVICE_FIELDS))
|
27
|
+
Metadata = Struct.new(:superblock, :devices)
|
28
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'thinp_xml/metadata'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'rexml/streamlistener'
|
4
|
+
|
5
|
+
module ThinpXML
|
6
|
+
module ParseDetail
|
7
|
+
include REXML
|
8
|
+
|
9
|
+
class Listener
|
10
|
+
include REXML::StreamListener
|
11
|
+
|
12
|
+
attr_reader :metadata
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@metadata = Metadata.new(nil, Array.new)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash(pairs)
|
19
|
+
r = Hash.new
|
20
|
+
pairs.each do |p|
|
21
|
+
r[p[0].intern] = p[1]
|
22
|
+
end
|
23
|
+
r
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_fields(attr, flds)
|
27
|
+
flds.map do |n,t|
|
28
|
+
case t
|
29
|
+
when :int
|
30
|
+
attr[n].to_i
|
31
|
+
|
32
|
+
when :string
|
33
|
+
attr[n]
|
34
|
+
|
35
|
+
when :object
|
36
|
+
attr[n]
|
37
|
+
|
38
|
+
else
|
39
|
+
raise "unknown field type"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def tag_start(tag, args)
|
45
|
+
attr = to_hash(args)
|
46
|
+
|
47
|
+
case tag
|
48
|
+
when 'superblock'
|
49
|
+
@metadata.superblock = Superblock.new(*get_fields(attr, SUPERBLOCK_FIELDS))
|
50
|
+
|
51
|
+
when 'device'
|
52
|
+
attr[:mappings] = Array.new
|
53
|
+
@current_device = Device.new(*get_fields(attr, DEVICE_FIELDS))
|
54
|
+
@metadata.devices << @current_device
|
55
|
+
|
56
|
+
when 'single_mapping'
|
57
|
+
@current_device.mappings << Mapping.new(attr[:origin_block].to_i, attr[:data_block].to_i, 1, attr[:time])
|
58
|
+
|
59
|
+
when 'range_mapping'
|
60
|
+
@current_device.mappings << Mapping.new(*get_fields(attr, MAPPING_FIELDS))
|
61
|
+
|
62
|
+
else
|
63
|
+
puts "unhandled tag '#{tag} #{attr.map {|x| x.inspect}.join(', ')}'"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def tag_end(tag)
|
68
|
+
end
|
69
|
+
|
70
|
+
def text(data)
|
71
|
+
return if data =~ /^\w*$/ # ignore whitespace
|
72
|
+
abbrev = data[0..40] + (data.length > 40 ? "..." : "")
|
73
|
+
puts " text : #{abbrev.inspect}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_xml(io)
|
79
|
+
l = ParseDetail::Listener.new
|
80
|
+
REXML::Document.parse_stream(io, l)
|
81
|
+
l.metadata
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#----------------------------------------------------------------
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'thinp_xml/metadata'
|
2
|
+
|
3
|
+
#----------------------------------------------------------------
|
4
|
+
|
5
|
+
module ThinpXML
|
6
|
+
def get_device(md, dev_id)
|
7
|
+
md.devices.each do |dev|
|
8
|
+
if dev.dev_id == dev_id
|
9
|
+
return dev
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Turns 2 lists of mappings, into a list of pairs of mappings.
|
15
|
+
# These pairs cover identical regions. nil is used for the
|
16
|
+
# data_begin if that region isn't mapped.
|
17
|
+
def expand_mappings(left, right)
|
18
|
+
pairs = Array.new
|
19
|
+
i1 = 0
|
20
|
+
i2 = 0
|
21
|
+
|
22
|
+
m1 = left[i1]
|
23
|
+
m2 = right[i2]
|
24
|
+
|
25
|
+
# look away now ...
|
26
|
+
loop do
|
27
|
+
if !m1 && !m2
|
28
|
+
return pairs
|
29
|
+
elsif !m1
|
30
|
+
pairs << [Mapping.new(m2.origin_begin, nil, m2.length, m2.time),
|
31
|
+
m2]
|
32
|
+
m2 = nil
|
33
|
+
elsif !m2
|
34
|
+
pairs << [m1,
|
35
|
+
Mapping.new(m1.origin_begin, nil, m1.length, m1.time)]
|
36
|
+
m1 = nil
|
37
|
+
elsif m1.origin_begin < m2.origin_begin
|
38
|
+
if m1.origin_begin + m1.length <= m2.origin_begin
|
39
|
+
pairs << [Mapping.new(m1.origin_begin, m1.data_begin, m1.length, m1.time),
|
40
|
+
Mapping.new(m1.origin_begin, nil, m1.length, m1.time)]
|
41
|
+
i1 += 1
|
42
|
+
m1 = left[i1]
|
43
|
+
else
|
44
|
+
len = m2.origin_begin - m1.origin_begin
|
45
|
+
pairs << [Mapping.new(m1.origin_begin, m1.data_begin, len, m1.time),
|
46
|
+
Mapping.new(m1.origin_begin, nil, len, m1.time)]
|
47
|
+
m1 = Mapping.new(m1.origin_begin + len, m1.data_begin + len, m1.length - len, m1.time)
|
48
|
+
end
|
49
|
+
elsif m2.origin_begin < m1.origin_begin
|
50
|
+
if m2.origin_begin + m2.length <= m1.origin_begin
|
51
|
+
pairs << [Mapping.new(m2.origin_begin, nil, m2.length, m2.time),
|
52
|
+
Mapping.new(m2.origin_begin, m2.data_begin, m2.length, m2.time)]
|
53
|
+
i2 += 1
|
54
|
+
m2 = right[i2]
|
55
|
+
else
|
56
|
+
len = m1.origin_begin - m2.origin_begin
|
57
|
+
pairs << [Mapping.new(m2.origin_begin, nil, len, m2.time),
|
58
|
+
Mapping.new(m2.origin_begin, m2.data_begin, len, m2.time)]
|
59
|
+
m2 = Mapping.new(m2.origin_begin + len, m2.data_begin + len, m2.length - len, m2.time)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
len = [m1.length, m2.length].min
|
63
|
+
pairs << [Mapping.new(m1.origin_begin, m1.data_begin, len, m1.time),
|
64
|
+
Mapping.new(m1.origin_begin, m2.data_begin, len, m2.time)]
|
65
|
+
if m1.length < m2.length
|
66
|
+
i1 += 1
|
67
|
+
m1 = left[i1]
|
68
|
+
m2 = Mapping.new(m2.origin_begin + len, m2.data_begin + len, m2.length - len, m2.time)
|
69
|
+
elsif m2.length < m1.length
|
70
|
+
i2 += 1
|
71
|
+
m1 = Mapping.new(m1.origin_begin + len, m1.data_begin + len, m1.length - len, m1.time)
|
72
|
+
m2 = right[i2]
|
73
|
+
else
|
74
|
+
i1 += 1
|
75
|
+
i2 += 1
|
76
|
+
m1 = left[i1]
|
77
|
+
m2 = right[i2]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# returns 3 arrays of mappings: unique to first arg, common, unique
|
84
|
+
# to second arg
|
85
|
+
def compare_devs(md_dev1, md_dev2)
|
86
|
+
m1 = md_dev1.mappings
|
87
|
+
m2 = md_dev2.mappings
|
88
|
+
|
89
|
+
left = Array.new
|
90
|
+
center = Array.new
|
91
|
+
right = Array.new
|
92
|
+
|
93
|
+
expand_mappings(m1, m2).each do |pair|
|
94
|
+
if pair[0].data_begin == pair[1].data_begin &&
|
95
|
+
pair[0].time == pair[1].time
|
96
|
+
# mappings are the same
|
97
|
+
center << pair[0]
|
98
|
+
else
|
99
|
+
left << pair[0]
|
100
|
+
right << pair[1]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
[left, center, right].each {|a| a.reject {|e| e.data_begin == nil}}
|
105
|
+
[left, center, right]
|
106
|
+
end
|
107
|
+
|
108
|
+
def compare_thins(md1, md2, dev_id)
|
109
|
+
compare_devs(get_device(md1, dev_id),
|
110
|
+
get_device(md2, dev_id))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
#----------------------------------------------------------------
|
115
|
+
|
@@ -0,0 +1,286 @@
|
|
1
|
+
# The thin_dump and thin_restore use an xml based external
|
2
|
+
# representation of the metadata. This module gives the test suite
|
3
|
+
# access to this xml data.
|
4
|
+
|
5
|
+
#----------------------------------------------------------------
|
6
|
+
|
7
|
+
require 'rexml/document'
|
8
|
+
require 'rexml/streamlistener'
|
9
|
+
|
10
|
+
module XMLFormat
|
11
|
+
include REXML
|
12
|
+
|
13
|
+
SUPERBLOCK_FIELDS = [[:uuid, :string],
|
14
|
+
[:time, :int],
|
15
|
+
[:transaction, :int],
|
16
|
+
[:data_block_size, :int],
|
17
|
+
[:nr_data_blocks, :int]]
|
18
|
+
|
19
|
+
MAPPING_FIELDS = [[:origin_begin, :int],
|
20
|
+
[:data_begin, :int],
|
21
|
+
[:length, :int],
|
22
|
+
[:time, :int]]
|
23
|
+
|
24
|
+
DEVICE_FIELDS = [[:dev_id, :int],
|
25
|
+
[:mapped_blocks, :int],
|
26
|
+
[:transaction, :int],
|
27
|
+
[:creation_time, :int],
|
28
|
+
[:snap_time, :int],
|
29
|
+
[:mappings, :object]]
|
30
|
+
|
31
|
+
def self.field_names(flds)
|
32
|
+
flds.map {|p| p[0]}
|
33
|
+
end
|
34
|
+
|
35
|
+
Superblock = Struct.new(*field_names(SUPERBLOCK_FIELDS))
|
36
|
+
Mapping = Struct.new(*field_names(MAPPING_FIELDS))
|
37
|
+
Device = Struct.new(*field_names(DEVICE_FIELDS))
|
38
|
+
Metadata = Struct.new(:superblock, :devices)
|
39
|
+
|
40
|
+
class Listener
|
41
|
+
include REXML::StreamListener
|
42
|
+
|
43
|
+
attr_reader :metadata
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
@metadata = Metadata.new(nil, Array.new)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_hash(pairs)
|
50
|
+
r = Hash.new
|
51
|
+
pairs.each do |p|
|
52
|
+
r[p[0].intern] = p[1]
|
53
|
+
end
|
54
|
+
r
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_fields(attr, flds)
|
58
|
+
flds.map do |n,t|
|
59
|
+
case t
|
60
|
+
when :int
|
61
|
+
attr[n].to_i
|
62
|
+
|
63
|
+
when :string
|
64
|
+
attr[n]
|
65
|
+
|
66
|
+
when :object
|
67
|
+
attr[n]
|
68
|
+
|
69
|
+
else
|
70
|
+
raise "unknown field type"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def tag_start(tag, args)
|
76
|
+
attr = to_hash(args)
|
77
|
+
|
78
|
+
case tag
|
79
|
+
when 'superblock'
|
80
|
+
@metadata.superblock = Superblock.new(*get_fields(attr, SUPERBLOCK_FIELDS))
|
81
|
+
|
82
|
+
when 'device'
|
83
|
+
attr[:mappings] = Array.new
|
84
|
+
@current_device = Device.new(*get_fields(attr, DEVICE_FIELDS))
|
85
|
+
@metadata.devices << @current_device
|
86
|
+
|
87
|
+
when 'single_mapping'
|
88
|
+
@current_device.mappings << Mapping.new(attr[:origin_block].to_i, attr[:data_block].to_i, 1, attr[:time])
|
89
|
+
|
90
|
+
when 'range_mapping'
|
91
|
+
@current_device.mappings << Mapping.new(*get_fields(attr, MAPPING_FIELDS))
|
92
|
+
|
93
|
+
else
|
94
|
+
puts "unhandled tag '#{tag} #{attr.map {|x| x.inspect}.join(', ')}'"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def tag_end(tag)
|
99
|
+
end
|
100
|
+
|
101
|
+
def text(data)
|
102
|
+
return if data =~ /^\w*$/ # ignore whitespace
|
103
|
+
abbrev = data[0..40] + (data.length > 40 ? "..." : "")
|
104
|
+
puts " text : #{abbrev.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def read_xml(io)
|
109
|
+
l = Listener.new
|
110
|
+
Document.parse_stream(io, l)
|
111
|
+
l.metadata
|
112
|
+
end
|
113
|
+
|
114
|
+
class Emitter
|
115
|
+
def initialize(out)
|
116
|
+
@out = out
|
117
|
+
@indent = 0
|
118
|
+
end
|
119
|
+
|
120
|
+
def emit_tag(obj, tag, *fields, &block)
|
121
|
+
expanded = fields.map {|fld| "#{fld}=\"#{obj.send(fld)}\""}
|
122
|
+
if block.nil?
|
123
|
+
emit_line "<#{tag} #{expanded.join(' ')}/>"
|
124
|
+
else
|
125
|
+
emit_line "<#{tag} #{expanded.join(' ')}>"
|
126
|
+
push
|
127
|
+
yield unless block.nil?
|
128
|
+
pop
|
129
|
+
emit_line "</#{tag}>"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def emit_line(str)
|
134
|
+
@out.puts((' ' * @indent) + str)
|
135
|
+
end
|
136
|
+
|
137
|
+
def push
|
138
|
+
@indent += 2
|
139
|
+
end
|
140
|
+
|
141
|
+
def pop
|
142
|
+
@indent -= 2
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def emit_superblock(e, sb, &block)
|
147
|
+
e.emit_tag(sb, 'superblock', :uuid, :time, :transaction, :data_block_size, :nr_data_blocks, &block)
|
148
|
+
end
|
149
|
+
|
150
|
+
def emit_device(e, dev, &block)
|
151
|
+
e.emit_tag(dev, 'device', :dev_id, :mapped_blocks, :transaction, :creation_time, :snap_time, &block)
|
152
|
+
end
|
153
|
+
|
154
|
+
def emit_mapping(e, m)
|
155
|
+
if m.length == 1
|
156
|
+
e.emit_line("<single_mapping origin_block=\"#{m.origin_begin}\" data_block=\"#{m.data_begin}\" time=\"#{m.time}\"/>")
|
157
|
+
else
|
158
|
+
e.emit_tag(m, 'range_mapping', :origin_begin, :data_begin, :length, :time)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def write_xml(metadata, io)
|
163
|
+
e = Emitter.new(io)
|
164
|
+
|
165
|
+
emit_superblock(e, metadata.superblock) do
|
166
|
+
metadata.devices.each do |dev|
|
167
|
+
emit_device(e, dev) do
|
168
|
+
dev.mappings.each do |m|
|
169
|
+
emit_mapping(e, m)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
#--------------------------------------------------------------
|
177
|
+
|
178
|
+
def get_device(md, dev_id)
|
179
|
+
md.devices.each do |dev|
|
180
|
+
if dev.dev_id == dev_id
|
181
|
+
return dev
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Turns 2 lists of mappings, into a list of pairs of mappings.
|
187
|
+
# These pairs cover identical regions. nil is used for the
|
188
|
+
# data_begin if that region isn't mapped.
|
189
|
+
def expand_mappings(left, right)
|
190
|
+
pairs = Array.new
|
191
|
+
i1 = 0
|
192
|
+
i2 = 0
|
193
|
+
|
194
|
+
m1 = left[i1]
|
195
|
+
m2 = right[i2]
|
196
|
+
|
197
|
+
# look away now ...
|
198
|
+
loop do
|
199
|
+
if !m1 && !m2
|
200
|
+
return pairs
|
201
|
+
elsif !m1
|
202
|
+
pairs << [Mapping.new(m2.origin_begin, nil, m2.length, m2.time),
|
203
|
+
m2]
|
204
|
+
m2 = nil
|
205
|
+
elsif !m2
|
206
|
+
pairs << [m1,
|
207
|
+
Mapping.new(m1.origin_begin, nil, m1.length, m1.time)]
|
208
|
+
m1 = nil
|
209
|
+
elsif m1.origin_begin < m2.origin_begin
|
210
|
+
if m1.origin_begin + m1.length <= m2.origin_begin
|
211
|
+
pairs << [Mapping.new(m1.origin_begin, m1.data_begin, m1.length, m1.time),
|
212
|
+
Mapping.new(m1.origin_begin, nil, m1.length, m1.time)]
|
213
|
+
i1 += 1
|
214
|
+
m1 = left[i1]
|
215
|
+
else
|
216
|
+
len = m2.origin_begin - m1.origin_begin
|
217
|
+
pairs << [Mapping.new(m1.origin_begin, m1.data_begin, len, m1.time),
|
218
|
+
Mapping.new(m1.origin_begin, nil, len, m1.time)]
|
219
|
+
m1 = Mapping.new(m1.origin_begin + len, m1.data_begin + len, m1.length - len, m1.time)
|
220
|
+
end
|
221
|
+
elsif m2.origin_begin < m1.origin_begin
|
222
|
+
if m2.origin_begin + m2.length <= m1.origin_begin
|
223
|
+
pairs << [Mapping.new(m2.origin_begin, nil, m2.length, m2.time),
|
224
|
+
Mapping.new(m2.origin_begin, m2.data_begin, m2.length, m2.time)]
|
225
|
+
i2 += 1
|
226
|
+
m2 = right[i2]
|
227
|
+
else
|
228
|
+
len = m1.origin_begin - m2.origin_begin
|
229
|
+
pairs << [Mapping.new(m2.origin_begin, nil, len, m2.time),
|
230
|
+
Mapping.new(m2.origin_begin, m2.data_begin, len, m2.time)]
|
231
|
+
m2 = Mapping.new(m2.origin_begin + len, m2.data_begin + len, m2.length - len, m2.time)
|
232
|
+
end
|
233
|
+
else
|
234
|
+
len = [m1.length, m2.length].min
|
235
|
+
pairs << [Mapping.new(m1.origin_begin, m1.data_begin, len, m1.time),
|
236
|
+
Mapping.new(m1.origin_begin, m2.data_begin, len, m2.time)]
|
237
|
+
if m1.length < m2.length
|
238
|
+
i1 += 1
|
239
|
+
m1 = left[i1]
|
240
|
+
m2 = Mapping.new(m2.origin_begin + len, m2.data_begin + len, m2.length - len, m2.time)
|
241
|
+
elsif m2.length < m1.length
|
242
|
+
i2 += 1
|
243
|
+
m1 = Mapping.new(m1.origin_begin + len, m1.data_begin + len, m1.length - len, m1.time)
|
244
|
+
m2 = right[i2]
|
245
|
+
else
|
246
|
+
i1 += 1
|
247
|
+
i2 += 1
|
248
|
+
m1 = left[i1]
|
249
|
+
m2 = right[i2]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# returns 3 arrays of mappings: unique to first arg, common, unique
|
256
|
+
# to second arg
|
257
|
+
def compare_devs(md_dev1, md_dev2)
|
258
|
+
m1 = md_dev1.mappings
|
259
|
+
m2 = md_dev2.mappings
|
260
|
+
|
261
|
+
left = Array.new
|
262
|
+
center = Array.new
|
263
|
+
right = Array.new
|
264
|
+
|
265
|
+
expand_mappings(m1, m2).each do |pair|
|
266
|
+
if pair[0].data_begin == pair[1].data_begin &&
|
267
|
+
pair[0].time == pair[1].time
|
268
|
+
# mappings are the same
|
269
|
+
center << pair[0]
|
270
|
+
else
|
271
|
+
left << pair[0]
|
272
|
+
right << pair[1]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
[left, center, right].each {|a| a.reject {|e| e.data_begin == nil}}
|
277
|
+
[left, center, right]
|
278
|
+
end
|
279
|
+
|
280
|
+
def compare_thins(md1, md2, dev_id)
|
281
|
+
compare_devs(get_device(md1, dev_id),
|
282
|
+
get_device(md2, dev_id))
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
#----------------------------------------------------------------
|
data/lib/thinp_xml.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module ThinpXML
|
2
|
+
THINP_TOOLS_VERSION = '0.1.5'
|
3
|
+
|
4
|
+
def self.tools_are_installed
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
unless ThinpXML::tools_are_installed
|
10
|
+
raise "please install the thin provisioning tools version #{THINP_TOOLS_VERSION}"
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'thinp_xml/builder'
|
14
|
+
require 'thinp_xml/emit'
|
15
|
+
require 'thinp_xml/metadata'
|
16
|
+
require 'thinp_xml/parse'
|
17
|
+
require 'thinp_xml/utils'
|
18
|
+
require 'thinp_xml/version'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'thinp_xml/builder'
|
2
|
+
|
3
|
+
#----------------------------------------------------------------
|
4
|
+
|
5
|
+
describe "ThinpXML::Builder" do
|
6
|
+
before :each do
|
7
|
+
@b = ThinpXML::Builder.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def total_mapped(device)
|
11
|
+
device.mappings.inject(0) {|sum, m| sum + m.length}
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "uuid" do
|
15
|
+
it "should be an empty string by default" do
|
16
|
+
@b.uuid.should == ''
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should reflect any changes" do
|
20
|
+
uuid = 'one two three'
|
21
|
+
@b.uuid = uuid
|
22
|
+
@b.uuid.should == uuid
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should generate the correct uuid" do
|
26
|
+
uuid = 'one two three'
|
27
|
+
@b.uuid = uuid
|
28
|
+
md = @b.generate
|
29
|
+
md.superblock.uuid.should == uuid
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "nr of thins" do
|
34
|
+
it "zero by default" do
|
35
|
+
@b.nr_thins.should == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should reflect any changes" do
|
39
|
+
@b.nr_thins = 5
|
40
|
+
@b.nr_thins.should == 5
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should generate the correct nr" do
|
44
|
+
@b.nr_thins = 5
|
45
|
+
md = @b.generate
|
46
|
+
|
47
|
+
md.should have(5).devices
|
48
|
+
0.upto(4) do |n|
|
49
|
+
md.devices[n].should_not == nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should take a distribution" do
|
54
|
+
@b.nr_thins = UniformDistribution.new(2, 6)
|
55
|
+
md = @b.generate
|
56
|
+
|
57
|
+
md.should have_at_most(5).devices
|
58
|
+
md.should have_at_least(2).devices
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "nr of mappings" do
|
63
|
+
it "none by default" do
|
64
|
+
@b.nr_thins = 1
|
65
|
+
@b.generate.devices[0].should have(0).mappings
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should reflect any changes" do
|
69
|
+
@b.nr_mappings = 101
|
70
|
+
@b.nr_mappings.should == 101
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should generate the correct nr" do
|
74
|
+
@b.nr_thins = 5
|
75
|
+
@b.nr_mappings = 101
|
76
|
+
md = @b.generate
|
77
|
+
|
78
|
+
0.upto(@b.nr_thins - 1) do |n|
|
79
|
+
dev = md.devices[n]
|
80
|
+
total = total_mapped(dev)
|
81
|
+
total.should == 101
|
82
|
+
dev.mapped_blocks.should == 101
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should generate a single mapping" do
|
87
|
+
@b.nr_thins = 1
|
88
|
+
@b.nr_mappings = 101
|
89
|
+
md = @b.generate
|
90
|
+
|
91
|
+
md.devices[0].should have(1).mappings
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#----------------------------------------------------------------
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'thinp_xml/distribution'
|
2
|
+
|
3
|
+
include ThinpXML
|
4
|
+
|
5
|
+
#----------------------------------------------------------------
|
6
|
+
|
7
|
+
module ApproxInt
|
8
|
+
def approx?(target, delta)
|
9
|
+
(self.to_i >= target - delta) && (self.to_i <= target + delta)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Fixnum
|
14
|
+
include ApproxInt
|
15
|
+
end
|
16
|
+
|
17
|
+
class Bignum
|
18
|
+
include ApproxInt
|
19
|
+
end
|
20
|
+
|
21
|
+
#----------------------------------------------------------------
|
22
|
+
|
23
|
+
describe "random distributions" do
|
24
|
+
describe UniformDistribution do
|
25
|
+
it "should be constructed with a range of values" do
|
26
|
+
dist = UniformDistribution.new(1, 10)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should generate a number from this range every time generate is called" do
|
30
|
+
dist = UniformDistribution.new(1, 10)
|
31
|
+
|
32
|
+
buckets = Array.new(11, 0)
|
33
|
+
|
34
|
+
10000.times do
|
35
|
+
buckets[dist.generate] += 1
|
36
|
+
end
|
37
|
+
|
38
|
+
buckets[0].should == 0
|
39
|
+
buckets[10].should == 0
|
40
|
+
|
41
|
+
1.upto(9) do |n|
|
42
|
+
buckets[n].should be_approx(1000, 200)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have a to_i method that just calls generate" do
|
47
|
+
dist = UniformDistribution.new(1, 10)
|
48
|
+
|
49
|
+
# redefine generate
|
50
|
+
$called456 = false
|
51
|
+
class << dist
|
52
|
+
def generate
|
53
|
+
$called456 = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
dist.to_i
|
58
|
+
$called456.should be_true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#----------------------------------------------------------------
|
data/thinp_xml.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'thinp_xml/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "thinp_xml"
|
8
|
+
spec.version = ThinpXml::VERSION
|
9
|
+
spec.authors = ["Joe Thornber"]
|
10
|
+
spec.email = ["ejt@redhat.com"]
|
11
|
+
spec.description = %q{Ruby library for parsing and generating the Linux thin-provisioning xml metadata format.}
|
12
|
+
spec.summary = %q{Thin provisioning xml}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "GPL"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "ejt_command_line", "0.0.2"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency 'json', '~> 1.7.7'
|
26
|
+
spec.add_development_dependency "cucumber"
|
27
|
+
spec.add_development_dependency "aruba"
|
28
|
+
spec.add_development_dependency "rspec"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thinp_xml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe Thornber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ejt_command_line
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.7.7
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.7.7
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cucumber
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: aruba
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Ruby library for parsing and generating the Linux thin-provisioning xml
|
112
|
+
metadata format.
|
113
|
+
email:
|
114
|
+
- ejt@redhat.com
|
115
|
+
executables:
|
116
|
+
- thinp_xml
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- Gemfile
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- bin/thinp_xml
|
126
|
+
- features/create.feature
|
127
|
+
- features/step_definitions/thinp_xml.rb
|
128
|
+
- features/support/env.rb
|
129
|
+
- features/usage.feature
|
130
|
+
- lib/thinp_xml.rb
|
131
|
+
- lib/thinp_xml/builder.rb
|
132
|
+
- lib/thinp_xml/distribution.rb
|
133
|
+
- lib/thinp_xml/emit.rb
|
134
|
+
- lib/thinp_xml/metadata.rb
|
135
|
+
- lib/thinp_xml/parse.rb
|
136
|
+
- lib/thinp_xml/utils.rb
|
137
|
+
- lib/thinp_xml/version.rb
|
138
|
+
- lib/thinp_xml/xml_format.rb
|
139
|
+
- spec/builder_spec.rb
|
140
|
+
- spec/distribution_spec.rb
|
141
|
+
- thinp_xml.gemspec
|
142
|
+
homepage: ''
|
143
|
+
licenses:
|
144
|
+
- GPL
|
145
|
+
metadata: {}
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
require_paths:
|
149
|
+
- lib
|
150
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ! '>='
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 2.0.3
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: Thin provisioning xml
|
166
|
+
test_files:
|
167
|
+
- features/create.feature
|
168
|
+
- features/step_definitions/thinp_xml.rb
|
169
|
+
- features/support/env.rb
|
170
|
+
- features/usage.feature
|
171
|
+
- spec/builder_spec.rb
|
172
|
+
- spec/distribution_spec.rb
|