furnish-ip 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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +34 -0
- data/Rakefile +31 -0
- data/furnish-ip.gemspec +30 -0
- data/lib/furnish/ip/version.rb +6 -0
- data/lib/furnish/ip.rb +120 -0
- data/lib/furnish/provisioners/auto_ip.rb +51 -0
- data/lib/furnish/provisioners/ip.rb +50 -0
- data/test/helper.rb +18 -0
- data/test/mt_cases.rb +50 -0
- data/test/test_auto_ip_provisioner.rb +54 -0
- data/test/test_ip_lib.rb +78 -0
- data/test/test_ip_provisioner.rb +77 -0
- metadata +210 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# vim: ft=ruby
|
2
|
+
guard 'minitest' do
|
3
|
+
# with Minitest::Unit
|
4
|
+
watch(%r!^test/(.*)\/?test_(.*)\.rb!)
|
5
|
+
watch(%r!^test/(?:mt_cases|helper)\.rb!) { "test" }
|
6
|
+
end
|
7
|
+
|
8
|
+
guard 'rake', :failure_ok => true, :run_on_all => false, :task => 'rdoc_cov' do
|
9
|
+
watch(%r!^lib/(.*)([^/]+)\.rb!)
|
10
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Erik Hollensbe
|
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,34 @@
|
|
1
|
+
# Furnish::IP
|
2
|
+
|
3
|
+
This provides 3 classes:
|
4
|
+
|
5
|
+
* Furnish::IP, a simple class to maintain a grouped IP registry
|
6
|
+
* Furnish::Provisioner::IP, a furnish provisioner intended to be chained to
|
7
|
+
resources that allocate their own IP addresses for recording purposes.
|
8
|
+
* Furnish::Provisioner::AutoIP, a furnish provisioner that works against a
|
9
|
+
subnet, allocating IPs automatically based on what's not in use.
|
10
|
+
|
11
|
+
Please see the documentation for these classes, and the furnish documentation,
|
12
|
+
for further usage.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
gem 'furnish-ip'
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install furnish-ip
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it
|
31
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList["test/test_*.rb"]
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
RDoc::Task.new do |rdoc|
|
12
|
+
rdoc.title = "Generic IP allocator for the Furnish provisioning system"
|
13
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
14
|
+
rdoc.rdoc_files -= ["lib/furnish/ip/version.rb"]
|
15
|
+
if ENV["RDOC_COVER"]
|
16
|
+
rdoc.options << "-C"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "run tests with coverage report"
|
21
|
+
task "test:coverage" do
|
22
|
+
ENV["COVERAGE"] = "1"
|
23
|
+
Rake::Task["test"].invoke
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "run rdoc with coverage report"
|
27
|
+
task :rdoc_cov do
|
28
|
+
# ugh
|
29
|
+
ENV["RDOC_COVER"] = "1"
|
30
|
+
ruby "-S rake rerdoc"
|
31
|
+
end
|
data/furnish-ip.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'furnish/ip/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "furnish-ip"
|
8
|
+
gem.version = Furnish::IP::VERSION
|
9
|
+
gem.authors = ["Erik Hollensbe"]
|
10
|
+
gem.email = ["erik+github@hollensbe.org"]
|
11
|
+
gem.description = %q{Generic IP allocator for the Furnish provisioning system}
|
12
|
+
gem.summary = %q{Generic IP allocator for the Furnish provisioning system}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'furnish', '~> 0.0.1'
|
21
|
+
gem.add_dependency 'palsy', '~> 0.0.2'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'rake'
|
24
|
+
gem.add_development_dependency 'minitest', '~> 4.5.0'
|
25
|
+
gem.add_development_dependency 'guard-minitest'
|
26
|
+
gem.add_development_dependency 'guard-rake'
|
27
|
+
gem.add_development_dependency 'rdoc'
|
28
|
+
gem.add_development_dependency 'rb-fsevent'
|
29
|
+
gem.add_development_dependency 'simplecov'
|
30
|
+
end
|
data/lib/furnish/ip.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Furnish # :nodoc:
|
5
|
+
|
6
|
+
#
|
7
|
+
# Furnish::IP maintains a saved registry of IP allocations, and can be
|
8
|
+
# instantiated any time after Furnish is.
|
9
|
+
#
|
10
|
+
class IP
|
11
|
+
|
12
|
+
##
|
13
|
+
# the subnet being worked over as an IPAddr object.
|
14
|
+
attr_reader :subnet
|
15
|
+
##
|
16
|
+
# the name of the registry.
|
17
|
+
attr_reader :name
|
18
|
+
##
|
19
|
+
# A Palsy::Set of the sum of allocated addresses.
|
20
|
+
attr_reader :allocated
|
21
|
+
##
|
22
|
+
# A Palsy::Map of the individual groups of addresses. Each item yields a
|
23
|
+
# set of string IP addresses.
|
24
|
+
attr_reader :groups
|
25
|
+
|
26
|
+
#
|
27
|
+
# Instantiate a new IP registry.
|
28
|
+
#
|
29
|
+
# Subnet is expected to be a String in CIDR notation or IPAddr object. If
|
30
|
+
# nil is passed, indicates no automated allocation logic is to be used and
|
31
|
+
# calls that do so will raise if referenced, allowing you to record
|
32
|
+
# allocations from another source.
|
33
|
+
#
|
34
|
+
# The name is a string and is "default" by default. Can be used to create
|
35
|
+
# semantic relationships or refer to an existing collection with a newly
|
36
|
+
# constructed object.
|
37
|
+
#
|
38
|
+
def initialize(subnet=nil, name="default")
|
39
|
+
if subnet and !subnet.kind_of?(IPAddr)
|
40
|
+
subnet = IPAddr.new(subnet) unless subnet.kind_of?(IPAddr)
|
41
|
+
end
|
42
|
+
|
43
|
+
@subnet = subnet
|
44
|
+
@name = name
|
45
|
+
@allocated = Palsy::Set.new('furnish_ip_allocations', name)
|
46
|
+
@groups = Palsy::Map.new('furnish_ip_groups', name)
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Determine, based on allocations already made, what the next available
|
51
|
+
# un-used IP is. Will raise ArgumentError if #new is not provided with an
|
52
|
+
# IPAddr object (see the documentation there for details).
|
53
|
+
#
|
54
|
+
# Will raise RuntimeError if the subnet is exhausted.
|
55
|
+
#
|
56
|
+
def unused_ip
|
57
|
+
unless subnet
|
58
|
+
raise ArgumentError, "#{self.class}#unused_ip requires an IPAddr object to work"
|
59
|
+
end
|
60
|
+
|
61
|
+
subnet_range = subnet.to_range
|
62
|
+
|
63
|
+
net = subnet
|
64
|
+
|
65
|
+
subnet_range.count.times do
|
66
|
+
this_ip = net.to_s
|
67
|
+
|
68
|
+
return this_ip unless allocated.include?(this_ip)
|
69
|
+
|
70
|
+
net = net.succ
|
71
|
+
|
72
|
+
unless subnet_range.include?(net.to_s)
|
73
|
+
net = subnet
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
raise "No free IP address for subnet #{subnet.inspect}"
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Get a set of IPs for a given group. Returns a Set object regardless of
|
82
|
+
# whether the group exists or not (it will just be empty).
|
83
|
+
#
|
84
|
+
def group_ips(name)
|
85
|
+
groups[name] || Set.new
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Assign one or more ips to a group. This method is additive, so multiple
|
90
|
+
# calls will grow the group, not replace it.
|
91
|
+
#
|
92
|
+
def assign_group_ips(name, *ips)
|
93
|
+
group = group_ips(name)
|
94
|
+
|
95
|
+
ips.each do |ip|
|
96
|
+
allocated.add(ip)
|
97
|
+
group.add(ip)
|
98
|
+
end
|
99
|
+
|
100
|
+
groups[name] = group
|
101
|
+
|
102
|
+
return ips
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Remove a group and free its allocated IP addresses.
|
107
|
+
#
|
108
|
+
def decommission_group(name)
|
109
|
+
group = group_ips(name)
|
110
|
+
|
111
|
+
group.each do |ip|
|
112
|
+
allocated.delete(ip)
|
113
|
+
end
|
114
|
+
|
115
|
+
groups.delete(name)
|
116
|
+
|
117
|
+
return name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'furnish/ip'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Furnish # :nodoc:
|
5
|
+
module Provisioner # :nodoc:
|
6
|
+
#
|
7
|
+
# The AutoIP provisioner is used to create IP addresses for other systems
|
8
|
+
# that allow them as direct input, such as VirtualBox VMs. The expectation
|
9
|
+
# is that this will be chained to another provisioner who would accept it
|
10
|
+
# as input during startup.
|
11
|
+
#
|
12
|
+
class AutoIP
|
13
|
+
##
|
14
|
+
# The name of the provisioner group, set at scheduling time.
|
15
|
+
attr_accessor :name
|
16
|
+
|
17
|
+
##
|
18
|
+
# Create the provisioner. Takes a Furnish::IP object that has subnet
|
19
|
+
# semantics (see Furnish::IP#new for more information) and a number of
|
20
|
+
# addresses to allocate, default 1.
|
21
|
+
def initialize(ip, number_of_addresses=1)
|
22
|
+
@ip = ip
|
23
|
+
@number_of_addresses = number_of_addresses
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Allocate addresses and forward them on to the next provisioner. Uses
|
28
|
+
# the provisioner group name as the key for the address set.
|
29
|
+
def startup(*args)
|
30
|
+
@number_of_addresses.times do
|
31
|
+
@ip.assign_group_ips(name, @ip.unused_ip)
|
32
|
+
end
|
33
|
+
|
34
|
+
return @ip.group_ips(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Release the IP addresses back to the allocator.
|
39
|
+
def shutdown
|
40
|
+
@ip.decommission_group(name)
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Display the allocated addresses for this group.
|
46
|
+
def report
|
47
|
+
@ip.group_ips(name).to_a
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'furnish/ip'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Furnish # :nodoc:
|
5
|
+
module Provisioner # :nodoc:
|
6
|
+
#
|
7
|
+
# Static Provisioner against the Furnish::IP registry.
|
8
|
+
#
|
9
|
+
class IP
|
10
|
+
##
|
11
|
+
# The name of the provisioner group, set at scheduling time.
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
##
|
15
|
+
#
|
16
|
+
# Create the provisioner. Takes a Furnish::IP object, and uses no
|
17
|
+
# auto-allocation features.
|
18
|
+
#
|
19
|
+
def initialize(ip)
|
20
|
+
@ip = ip
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Allocate the results passed to the argument list (presumably from a
|
25
|
+
# previously-run provisioner) in a Furnish::IP group named after the name
|
26
|
+
# of the provisioning group.
|
27
|
+
#
|
28
|
+
# Returns the set of IP addresses.
|
29
|
+
#
|
30
|
+
def startup(*args)
|
31
|
+
@ip.assign_group_ips(name, *args.flatten)
|
32
|
+
return @ip.group_ips(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Releases the allocated addresses.
|
37
|
+
#
|
38
|
+
def shutdown
|
39
|
+
@ip.decommission_group(name)
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Display the allocated addresses for this group.
|
45
|
+
def report
|
46
|
+
@ip.group_ips(name).to_a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
if ENV["COVERAGE"]
|
4
|
+
require 'simplecov'
|
5
|
+
SimpleCov.start do
|
6
|
+
add_filter '/test/'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'minitest/unit'
|
11
|
+
require 'tempfile'
|
12
|
+
require 'furnish'
|
13
|
+
require 'furnish/ip'
|
14
|
+
require 'furnish/provisioners/ip'
|
15
|
+
require 'furnish/provisioners/auto_ip'
|
16
|
+
require 'mt_cases'
|
17
|
+
|
18
|
+
require 'minitest/autorun'
|
data/test/mt_cases.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# FIXME copied from the furnish repo, should be a first-class thing in the furnish gem others can consume.
|
2
|
+
module Furnish
|
3
|
+
class TestCase < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@tempfiles ||= []
|
6
|
+
file = Tempfile.new('furnish_db')
|
7
|
+
@tempfiles.push(file)
|
8
|
+
logfile = Tempfile.new('furnish_log')
|
9
|
+
@tempfiles.push(logfile)
|
10
|
+
Furnish.logger = Furnish::Logger.new(logfile, 3)
|
11
|
+
Furnish.init(file.path)
|
12
|
+
return file
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
Furnish.logger.close
|
17
|
+
Furnish.shutdown
|
18
|
+
@tempfiles.each do |file|
|
19
|
+
file.unlink
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class SchedulerTestCase < TestCase
|
25
|
+
attr_reader :sched
|
26
|
+
|
27
|
+
def setup
|
28
|
+
super
|
29
|
+
@sched = Furnish::Scheduler.new
|
30
|
+
@monitor = Thread.new { loop { @sched.running?; sleep 1 } }
|
31
|
+
@monitor.abort_on_exception = true
|
32
|
+
@sched.run
|
33
|
+
end
|
34
|
+
|
35
|
+
def teardown
|
36
|
+
@sched.stop
|
37
|
+
sleep 0.3 while @sched.running?
|
38
|
+
@monitor.kill rescue nil
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def assert_solved(name)
|
43
|
+
assert_includes(sched.vm.solved, name, "#{name} is solved in the scheduler")
|
44
|
+
end
|
45
|
+
|
46
|
+
def refute_solved(name)
|
47
|
+
refute_includes(sched.vm.solved, name, "#{name} is solved in the scheduler")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestAutoIPProvisioner < Furnish::SchedulerTestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
@ip = Furnish::IP.new("127.0.0.1/24")
|
7
|
+
end
|
8
|
+
|
9
|
+
def make_provisioner(num=1)
|
10
|
+
Furnish::Provisioner::AutoIP.new(@ip, num)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_basic_provision
|
14
|
+
provisioner = make_provisioner
|
15
|
+
sched.schedule_provision("test1", provisioner, [])
|
16
|
+
sched.wait_for("test1")
|
17
|
+
|
18
|
+
assert_solved("test1")
|
19
|
+
|
20
|
+
assert_equal(1, @ip.group_ips("test1").count, "one IP was allocated")
|
21
|
+
assert_equal(1, @ip.allocated.count, "reflected in the allocation count")
|
22
|
+
|
23
|
+
first_ip = @ip.group_ips("test1").first
|
24
|
+
|
25
|
+
assert_equal("test1", provisioner.name, "name reflects provisioner name")
|
26
|
+
assert_equal([first_ip], provisioner.report, "report reflects provisioned IPs")
|
27
|
+
|
28
|
+
sched.schedule_provision("test2", make_provisioner(10), [])
|
29
|
+
sched.wait_for("test2")
|
30
|
+
|
31
|
+
assert_solved("test2")
|
32
|
+
|
33
|
+
assert_equal(10, @ip.group_ips("test2").count, "ten new ips were allocated to the test2 provisioning group")
|
34
|
+
assert_equal(11, @ip.allocated.count, "a total of 11 IPs are allocated now, across the two groups")
|
35
|
+
|
36
|
+
sched.teardown_group("test1")
|
37
|
+
|
38
|
+
refute_solved("test1")
|
39
|
+
assert_equal(0, @ip.group_ips("test1").count, "deprovisioning deallocates the IP group")
|
40
|
+
assert_equal(10, @ip.allocated.count, "ten IPs now, reflecting the loss of the one in this group")
|
41
|
+
|
42
|
+
sched.teardown_group("test2")
|
43
|
+
|
44
|
+
refute_solved("test2")
|
45
|
+
assert_equal(0, @ip.group_ips("test2").count, "deprovisioning deallocates the IP group")
|
46
|
+
assert_equal(0, @ip.allocated.count, "no IPs left in allocation group")
|
47
|
+
|
48
|
+
sched.schedule_provision("test1", make_provisioner, [])
|
49
|
+
sched.wait_for("test1")
|
50
|
+
|
51
|
+
assert_solved("test1")
|
52
|
+
assert_equal(first_ip, @ip.group_ips("test1").first, "provisioner re-uses deprovisioned IP addresses")
|
53
|
+
end
|
54
|
+
end
|
data/test/test_ip_lib.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestIPLib < Furnish::TestCase
|
4
|
+
def test_constructor
|
5
|
+
ip = Furnish::IP.new("127.0.0.1/24")
|
6
|
+
assert_equal("default", ip.name, "default name is 'default'")
|
7
|
+
assert_equal(IPAddr.new("127.0.0.1/24"), ip.subnet, "string subnet is cast to IPAddr")
|
8
|
+
|
9
|
+
ip = Furnish::IP.new(IPAddr.new("127.0.0.1/24"), "foo")
|
10
|
+
assert_equal("foo", ip.name, "when second argument is supplied, changes name")
|
11
|
+
assert_equal(IPAddr.new("127.0.0.1/24"), ip.subnet, "IPAddr is just accepted as an argument")
|
12
|
+
|
13
|
+
ip = Furnish::IP.new
|
14
|
+
assert_nil(ip.subnet, "subnet was not cast without provided subnet")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_unused_ip
|
18
|
+
ip = Furnish::IP.new(IPAddr.new("127.0.0.1/24"))
|
19
|
+
assert_equal("127.0.0.0", ip.unused_ip, "picks the next available ip")
|
20
|
+
assert_equal("127.0.0.0", ip.unused_ip, "has no side effects -- returns the same IP on the second call")
|
21
|
+
|
22
|
+
ip.allocated.add("127.0.0.0") # add something to get it to choose the next thing.
|
23
|
+
|
24
|
+
assert_equal("127.0.0.1", ip.unused_ip, "picks the next unavailable ip")
|
25
|
+
|
26
|
+
ip = Furnish::IP.new
|
27
|
+
assert_raises(ArgumentError, "Furnish::IP#unused_ip requires an IPAddr object to work") { ip.unused_ip }
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_cycle_detection
|
31
|
+
ip = Furnish::IP.new(IPAddr.new("127.0.0.1/31"))
|
32
|
+
|
33
|
+
2.times { ip.assign_group_ips("test", ip.unused_ip) }
|
34
|
+
|
35
|
+
assert_raises(RuntimeError, "No free IP address for subnet #{ip.inspect}") { ip.unused_ip }
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_group_management
|
39
|
+
ip = Furnish::IP.new(IPAddr.new("127.0.0.1/24"))
|
40
|
+
assert_kind_of(Set, ip.group_ips("foo"), "a non-existent group always returns an empty set")
|
41
|
+
assert_empty(ip.group_ips("foo"), "a non-existent group always returns an empty set")
|
42
|
+
|
43
|
+
allocated = Set.new
|
44
|
+
|
45
|
+
ip_arg = ip.unused_ip
|
46
|
+
|
47
|
+
allocated.add(ip_arg)
|
48
|
+
|
49
|
+
assert_equal(
|
50
|
+
[ip_arg],
|
51
|
+
ip.assign_group_ips("foo", ip_arg),
|
52
|
+
"single argument is return as an array"
|
53
|
+
)
|
54
|
+
|
55
|
+
assert_equal(Set[ip_arg], ip.group_ips("foo"), "returned as set from group_ips")
|
56
|
+
|
57
|
+
unused_ip = ip.unused_ip
|
58
|
+
ip_arg = [unused_ip, unused_ip.succ, unused_ip.succ.succ]
|
59
|
+
|
60
|
+
ip_arg.each { |i| allocated.add(i) }
|
61
|
+
|
62
|
+
assert_equal(
|
63
|
+
ip_arg,
|
64
|
+
ip.assign_group_ips("bar", *ip_arg),
|
65
|
+
"array is returned when multiple arguments passed"
|
66
|
+
)
|
67
|
+
|
68
|
+
assert_equal(Set[*ip_arg], ip.group_ips("bar"), "returned as set from group_ips")
|
69
|
+
assert_equal(allocated, allocated & ip.allocated.to_set, "group IPs are in the allocation table")
|
70
|
+
|
71
|
+
ip.decommission_group("foo")
|
72
|
+
ip.decommission_group("bar")
|
73
|
+
|
74
|
+
assert_empty(ip.group_ips("foo"), "group foo has been deallocated")
|
75
|
+
assert_empty(ip.group_ips("bar"), "group bar has been deallocated")
|
76
|
+
assert_empty(ip.allocated.to_set, "nothing in the allocation table since deprovisioning groups")
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'ipaddr'
|
3
|
+
|
4
|
+
class IPReturningProvisioner
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
def initialize(*ips)
|
8
|
+
@ips = ips
|
9
|
+
end
|
10
|
+
|
11
|
+
def startup(*args)
|
12
|
+
return @ips
|
13
|
+
end
|
14
|
+
|
15
|
+
def shutdown
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
|
19
|
+
def report
|
20
|
+
@ips
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class TestIPProvisioner < Furnish::SchedulerTestCase
|
25
|
+
def setup
|
26
|
+
super
|
27
|
+
@ip = Furnish::IP.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def make_provisioner(*ips)
|
31
|
+
[
|
32
|
+
IPReturningProvisioner.new(*ips),
|
33
|
+
Furnish::Provisioner::IP.new(@ip)
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_basic_provision
|
38
|
+
ipaddr = IPAddr.new("127.0.0.1/24")
|
39
|
+
ips = (0..4).map { ipaddr = ipaddr.succ; ipaddr.to_s }
|
40
|
+
provisioner = make_provisioner(*ips)
|
41
|
+
|
42
|
+
sched.schedule_provision("test1", provisioner, [])
|
43
|
+
sched.wait_for("test1")
|
44
|
+
|
45
|
+
count = ips.count
|
46
|
+
|
47
|
+
assert_solved("test1")
|
48
|
+
assert_equal(count, @ip.group_ips("test1").count, "#{count} IPs were allocated")
|
49
|
+
assert_equal(count, @ip.allocated.count, "#{count} IPs were allocated")
|
50
|
+
|
51
|
+
assert_equal("test1", provisioner.last.name, "name reflects group name")
|
52
|
+
assert_equal(ips, provisioner.last.report, "report reflects ip list")
|
53
|
+
|
54
|
+
ips = (0..4).map { ipaddr = ipaddr.succ; ipaddr.to_s }
|
55
|
+
provisioner = make_provisioner(*ips)
|
56
|
+
count += ips.count
|
57
|
+
|
58
|
+
sched.schedule_provision("test2", provisioner, [])
|
59
|
+
sched.wait_for("test2")
|
60
|
+
|
61
|
+
assert_solved("test2")
|
62
|
+
assert_equal(ips.count, @ip.group_ips("test2").count, "#{ips.count} IPs were allocated")
|
63
|
+
assert_equal(count, @ip.allocated.count, "between both groups, #{count} IPs were allocated")
|
64
|
+
|
65
|
+
count -= ips.count
|
66
|
+
|
67
|
+
sched.teardown_group("test2")
|
68
|
+
|
69
|
+
refute_solved("test2")
|
70
|
+
assert_empty(@ip.group_ips("test2"), "torn down group is empty")
|
71
|
+
assert_equal(count, @ip.allocated.count, "count is correct (#{count}) after deprovisioning one group")
|
72
|
+
|
73
|
+
sched.teardown_group("test1")
|
74
|
+
assert_empty(@ip.group_ips("test1"), "torn down group is empty")
|
75
|
+
assert_equal(0, @ip.allocated.count, "count is zero after deprovisioning all groups")
|
76
|
+
end
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: furnish-ip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Erik Hollensbe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: furnish
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.0.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: palsy
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.0.2
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.0.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: minitest
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 4.5.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 4.5.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-minitest
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: guard-rake
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rdoc
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rb-fsevent
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: simplecov
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
description: Generic IP allocator for the Furnish provisioning system
|
159
|
+
email:
|
160
|
+
- erik+github@hollensbe.org
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- .gitignore
|
166
|
+
- Gemfile
|
167
|
+
- Guardfile
|
168
|
+
- LICENSE.txt
|
169
|
+
- README.md
|
170
|
+
- Rakefile
|
171
|
+
- furnish-ip.gemspec
|
172
|
+
- lib/furnish/ip.rb
|
173
|
+
- lib/furnish/ip/version.rb
|
174
|
+
- lib/furnish/provisioners/auto_ip.rb
|
175
|
+
- lib/furnish/provisioners/ip.rb
|
176
|
+
- test/helper.rb
|
177
|
+
- test/mt_cases.rb
|
178
|
+
- test/test_auto_ip_provisioner.rb
|
179
|
+
- test/test_ip_lib.rb
|
180
|
+
- test/test_ip_provisioner.rb
|
181
|
+
homepage: ''
|
182
|
+
licenses: []
|
183
|
+
post_install_message:
|
184
|
+
rdoc_options: []
|
185
|
+
require_paths:
|
186
|
+
- lib
|
187
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
188
|
+
none: false
|
189
|
+
requirements:
|
190
|
+
- - ! '>='
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
none: false
|
195
|
+
requirements:
|
196
|
+
- - ! '>='
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
requirements: []
|
200
|
+
rubyforge_project:
|
201
|
+
rubygems_version: 1.8.25
|
202
|
+
signing_key:
|
203
|
+
specification_version: 3
|
204
|
+
summary: Generic IP allocator for the Furnish provisioning system
|
205
|
+
test_files:
|
206
|
+
- test/helper.rb
|
207
|
+
- test/mt_cases.rb
|
208
|
+
- test/test_auto_ip_provisioner.rb
|
209
|
+
- test/test_ip_lib.rb
|
210
|
+
- test/test_ip_provisioner.rb
|