furnish-ip 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|