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 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
@@ -0,0 +1,18 @@
1
+ *~
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in thinp_xml.gemspec
4
+ gemspec
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,12 @@
1
+ When(/^I thinp_xml (.*)$/) do |cmd|
2
+ run_simple(unescape("thinp_xml #{cmd}"), false)
3
+ end
4
+
5
+ Then(/^it should pass$/) do
6
+ assert_success(true)
7
+ end
8
+
9
+ Then(/^it should fail$/) do
10
+ assert_success(false)
11
+ end
12
+
@@ -0,0 +1,5 @@
1
+ ENV['RUBYLIB'] = "#{Dir.pwd}"
2
+ ENV['PATH'] = "#{Dir::pwd}:#{ENV['PATH']}"
3
+
4
+ require 'thinp_xml'
5
+ require 'aruba/cucumber'
@@ -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,3 @@
1
+ module ThinpXml
2
+ VERSION = "0.0.1"
3
+ end
@@ -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