smart_proxy_dhcp_infoblox 0.0.4 → 0.0.5

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.
@@ -1,24 +1,16 @@
1
- require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_version'
2
-
3
1
  module Proxy::DHCP::Infoblox
4
2
  class Plugin < ::Proxy::Provider
5
- plugin :dhcp_infoblox, ::Proxy::DHCP::Infoblox::VERSION, :provider_class => "::Proxy::DHCP::Infoblox::Provider"
3
+ plugin :dhcp_infoblox, ::Proxy::DHCP::Infoblox::VERSION
4
+
5
+ default_settings :record_type => 'host', :range => false
6
+ validate_presence :username, :password
6
7
 
7
- # Settings listed under default_settings are required.
8
- # An exception will be raised if they are initialized with nil values.
9
- # Settings not listed under default_settings are considered optional and by default have nil value.
10
- default_settings :infoblox_user => 'infoblox',
11
- :infoblox_pw => 'infoblox',
12
- :infoblox_host => 'infoblox.my.domain',
13
- :record_type => 'host',
14
- :wapi_version => '2.0',
15
- :range => false
8
+ requires :dhcp, '>= 1.13'
16
9
 
17
- requires :dhcp, '>= 1.11'
10
+ load_classes ::Proxy::DHCP::Infoblox::PluginConfiguration
11
+ load_validators :record_type_validator => ::Proxy::DHCP::Infoblox::RecordTypeValidator
12
+ load_dependency_injection_wirings ::Proxy::DHCP::Infoblox::PluginConfiguration
18
13
 
19
- after_activation do
20
- require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_main'
21
- require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_dependencies'
22
- end
14
+ validate :record_type, :record_type_validator => true
23
15
  end
24
16
  end
@@ -1,7 +1,7 @@
1
1
  module Proxy
2
2
  module DHCP
3
3
  module Infoblox
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,53 @@
1
+ require 'smart_proxy_dhcp_infoblox/common_crud'
2
+
3
+ module ::Proxy::DHCP::Infoblox
4
+ class FixedAddressCRUD < CommonCRUD
5
+ def initialize(connection)
6
+ @memoized_host = nil
7
+ @memoized_condition = nil
8
+ super
9
+ end
10
+
11
+ def all_hosts(subnet_address)
12
+ network = ::Infoblox::Fixedaddress.find(@connection, 'network' => subnet_address, '_max_results' => 2147483646) #2**(32-cidr_to_i(subnet_address)))
13
+ network.map {|h| build_reservation(h.name, h, subnet_address)}.compact
14
+ end
15
+
16
+ def find_record_by_ip(subnet_address, ip_address)
17
+ found = find_host('ipv4addr' => ip_address)
18
+ return nil if found.nil?
19
+ build_reservation(found.name, found, subnet_address)
20
+ end
21
+
22
+ def find_record_by_mac(subnet_address, mac_address)
23
+ found = find_host('mac' => mac_address)
24
+ return nil if found.nil?
25
+ build_reservation(found.name, found, subnet_address)
26
+ end
27
+
28
+ def find_host(condition)
29
+ return @memoized_host if (!@memoized_host.nil? && @memoized_condition == condition)
30
+ @memoized_condition = condition
31
+ @memoized_host = ::Infoblox::Fixedaddress.find(@connection, condition.merge('_max_results' => 1)).first
32
+ end
33
+
34
+ def find_host_and_name_by_ip(ip_address)
35
+ h = find_host('ipv4addr' => ip_address)
36
+ [h.name, h]
37
+ end
38
+
39
+ def build_host(options)
40
+ host = ::Infoblox::Fixedaddress.new(:connection => @connection)
41
+ host.name = options[:hostname]
42
+ host.ipv4addr = options[:ip]
43
+ host.mac = options[:mac]
44
+ # TODO: nextserver, use_nextserver, bootfile, and use_bootfile attrs exist but are not available in the model
45
+ # Might be useful to extend the model to include these
46
+ #host.nextserver = options[:nextServer]
47
+ #host.use_nextserver = true
48
+ #host.bootfile = options[:filename]
49
+ #host.use_bootfile = true
50
+ host
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ module ::Proxy::DHCP::Infoblox
2
+ class GridRestart
3
+ MAX_ATTEMPTS = 3
4
+
5
+ include ::Proxy::Log
6
+ attr_reader :connection
7
+
8
+ def initialize(connection)
9
+ @connection = connection
10
+ end
11
+
12
+ def try_restart
13
+ logger.debug 'Restarting grid.'
14
+
15
+ MAX_ATTEMPTS.times do |tries|
16
+ sleep tries
17
+ return if restart
18
+ end
19
+
20
+ logger.info 'Restarting Grid failed.'
21
+ false
22
+ end
23
+
24
+ def restart
25
+ (@grid ||= ::Infoblox::Grid.get(@connection).first).restartservices
26
+ true
27
+ rescue Exception => e
28
+ false
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,60 @@
1
+ require 'smart_proxy_dhcp_infoblox/common_crud'
2
+ require 'smart_proxy_dhcp_infoblox/network_address_range_regex_generator'
3
+
4
+ module ::Proxy::DHCP::Infoblox
5
+ class HostIpv4AddressCRUD < CommonCRUD
6
+ def initialize(connection)
7
+ @memoized_host = nil
8
+ @memoized_condition = nil
9
+ super
10
+ end
11
+
12
+ def all_hosts(subnet_address)
13
+ address_range_regex = NetworkAddressesRegularExpressionGenerator.new.generate_regex(subnet_address)
14
+
15
+ hosts = ::Infoblox::Host.find(
16
+ @connection,
17
+ 'ipv4addr~' => address_range_regex,
18
+ '_max_results' => 2147483646)
19
+
20
+ ip_addr_matcher = Regexp.new(address_range_regex) # pre-compile the regex
21
+ hosts.map {|host| build_reservation(host.name, host.ipv4addrs.find {|ip| ip_addr_matcher =~ ip.ipv4addr}, subnet_address)}.compact
22
+ end
23
+
24
+ def find_record_by_ip(subnet_address, ip_address)
25
+ found = find_host('ipv4addr' => ip_address)
26
+ return nil if found.nil?
27
+ build_reservation(found.name, found.ipv4addrs.find {|ip| ip.ipv4addr == ip_address}, subnet_address)
28
+ end
29
+
30
+ def find_record_by_mac(subnet_address, mac_address)
31
+ found = find_host('mac' => mac_address)
32
+ return nil if found.nil?
33
+ build_reservation(found.name, found.ipv4addrs.find {|ip| ip.mac == mac_address}, subnet_address)
34
+ end
35
+
36
+ def find_host_and_name_by_ip(ip_address)
37
+ h = find_host('ipv4addr' => ip_address)
38
+ [h.name, h.ipv4addrs.find {|ip| ip.ipv4addr == ip_address}]
39
+ end
40
+
41
+ def find_host(condition)
42
+ return @memoized_host if (!@memoized_host.nil? && @memoized_condition == condition)
43
+ @memoized_condition = condition
44
+ @memoized_host = ::Infoblox::Host.find(@connection, condition.merge('_max_results' => 1)).first
45
+ end
46
+
47
+ def build_host(options)
48
+ host = ::Infoblox::Host.new(:connection => @connection)
49
+ host.name = options[:hostname]
50
+ host_addr = host.add_ipv4addr(options[:ip]).last
51
+ host_addr.mac = options[:mac]
52
+ host_addr.configure_for_dhcp = true
53
+ host_addr.nextserver = options[:nextServer]
54
+ host_addr.use_nextserver = true
55
+ host_addr.bootfile = options[:filename]
56
+ host_addr.use_bootfile = true
57
+ host
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ module ::Proxy::DHCP::Infoblox
2
+ module IpAddressArithmetic
3
+ def cidr_to_ip_mask(prefix_length)
4
+ bitmask = 0xFFFFFFFF ^ (2 ** (32-prefix_length) - 1)
5
+ (0..3).map {|i| (bitmask >> i*8) & 0xFF}.reverse.join('.')
6
+ end
7
+
8
+ def ipv4_to_i(an_address)
9
+ an_address.split('.').inject(0) {|a, c| (a << 8) + c.to_i}
10
+ end
11
+
12
+ def i_to_ipv4(i)
13
+ (0..3).inject([]) {|a, c| a.push((i >> (c * 8)) & 0xFF)}.reverse.join('.')
14
+ end
15
+
16
+ def cidr_to_bitmask(prefix_length)
17
+ 0xFFFFFFFF ^ (2 ** (32-prefix_length) - 1)
18
+ end
19
+
20
+ def cidr_to_i(an_address_with_cidr)
21
+ an_address_with_cidr.split("/").last.to_i
22
+ end
23
+
24
+ def network_cidr_to_range(network_and_cidr)
25
+ network_addr, cidr = network_and_cidr.split('/')
26
+ mask = cidr_to_bitmask(cidr.to_i)
27
+
28
+ range_start = ipv4_to_i(network_addr) & mask
29
+ range_end = range_start | (0xFFFFFFFF ^ mask)
30
+
31
+ [i_to_ipv4(range_start), i_to_ipv4(range_end)]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,131 @@
1
+ require 'smart_proxy_dhcp_infoblox/ip_address_arithmetic'
2
+
3
+ module ::Proxy::DHCP::Infoblox
4
+ class RangeRegularExpressionGenerator
5
+ class Node
6
+ attr_accessor :value, :children
7
+
8
+ def initialize(value, children = [])
9
+ @value = value
10
+ @children = children
11
+ end
12
+
13
+ def add_children(values)
14
+ return if values.empty?
15
+ node = (found = children.find {|n| n.value == values.first}).nil? ? add_child(Node.new(values.first)) : found
16
+ node.add_children(values[1..-1])
17
+ end
18
+
19
+ def <=>(other)
20
+ return -1 if value.to_s == '0?'
21
+ return 1 if other.value.to_s == '0?'
22
+ return 0 if value == other.value
23
+ return -1 if value < other.value
24
+ 1
25
+ end
26
+
27
+ def add_child(a_node)
28
+ children.push(a_node)
29
+ children.sort!
30
+ a_node
31
+ end
32
+
33
+ def group_children
34
+ children.each {|n| n.group_children}
35
+ return if children.size < 2
36
+ @children = children[1..-1].inject([MergedNode.new(children.first)]) do |grouped, to_group|
37
+ current = MergedNode.new(to_group)
38
+ found = grouped.find {|g| ((g.value != ['0?'] && current.value != ['0?']) || (current.value == ['0?'] && g.value == ['0?'])) && (g.children == current.children)}
39
+ found.nil? ? grouped.push(current) : found.merge(current)
40
+ grouped
41
+ end
42
+ end
43
+
44
+ def as_regex
45
+ children.empty? ? [value.to_s] : children.map {|c| c.as_regex.map {|r| value.to_s + r}}.flatten
46
+ end
47
+ end
48
+
49
+ class MergedNode
50
+ attr_accessor :value, :children
51
+ def initialize(a_node)
52
+ @value = [a_node.value].flatten
53
+ @children = a_node.children
54
+ end
55
+
56
+ def merge(other)
57
+ value.push(other.value).flatten!
58
+ self
59
+ end
60
+
61
+ def as_regex
62
+ children.empty? ? [value_as_regex] : children.map {|c| c.as_regex.map {|r| value_as_regex + r}}.flatten
63
+ end
64
+
65
+ def value_as_regex
66
+ value.size < 2 ? value.first.to_s : "[#{value.join('')}]"
67
+ end
68
+
69
+ def ==(other)
70
+ return false if self.class != other.class
71
+ value == other.value
72
+ end
73
+ end
74
+
75
+ class Root < Node
76
+ def add_number(a_number)
77
+ add_children((['0?', '0?'] + digits(a_number))[-3, 3])
78
+ end
79
+
80
+ def as_regex
81
+ group_children
82
+ "(%s)" % children.map {|c| c.as_regex}.join('|')
83
+ end
84
+
85
+ def digits(a_number)
86
+ to_return = []
87
+ begin
88
+ to_return.push(a_number % 10)
89
+ a_number = a_number / 10
90
+ end while a_number != 0
91
+ to_return.reverse
92
+ end
93
+ end
94
+
95
+ def range_regex(range_start, range_end)
96
+ root = Root.new(nil)
97
+ (range_start..range_end).to_a.each {|i| root.add_number(i)}
98
+ root.as_regex
99
+ end
100
+ end
101
+
102
+ class NetworkAddressesRegularExpressionGenerator
103
+ include IpAddressArithmetic
104
+
105
+ def generate_regex(network_and_cidr)
106
+ range_to_regex(network_cidr_range_octets(network_and_cidr))
107
+ end
108
+
109
+ def network_cidr_range_octets(network_and_cidr)
110
+ range = network_cidr_to_range(network_and_cidr)
111
+ range_start_octets = range.first.split('.').map(&:to_i)
112
+ range_end_octets = range.last.split('.').map(&:to_i)
113
+
114
+ (0..3).map {|i| [range_start_octets[i], range_end_octets[i]]}
115
+ end
116
+
117
+ def range_to_regex(range)
118
+ range.inject([]) do |a, c|
119
+ start_range, end_range = c
120
+ regex = if start_range == end_range
121
+ start_range.to_s
122
+ elsif start_range == 0 && end_range == 255
123
+ '.+'
124
+ else
125
+ RangeRegularExpressionGenerator.new.range_regex(start_range + 1, end_range - 1)
126
+ end
127
+ a.push(regex)
128
+ end.join('\.')
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,37 @@
1
+ module Proxy::DHCP::Infoblox
2
+ class PluginConfiguration
3
+ def load_classes
4
+ require 'infoblox'
5
+ require 'dhcp_common/dhcp_common'
6
+ require 'smart_proxy_dhcp_infoblox/host_ipv4_address_crud'
7
+ require 'smart_proxy_dhcp_infoblox/fixed_address_crud'
8
+ require 'smart_proxy_dhcp_infoblox/grid_restart'
9
+ require 'smart_proxy_dhcp_infoblox/unused_ips'
10
+ require 'smart_proxy_dhcp_infoblox/dhcp_infoblox_main'
11
+ end
12
+
13
+ def load_dependency_injection_wirings(c, settings)
14
+
15
+
16
+ c.dependency :connection, (lambda do
17
+ ::Infoblox.wapi_version = '2.0'
18
+ ::Infoblox::Connection.new(:username => settings[:username] ,:password => settings[:password],
19
+ :host => settings[:server], :ssl_opts => {:verify => false})
20
+ end)
21
+
22
+
23
+ c.dependency :unused_ips, lambda { ::Proxy::DHCP::Infoblox::UnusedIps.new(c.get_dependency(:connection), settings[:use_ranges])}
24
+ c.dependency :host_ipv4_crud, lambda { ::Proxy::DHCP::Infoblox::HostIpv4AddressCRUD.new(c.get_dependency(:connection))}
25
+ c.dependency :fixed_address_crud, lambda { ::Proxy::DHCP::Infoblox::FixedAddressCRUD.new(c.get_dependency(:connection))}
26
+ c.dependency :grid_restart, lambda { ::Proxy::DHCP::Infoblox::GridRestart.new(c.get_dependency(:connection))}
27
+ c.dependency :dhcp_provider, (lambda do
28
+ ::Proxy::DHCP::Infoblox::Provider.new(
29
+ c.get_dependency(:connection),
30
+ settings[:record_type] == 'host' ? c.get_dependency(:host_ipv4_crud) : c.get_dependency(:fixed_address_crud),
31
+ c.get_dependency(:grid_restart),
32
+ c.get_dependency(:unused_ips),
33
+ settings[:subnets])
34
+ end)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ module ::Proxy::DHCP::Infoblox
2
+ class RecordTypeValidator < ::Proxy::PluginValidators::Base
3
+ def validate!(settings)
4
+ return true if ['host', 'fixedaddress'].include?(settings[:record_type])
5
+ raise ::Proxy::Error::ConfigurationError, "Setting 'record_type' can be set to either 'host' or 'fixedaddress'"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,47 @@
1
+ require 'ipaddr'
2
+ require 'smart_proxy_dhcp_infoblox/ip_address_arithmetic'
3
+
4
+ module Proxy::DHCP::Infoblox
5
+ class UnusedIps
6
+ include IpAddressArithmetic
7
+ attr_reader :connection, :use_ranges
8
+
9
+ def initialize(connection, use_ranges)
10
+ @connection = connection
11
+ @use_ranges = use_ranges
12
+ end
13
+
14
+ def unused_ip(network_address, from_ip_address, to_ip_address)
15
+ @use_ranges ? unused_range_ip(network_address, from_ip_address, to_ip_address) : unused_network_ip(network_address, from_ip_address, to_ip_address)
16
+ end
17
+
18
+ def unused_network_ip(network_address, from_ip_address, to_ip_address)
19
+ find_network(network_address).next_available_ip(1, excluded_ips(find_network(network_address).network, from_ip_address, to_ip_address)).first
20
+ end
21
+
22
+ def unused_range_ip(network_address, from_ip_address, to_ip_address)
23
+ find_range(network_address, from_ip_address, to_ip_address).next_available_ip(1).first
24
+ end
25
+
26
+ def excluded_ips(subnet_address, from, to)
27
+ return [] if from.nil? || to.nil?
28
+ (IPAddr.new(network_cidr_to_range(subnet_address).first)..IPAddr.new(network_cidr_to_range(subnet_address).last)).to_a.map(&:to_s) -
29
+ (IPAddr.new(from)..IPAddr.new(to)).to_a.map(&:to_s)
30
+ end
31
+
32
+ def find_range(network_address, from, to)
33
+ ranges = ::Infoblox::Range.find(@connection, 'network~' => network_address)
34
+ range = (from.nil? || to.nil?) ? ranges.first : ranges.find {|r| r.start_addr == from && r.end_addr == to}
35
+ raise "No Ranges found for #{network_address} network" if range.nil?
36
+ range
37
+ end
38
+
39
+ def find_network(network_address)
40
+ return @memoized_network if !@memoized_network.nil? && @memoized_address == network_address
41
+ @memoized_address = network_address
42
+ @memoized_network = ::Infoblox::Network.find(@connection, 'network~' => network_address, '_max_results' => 1).first
43
+ raise "Subnet #{network_address} not found" if @memoized_network.nil?
44
+ @memoized_network
45
+ end
46
+ end
47
+ end