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.
- checksums.yaml +4 -4
- data/README.md +15 -10
- data/config/dhcp_infoblox.yml.example +17 -0
- data/lib/smart_proxy_dhcp_infoblox.rb +9 -3
- data/lib/smart_proxy_dhcp_infoblox/common_crud.rb +75 -0
- data/lib/smart_proxy_dhcp_infoblox/dhcp_infoblox_main.rb +39 -225
- data/lib/smart_proxy_dhcp_infoblox/dhcp_infoblox_plugin.rb +9 -17
- data/lib/smart_proxy_dhcp_infoblox/dhcp_infoblox_version.rb +1 -1
- data/lib/smart_proxy_dhcp_infoblox/fixed_address_crud.rb +53 -0
- data/lib/smart_proxy_dhcp_infoblox/grid_restart.rb +31 -0
- data/lib/smart_proxy_dhcp_infoblox/host_ipv4_address_crud.rb +60 -0
- data/lib/smart_proxy_dhcp_infoblox/ip_address_arithmetic.rb +34 -0
- data/lib/smart_proxy_dhcp_infoblox/network_address_range_regex_generator.rb +131 -0
- data/lib/smart_proxy_dhcp_infoblox/plugin_configuration.rb +37 -0
- data/lib/smart_proxy_dhcp_infoblox/record_type_validator.rb +8 -0
- data/lib/smart_proxy_dhcp_infoblox/unused_ips.rb +47 -0
- data/test/host_and_fixedaddress_crud_test.rb +169 -0
- data/test/infoblox_provider_test.rb +61 -0
- data/test/plugin_configuration_test.rb +73 -0
- data/test/record_type_validator_test.rb +20 -0
- data/test/regex_generator_test.rb +86 -0
- data/test/test_helper.rb +0 -2
- data/test/unused_ip_test.rb +48 -0
- metadata +25 -49
- data/config/dhcp_infoblox.yml +0 -14
- data/lib/smart_proxy_dhcp_infoblox/dhcp_infoblox_dependencies.rb +0 -5
- data/test/dhcp_infoblox_record_test.rb +0 -95
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|