ipaddons 0.3.2__tar.gz → 0.4.0__tar.gz
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.
- {ipaddons-0.3.2 → ipaddons-0.4.0}/PKG-INFO +1 -1
- {ipaddons-0.3.2 → ipaddons-0.4.0}/pyproject.toml +9 -8
- {ipaddons-0.3.2 → ipaddons-0.4.0}/src/ipaddons/__init__.py +3 -1
- {ipaddons-0.3.2 → ipaddons-0.4.0}/src/ipaddons/_version.py +2 -2
- {ipaddons-0.3.2 → ipaddons-0.4.0}/src/ipaddons/tools.py +87 -35
- ipaddons-0.4.0/tests/test_ip_allocation.py +374 -0
- ipaddons-0.3.2/tests/test_ip_allocation.py +0 -142
- {ipaddons-0.3.2 → ipaddons-0.4.0}/.gitignore +0 -0
- {ipaddons-0.3.2 → ipaddons-0.4.0}/LICENSE +0 -0
- {ipaddons-0.3.2 → ipaddons-0.4.0}/README.md +0 -0
- {ipaddons-0.3.2 → ipaddons-0.4.0}/src/ipaddons/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipaddons
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Additional tools for the ipaddress standard library module
|
|
5
5
|
Project-URL: documentation, https://ipaddons.readthedocs.io/
|
|
6
6
|
Project-URL: homepage, https://gitlab.com/sebastianw/ipaddons/
|
|
@@ -31,6 +31,7 @@ urls.repository = "https://gitlab.com/sebastianw/ipaddons/"
|
|
|
31
31
|
|
|
32
32
|
[dependency-groups]
|
|
33
33
|
dev = [
|
|
34
|
+
"coverage>=7.11.3",
|
|
34
35
|
"mypy>=1.18.2",
|
|
35
36
|
"pytest>=8.4.2",
|
|
36
37
|
]
|
|
@@ -68,7 +69,6 @@ lint.extend-select = [
|
|
|
68
69
|
"BLE", # flake8-blind-except Don't catch base exceptions
|
|
69
70
|
"C4", # flake8-comprehensions Unneccessary comprehension code
|
|
70
71
|
"D", # pydocstyle
|
|
71
|
-
"DOC", # pydoclint
|
|
72
72
|
"DTZ", # flake8-datetimez Datetime mistakes
|
|
73
73
|
"E", # pycodestyle errors
|
|
74
74
|
"EM", # flake8-errmsg Bad message strings in exception handling
|
|
@@ -103,6 +103,8 @@ lint.extend-select = [
|
|
|
103
103
|
]
|
|
104
104
|
lint.ignore = [
|
|
105
105
|
"D100", # Public module documentation missing
|
|
106
|
+
"D203", # Blank line before doc-string
|
|
107
|
+
"D212", # new-line after doc-string begins
|
|
106
108
|
"E501", # Line too long
|
|
107
109
|
"G004", # Logging statement uses f-string
|
|
108
110
|
"S101", # Use of assert detected
|
|
@@ -110,6 +112,12 @@ lint.ignore = [
|
|
|
110
112
|
"S603", # Subprocess call
|
|
111
113
|
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass -> slower code
|
|
112
114
|
]
|
|
115
|
+
lint.per-file-ignores."tests/**/*.py" = [
|
|
116
|
+
"D",
|
|
117
|
+
"DOC",
|
|
118
|
+
"I002",
|
|
119
|
+
"INP",
|
|
120
|
+
]
|
|
113
121
|
lint.flake8-comprehensions.allow-dict-calls-with-keyword-arguments = true
|
|
114
122
|
lint.flake8-import-conventions.aliases.datetime = "dt"
|
|
115
123
|
lint.flake8-import-conventions.banned-from = [ "datetime" ]
|
|
@@ -117,13 +125,6 @@ lint.isort.required-imports = [ "from __future__ import annotations" ]
|
|
|
117
125
|
# Enable "FA" rule fixes
|
|
118
126
|
lint.future-annotations = true
|
|
119
127
|
|
|
120
|
-
per-file-ignores."tests/**/*.py" = [
|
|
121
|
-
"D",
|
|
122
|
-
"DOC",
|
|
123
|
-
"I002",
|
|
124
|
-
"INP",
|
|
125
|
-
]
|
|
126
|
-
|
|
127
128
|
[tool.pyproject-fmt]
|
|
128
129
|
max_supported_python = "3.14"
|
|
129
130
|
column_width = 120
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
"""Various addons for the :doc:`ipaddress <python:library/ipaddress>` standard library module."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
|
-
from ._version import __version__, __version_tuple__
|
|
5
|
+
from ._version import __version__, __version_tuple__ # noqa: F401
|
|
4
6
|
from .tools import IPv4Allocation, IPv6Allocation, ip_allocation
|
|
5
7
|
|
|
6
8
|
__all__ = ["IPv4Allocation", "IPv6Allocation", "ip_allocation"]
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.4.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -18,11 +18,14 @@ if TYPE_CHECKING:
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class IPAllocationError(Exception):
|
|
21
|
-
|
|
21
|
+
"""General Error in an IPAllocation object."""
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def ip_allocation(
|
|
25
|
-
|
|
24
|
+
def ip_allocation(
|
|
25
|
+
supernet: UnSpecNet, used_networks: UnspecNetSeq | None = None, ignore_invalid_subnets: bool = True
|
|
26
|
+
) -> IPv4Allocation | IPv6Allocation:
|
|
27
|
+
"""
|
|
28
|
+
Create an IP allocation.
|
|
26
29
|
|
|
27
30
|
Creates and returns an IP allocation object.
|
|
28
31
|
|
|
@@ -32,16 +35,18 @@ def ip_allocation(supernet: UnSpecNet, used_networks: UnspecNetSeq | None = None
|
|
|
32
35
|
either as a string or an :doc:`ipaddress <python:library/ipaddress>` network object.
|
|
33
36
|
:return: An :class:`IPv4Allocation` or :class:`IPv6Allocation` instance, depending on the passed
|
|
34
37
|
:py:attr:`supernet` argument. Same principle as :py:func:`python:ipaddress.ip_network`.
|
|
38
|
+
:param ignore_invalid_subnets: If set to False, used subnets that do not overlap with the supernet will raise
|
|
39
|
+
a ValueError.
|
|
35
40
|
"""
|
|
36
41
|
if used_networks is None:
|
|
37
42
|
used_networks = []
|
|
38
43
|
supernet = ipaddress.ip_network(supernet) if isinstance(supernet, str) else supernet
|
|
39
44
|
if supernet.version == 4:
|
|
40
45
|
assert isinstance(supernet, ipaddress.IPv4Network)
|
|
41
|
-
return IPv4Allocation(supernet, used_networks)
|
|
46
|
+
return IPv4Allocation(supernet, used_networks, ignore_invalid_subnets)
|
|
42
47
|
if supernet.version == 6:
|
|
43
48
|
assert isinstance(supernet, ipaddress.IPv6Network)
|
|
44
|
-
return IPv6Allocation(supernet, used_networks)
|
|
49
|
+
return IPv6Allocation(supernet, used_networks, ignore_invalid_subnets)
|
|
45
50
|
msg = f"IP Version of Supernet is unknown: {supernet.version}"
|
|
46
51
|
raise ValueError(msg)
|
|
47
52
|
|
|
@@ -51,7 +56,8 @@ def _ceildiv(a: int, b: int) -> int:
|
|
|
51
56
|
|
|
52
57
|
|
|
53
58
|
def range_overlap(a: NetworkRange, b: NetworkRange) -> bool:
|
|
54
|
-
"""
|
|
59
|
+
"""
|
|
60
|
+
Network Range Overlap Check.
|
|
55
61
|
|
|
56
62
|
:param a: a network range
|
|
57
63
|
:param b: a network range
|
|
@@ -61,7 +67,8 @@ def range_overlap(a: NetworkRange, b: NetworkRange) -> bool:
|
|
|
61
67
|
|
|
62
68
|
|
|
63
69
|
def range_overlaps(a: NetworkRange, b: list[NetworkRange]) -> NetworkRange | None:
|
|
64
|
-
"""
|
|
70
|
+
"""
|
|
71
|
+
Overlap between a network range and a list of other network ranges.
|
|
65
72
|
|
|
66
73
|
:param a: Network range to check against.
|
|
67
74
|
:param b: List of network ranges that could overlap.
|
|
@@ -74,7 +81,8 @@ def range_overlaps(a: NetworkRange, b: list[NetworkRange]) -> NetworkRange | Non
|
|
|
74
81
|
|
|
75
82
|
|
|
76
83
|
def netrange(network: IPNetwork) -> NetworkRange:
|
|
77
|
-
"""
|
|
84
|
+
"""
|
|
85
|
+
Create a network range from an :doc:python:`ipaddress <library/ipaddress>` object.
|
|
78
86
|
|
|
79
87
|
A network range is the integer presentation of the first and last IP address of the network as a tuple.
|
|
80
88
|
:param network: An :doc:`ipaddress <python:library/ipaddress>` network object.
|
|
@@ -84,30 +92,45 @@ def netrange(network: IPNetwork) -> NetworkRange:
|
|
|
84
92
|
|
|
85
93
|
|
|
86
94
|
def free_ranges(superrange: NetworkRange, used_ranges: list[NetworkRange]) -> Generator[NetworkRange, None, None]:
|
|
87
|
-
"""
|
|
95
|
+
"""
|
|
96
|
+
Get free ranges from a supernet with provided used ranges.
|
|
88
97
|
|
|
89
98
|
:param superrange: Covering range from where free subranges should be returned.
|
|
90
99
|
:param used_ranges: List of used network ranges.
|
|
91
100
|
:return: An interator for free ranges.
|
|
92
101
|
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
remaining_superrange = superrange
|
|
103
|
+
# Make values unique and sort them. This is crucial when interating over them.
|
|
104
|
+
used_ranges = list(dict.fromkeys(used_ranges))
|
|
105
|
+
used_ranges = sorted(used_ranges, key=lambda x: (x[0], -x[1]))
|
|
106
|
+
for used_range in used_ranges:
|
|
107
|
+
if not range_overlap(superrange, used_range):
|
|
108
|
+
msg = f"{used_range} does not overlap with {remaining_superrange}"
|
|
109
|
+
raise ValueError(msg)
|
|
110
|
+
if used_range[0] <= remaining_superrange[0] and used_range[1] >= remaining_superrange[1]:
|
|
111
|
+
# Subrange is as large or larger than the remaining superrange so we have no more free ranges.
|
|
97
112
|
return
|
|
98
|
-
if
|
|
99
|
-
#
|
|
100
|
-
superrange = range[1] + 1, superrange[1]
|
|
113
|
+
if used_range[1] <= remaining_superrange[0] or used_range[0] >= remaining_superrange[1]:
|
|
114
|
+
# Subrange ends below or begins above remaining superrange, ignore.
|
|
101
115
|
continue
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
if remaining_superrange[0] == used_range[0]:
|
|
117
|
+
# Used range is at the beginning of remaining superrange, shrink it.
|
|
118
|
+
remaining_superrange = used_range[1] + 1, remaining_superrange[1]
|
|
119
|
+
continue
|
|
120
|
+
# There is a free range between the beginning of the remaining superrange and the current used range.
|
|
121
|
+
free_range = remaining_superrange[0], used_range[0] - 1
|
|
122
|
+
# Adjust remaining free superrange to begin after current used range.
|
|
123
|
+
remaining_superrange = used_range[1] + 1, remaining_superrange[1]
|
|
124
|
+
assert free_range[0] < free_range[1]
|
|
104
125
|
yield free_range
|
|
105
126
|
# The rest of the superrange is free when there are no more used ranges left
|
|
106
|
-
|
|
127
|
+
assert remaining_superrange[0] < remaining_superrange[1]
|
|
128
|
+
yield remaining_superrange
|
|
107
129
|
|
|
108
130
|
|
|
109
131
|
def merge_ranges(ranges: list[NetworkRange]) -> list[NetworkRange]:
|
|
110
|
-
"""
|
|
132
|
+
"""
|
|
133
|
+
Merge network ranges that overlap.
|
|
111
134
|
|
|
112
135
|
:param ranges: A list of network ranges.
|
|
113
136
|
:return: A list of network ranges with overlapping ranges merged into one.
|
|
@@ -132,7 +155,8 @@ def merge_ranges(ranges: list[NetworkRange]) -> list[NetworkRange]:
|
|
|
132
155
|
def net_size_iterator(
|
|
133
156
|
supernet: IPNetwork, cidr: int, used_ranges: list[NetworkRange] | None = None
|
|
134
157
|
) -> Generator[NetworkRange, None, None]:
|
|
135
|
-
"""
|
|
158
|
+
"""
|
|
159
|
+
Iterate over an ipaddr network object and return subnet network ranges of the specified cidr size.
|
|
136
160
|
|
|
137
161
|
:param supernet: An ipaddr network object.
|
|
138
162
|
:param cidr: CIDR size of the subnets.
|
|
@@ -154,7 +178,8 @@ def net_size_iterator(
|
|
|
154
178
|
|
|
155
179
|
|
|
156
180
|
class _BaseIPAllocation(ABC, Generic[_N]):
|
|
157
|
-
"""
|
|
181
|
+
"""
|
|
182
|
+
An IP allocation.
|
|
158
183
|
|
|
159
184
|
The object represents a supernet (the allocation) with an optional list of used (assigned) subnets. You can also
|
|
160
185
|
request an iterator for free subnets of a specific size.
|
|
@@ -163,14 +188,15 @@ class _BaseIPAllocation(ABC, Generic[_N]):
|
|
|
163
188
|
network object.
|
|
164
189
|
:param used_subnets: A list of subnets of the :py:attr:`supernet` that are already in use in this allocation,
|
|
165
190
|
either as string or an :doc:`ipaddress <python:library/ipaddress>` network object.
|
|
191
|
+
:param ignore_invalid_subnets: If set to False, used subnets that do not overlap with the supernet will raise
|
|
192
|
+
a ValueError.
|
|
166
193
|
"""
|
|
167
194
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def __init__(self, supernet: UnSpecNet, used_subnets: UnspecNetSeq | None = None):
|
|
195
|
+
def __init__(
|
|
196
|
+
self, supernet: UnSpecNet, used_subnets: UnspecNetSeq | None = None, ignore_invalid_subnets: bool = True
|
|
197
|
+
):
|
|
172
198
|
self.lock = RLock()
|
|
173
|
-
self.
|
|
199
|
+
self.ignore_invalid_subnets = ignore_invalid_subnets
|
|
174
200
|
self.supernet: _N = self._normalize_network(supernet)
|
|
175
201
|
if used_subnets is None:
|
|
176
202
|
used_subnets = []
|
|
@@ -186,6 +212,16 @@ class _BaseIPAllocation(ABC, Generic[_N]):
|
|
|
186
212
|
def _network_class(self) -> type[_N]:
|
|
187
213
|
raise NotImplementedError
|
|
188
214
|
|
|
215
|
+
@property
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def _version(self) -> int:
|
|
218
|
+
raise NotImplementedError
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
@abstractmethod
|
|
222
|
+
def _max_prefixlen(self) -> int:
|
|
223
|
+
raise NotImplementedError
|
|
224
|
+
|
|
189
225
|
def _normalize_network(self, _network: UnSpecNet) -> _N:
|
|
190
226
|
network = ipaddress.ip_network(_network) if isinstance(_network, str) else _network
|
|
191
227
|
if not self._version_compatible(network):
|
|
@@ -199,9 +235,18 @@ class _BaseIPAllocation(ABC, Generic[_N]):
|
|
|
199
235
|
def _version_compatible(self, network: IPNetwork) -> TypeGuard[_N]:
|
|
200
236
|
return self._version == network.version
|
|
201
237
|
|
|
238
|
+
def _check_subnet(self, subnet: _N) -> bool:
|
|
239
|
+
if not range_overlap(netrange(self.supernet), netrange(subnet)):
|
|
240
|
+
if not self.ignore_invalid_subnets:
|
|
241
|
+
msg = f"Subnet {subnet} does not overlap with {self.supernet}"
|
|
242
|
+
raise ValueError(msg)
|
|
243
|
+
return False
|
|
244
|
+
return True
|
|
245
|
+
|
|
202
246
|
@property
|
|
203
|
-
def used_subnets(self) -> Generator[
|
|
204
|
-
"""
|
|
247
|
+
def used_subnets(self) -> Generator[_N, None, None]:
|
|
248
|
+
"""
|
|
249
|
+
Used subnets in this allocation.
|
|
205
250
|
|
|
206
251
|
:getter: An iterator for used networks in this allocation.
|
|
207
252
|
:setter: Sets the list of used networks.
|
|
@@ -212,17 +257,23 @@ class _BaseIPAllocation(ABC, Generic[_N]):
|
|
|
212
257
|
@used_subnets.setter
|
|
213
258
|
def used_subnets(self, subnets: UnspecNetSeq) -> None:
|
|
214
259
|
with self.lock:
|
|
215
|
-
self._used_subnets =
|
|
216
|
-
self.
|
|
260
|
+
self._used_subnets = []
|
|
261
|
+
self.add_used_subnets(subnets)
|
|
217
262
|
|
|
218
263
|
def add_used_subnets(self, subnets: UnspecNetSeq) -> None:
|
|
219
|
-
"""
|
|
264
|
+
"""
|
|
265
|
+
Add additional subnets to the list of used networks in this allocation.
|
|
220
266
|
|
|
221
267
|
:param subnets: A list of networks that are added to the in-use networks of this allocation, either as a string
|
|
222
268
|
or :doc:`ipaddress <python:library/ipaddress>` network objects.
|
|
223
269
|
"""
|
|
224
270
|
with self.lock:
|
|
225
|
-
|
|
271
|
+
for _subnet in subnets:
|
|
272
|
+
subnet = self._normalize_network(_subnet)
|
|
273
|
+
if not self._check_subnet(subnet):
|
|
274
|
+
continue
|
|
275
|
+
self._used_subnets.append(subnet)
|
|
276
|
+
self._used_subnets.sort()
|
|
226
277
|
self._update_used_subnet_ranges()
|
|
227
278
|
|
|
228
279
|
def _update_used_subnet_ranges(self, merge: bool = True) -> None:
|
|
@@ -233,14 +284,15 @@ class _BaseIPAllocation(ABC, Generic[_N]):
|
|
|
233
284
|
self._used_network_ranges = ranges
|
|
234
285
|
|
|
235
286
|
def get_free_subnets(self, prefixlen: int) -> Generator[_N, None, None]:
|
|
236
|
-
"""
|
|
287
|
+
"""
|
|
288
|
+
List free subnets of a specific size.
|
|
237
289
|
|
|
238
290
|
:param prefixlen: Size of the free subnets returned in CIDR notation.
|
|
239
291
|
:return: An iterator for free subnets of :py:attr:`prefixlen` size.
|
|
240
292
|
"""
|
|
241
293
|
for n in net_size_iterator(self.supernet, prefixlen, self._used_network_ranges):
|
|
242
294
|
if range_overlaps(n, self._used_network_ranges):
|
|
243
|
-
msg = "This should never happen!"
|
|
295
|
+
msg = "This should never happen! A free range cannot overlap with a used range."
|
|
244
296
|
raise IPAllocationError(msg)
|
|
245
297
|
yield self._network_class((n[0], prefixlen))
|
|
246
298
|
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ipaddress
|
|
4
|
+
from typing import Any, NamedTuple
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from ipaddons import IPv4Allocation, IPv6Allocation, ip_allocation, tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Net(NamedTuple):
|
|
12
|
+
str: str
|
|
13
|
+
ipa: ipaddress.IPv4Network | ipaddress.IPv6Network
|
|
14
|
+
range: tuple[int, int]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def make_nets(input_str: str | list[str]) -> Net | list[Net]:
|
|
18
|
+
if isinstance(input_str, str):
|
|
19
|
+
single = True
|
|
20
|
+
input_str = [input_str]
|
|
21
|
+
else:
|
|
22
|
+
single = False
|
|
23
|
+
|
|
24
|
+
ret = []
|
|
25
|
+
for input_net in input_str:
|
|
26
|
+
net = ipaddress.ip_network(input_net, strict=True)
|
|
27
|
+
ret.append(Net(str(net), net, (int(net[0]), int(net[-1]))))
|
|
28
|
+
|
|
29
|
+
if single:
|
|
30
|
+
return ret[0]
|
|
31
|
+
return ret
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def net_attr(nets: Net | list[Net], attribute: str) -> Any | list[Any]:
|
|
35
|
+
if isinstance(nets, Net):
|
|
36
|
+
return getattr(nets, attribute)
|
|
37
|
+
return [getattr(n, attribute) for n in nets]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
v4_supernet = make_nets("10.0.0.0/8")
|
|
41
|
+
|
|
42
|
+
v4_subnets = make_nets(
|
|
43
|
+
[
|
|
44
|
+
"10.0.0.0/16",
|
|
45
|
+
"10.1.1.0/24",
|
|
46
|
+
"10.1.1.16/29",
|
|
47
|
+
"10.1.2.0/25",
|
|
48
|
+
"10.1.2.0/24",
|
|
49
|
+
"10.1.3.0/25",
|
|
50
|
+
"10.1.3.64/26",
|
|
51
|
+
"10.1.3.128/26",
|
|
52
|
+
]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
v6_supernet = make_nets("fc00::/32")
|
|
56
|
+
|
|
57
|
+
v6_subnets = make_nets(
|
|
58
|
+
[
|
|
59
|
+
"fc00::/48",
|
|
60
|
+
"fc00:0:a::/56",
|
|
61
|
+
"fc00:0:b::/56",
|
|
62
|
+
"fc00:0:b:100::/56",
|
|
63
|
+
"fc00:0:b:200::/64",
|
|
64
|
+
"fc00:0:b:100::/56",
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
mergeable_subnets = [
|
|
70
|
+
[
|
|
71
|
+
(int(ipaddress.IPv4Network(n)[0]), int(ipaddress.IPv4Network(n)[-1]))
|
|
72
|
+
for n in ["192.168.0.0/24", "192.168.0.0/25", "192.168.0.128/25", "192.168.0.64/29"]
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
(int(ipaddress.IPv6Network(n)[0]), int(ipaddress.IPv6Network(n)[-1]))
|
|
76
|
+
for n in ["fc01::/32", "fc01::/64", "fc01:0:ffff::/48", "fc01:0:cafe::/64"]
|
|
77
|
+
],
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize(
|
|
82
|
+
("supernet", "subnets", "allocation_class", "network_class"),
|
|
83
|
+
[
|
|
84
|
+
(net_attr(v4_supernet, "ipa"), net_attr(v4_subnets, "ipa"), IPv4Allocation, ipaddress.IPv4Network),
|
|
85
|
+
(net_attr(v4_supernet, "str"), net_attr(v4_subnets, "str"), IPv4Allocation, ipaddress.IPv4Network),
|
|
86
|
+
(net_attr(v6_supernet, "ipa"), net_attr(v6_subnets, "ipa"), IPv6Allocation, ipaddress.IPv6Network),
|
|
87
|
+
(net_attr(v6_supernet, "str"), net_attr(v6_subnets, "str"), IPv6Allocation, ipaddress.IPv6Network),
|
|
88
|
+
(net_attr(v4_supernet, "ipa"), net_attr(v4_subnets[:1], "ipa"), IPv4Allocation, ipaddress.IPv4Network),
|
|
89
|
+
(net_attr(v6_supernet, "ipa"), net_attr(v6_subnets[:1], "ipa"), IPv6Allocation, ipaddress.IPv6Network),
|
|
90
|
+
(net_attr(v4_supernet, "ipa"), [], IPv4Allocation, ipaddress.IPv4Network),
|
|
91
|
+
(net_attr(v6_supernet, "ipa"), [], IPv6Allocation, ipaddress.IPv6Network),
|
|
92
|
+
],
|
|
93
|
+
)
|
|
94
|
+
def test_allocation_classes(supernet, subnets, allocation_class, network_class):
|
|
95
|
+
a = ip_allocation(supernet, used_networks=subnets)
|
|
96
|
+
assert isinstance(a, allocation_class)
|
|
97
|
+
assert all(isinstance(n, network_class) for n in a.used_subnets)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_base_abstract_methods():
|
|
101
|
+
class TestAllocation(tools._BaseIPAllocation):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
with pytest.raises(
|
|
105
|
+
TypeError,
|
|
106
|
+
match=r"Can't instantiate abstract class TestAllocation without an implementation for abstract methods '_max_prefixlen', '_network_class', '_version'",
|
|
107
|
+
):
|
|
108
|
+
TestAllocation()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.mark.parametrize(
|
|
112
|
+
("supernet", "used_nets", "cidr", "first_free", "last_free", "num_subnets"),
|
|
113
|
+
[
|
|
114
|
+
("10.0.0.0/8", ["10.0.0.0/16", "10.1.0.0/24"], 16, "10.2.0.0/16", "10.255.0.0/16", 254),
|
|
115
|
+
("2001:db8::/32", ["2001:db8::/48", "2001:db8:1::/120"], 48, "2001:db8:2::/48", "2001:db8:ffff::/48", 65534),
|
|
116
|
+
("10.0.0.0/8", ["10.5.0.0/16", "10.193.0.0/24"], 16, "10.0.0.0/16", "10.255.0.0/16", 254),
|
|
117
|
+
("2001:db8::/32", ["2001:db8:4::/48", "2001:db8:fca::/120"], 48, "2001:db8::/48", "2001:db8:ffff::/48", 65534),
|
|
118
|
+
("10.0.0.0/8", ["10.5.0.0/16", "10.255.0.0/24"], 16, "10.0.0.0/16", "10.254.0.0/16", 254),
|
|
119
|
+
("2001:db8::/32", ["2001:db8:4::/48", "2001:db8:ffff::/120"], 48, "2001:db8::/48", "2001:db8:fffe::/48", 65534),
|
|
120
|
+
],
|
|
121
|
+
)
|
|
122
|
+
def test_free_networks(supernet, used_nets, cidr, first_free, last_free, num_subnets):
|
|
123
|
+
allocation = ip_allocation(supernet, used_nets)
|
|
124
|
+
free_nets = list(allocation.get_free_subnets(cidr))
|
|
125
|
+
assert first_free == str(free_nets[0])
|
|
126
|
+
assert last_free == str(free_nets[-1])
|
|
127
|
+
assert num_subnets == len(free_nets)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.mark.parametrize(
|
|
131
|
+
("subnet", "subnet_range"),
|
|
132
|
+
zip(
|
|
133
|
+
net_attr(v4_subnets, "ipa") + net_attr(v6_subnets, "ipa"),
|
|
134
|
+
net_attr(v4_subnets, "range") + net_attr(v6_subnets, "range"),
|
|
135
|
+
strict=True,
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
def test_netrange(subnet, subnet_range):
|
|
139
|
+
assert tools.netrange(subnet) == subnet_range
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@pytest.mark.parametrize(
|
|
143
|
+
("supernet", "subnets", "subnet_ranges"),
|
|
144
|
+
[
|
|
145
|
+
(net_attr(v4_supernet, "ipa"), net_attr(v4_subnets, "ipa"), net_attr(v4_subnets, "range")),
|
|
146
|
+
(net_attr(v6_supernet, "ipa"), net_attr(v6_subnets, "ipa"), net_attr(v6_subnets, "range")),
|
|
147
|
+
],
|
|
148
|
+
)
|
|
149
|
+
def test_allocation_ranges(supernet, subnets, subnet_ranges):
|
|
150
|
+
a = ip_allocation(supernet, used_networks=subnets, ignore_invalid_subnets=False)
|
|
151
|
+
a._update_used_subnet_ranges(merge=False)
|
|
152
|
+
assert sorted(a._used_network_ranges) == sorted(subnet_ranges)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.parametrize(
|
|
156
|
+
("supernet_range", "subnet_ranges"),
|
|
157
|
+
[
|
|
158
|
+
(net_attr(v4_supernet, "range"), net_attr(v4_subnets, "range")),
|
|
159
|
+
(net_attr(v6_supernet, "range"), net_attr(v6_subnets, "range")),
|
|
160
|
+
],
|
|
161
|
+
)
|
|
162
|
+
def test_range_overlaps(supernet_range, subnet_ranges):
|
|
163
|
+
assert tools.range_overlaps(supernet_range, subnet_ranges) == subnet_ranges[0]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@pytest.mark.parametrize(
|
|
167
|
+
("supernet_range", "subnet_ranges"),
|
|
168
|
+
[
|
|
169
|
+
(net_attr(v4_supernet, "range"), net_attr(v6_subnets, "range")),
|
|
170
|
+
(net_attr(v6_supernet, "range"), net_attr(v4_subnets, "range")),
|
|
171
|
+
],
|
|
172
|
+
)
|
|
173
|
+
def test_range_no_overlaps(supernet_range, subnet_ranges):
|
|
174
|
+
assert tools.range_overlaps(supernet_range, subnet_ranges) is None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@pytest.mark.parametrize(
|
|
178
|
+
("supernet", "subnets"),
|
|
179
|
+
[
|
|
180
|
+
(net_attr(v4_supernet, "ipa"), net_attr(v4_subnets, "ipa")),
|
|
181
|
+
(net_attr(v6_supernet, "ipa"), net_attr(v6_subnets, "ipa")),
|
|
182
|
+
],
|
|
183
|
+
)
|
|
184
|
+
def test_add_used_subnets(supernet, subnets):
|
|
185
|
+
a = ip_allocation(supernet, ignore_invalid_subnets=False)
|
|
186
|
+
a.add_used_subnets([subnets[0]])
|
|
187
|
+
a.add_used_subnets(subnets[1:])
|
|
188
|
+
assert sorted(a.used_subnets) == sorted(subnets)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@pytest.mark.parametrize(
|
|
192
|
+
"subnets",
|
|
193
|
+
mergeable_subnets,
|
|
194
|
+
)
|
|
195
|
+
def test_merge(subnets):
|
|
196
|
+
covering_prefix = subnets[0]
|
|
197
|
+
assert tools.merge_ranges(subnets) == [covering_prefix]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@pytest.mark.parametrize(
|
|
201
|
+
"subnets",
|
|
202
|
+
mergeable_subnets,
|
|
203
|
+
)
|
|
204
|
+
def test_merge_one(subnets):
|
|
205
|
+
assert subnets[:1] == tools.merge_ranges(subnets[:1])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_merge_empty():
|
|
209
|
+
assert tools.merge_ranges([]) == []
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@pytest.mark.parametrize(
|
|
213
|
+
("supernet", "used_nets", "cidr", "first_free"),
|
|
214
|
+
[
|
|
215
|
+
("2001:db8::/32", ["2001:db8::/48", "2001:db8:1::/120"], 64, "2001:db8:1:1::/64"),
|
|
216
|
+
("10.0.0.0/8", ["10.0.0.0/16", "10.1.0.0/24"], 16, "10.2.0.0/16"),
|
|
217
|
+
],
|
|
218
|
+
)
|
|
219
|
+
def test_netsize_iterator(supernet, used_nets, cidr, first_free):
|
|
220
|
+
supernet = ipaddress.ip_network(supernet)
|
|
221
|
+
used_nets = [(int(ipaddress.ip_network(n)[0]), int(ipaddress.ip_network(n)[-1])) for n in used_nets]
|
|
222
|
+
i = tools.net_size_iterator(supernet, cidr, used_nets)
|
|
223
|
+
first_ip, last_ip = next(i)
|
|
224
|
+
net = next(ipaddress.summarize_address_range(ipaddress.ip_address(first_ip), ipaddress.ip_address(last_ip)))
|
|
225
|
+
assert first_free == str(net)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@pytest.mark.parametrize(
|
|
229
|
+
("supernet", "used_nets", "cidr", "last_free"),
|
|
230
|
+
[
|
|
231
|
+
("10.0.0.0/24", ["10.0.0.0/29", "10.0.0.96/30"], 30, "10.0.0.252/30"),
|
|
232
|
+
("2001:db8::/32", ["2001:db8::/48", "2001:db8:1::/120"], 48, "2001:db8:ffff::/48"),
|
|
233
|
+
],
|
|
234
|
+
)
|
|
235
|
+
def test_netsize_iterator_last(supernet, used_nets, cidr, last_free):
|
|
236
|
+
supernet = ipaddress.ip_network(supernet)
|
|
237
|
+
used_nets = [(int(ipaddress.ip_network(n)[0]), int(ipaddress.ip_network(n)[-1])) for n in used_nets]
|
|
238
|
+
i = tools.net_size_iterator(supernet, cidr, used_nets)
|
|
239
|
+
first_ip, last_ip = list(i)[-1]
|
|
240
|
+
net = next(ipaddress.summarize_address_range(ipaddress.ip_address(first_ip), ipaddress.ip_address(last_ip)))
|
|
241
|
+
assert last_free == str(net)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@pytest.mark.parametrize(
|
|
245
|
+
("supernet", "subnets", "used_networks"),
|
|
246
|
+
[
|
|
247
|
+
("10.0.0.0/24", ["192.168.1.0/30", "10.0.0.0/25"], ["10.0.0.0/25"]),
|
|
248
|
+
("fc00::/32", ["fd00::/64", "fc00::/64"], ["fc00::/64"]),
|
|
249
|
+
],
|
|
250
|
+
)
|
|
251
|
+
def test_outside_networks(supernet, subnets, used_networks):
|
|
252
|
+
allocation = ip_allocation(supernet, subnets)
|
|
253
|
+
assert sorted([str(s) for s in allocation.used_subnets]) == sorted(used_networks)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@pytest.mark.parametrize(
|
|
257
|
+
("supernet", "subnets", "used_networks"),
|
|
258
|
+
[
|
|
259
|
+
("10.0.0.0/24", ["192.168.1.0/30", "10.0.0.0/25"], ["10.0.0.0/25"]),
|
|
260
|
+
("fc00::/32", ["fd00::/64", "fc00::/64"], ["fc00::/64"]),
|
|
261
|
+
],
|
|
262
|
+
)
|
|
263
|
+
def test_outside_networks_added(supernet, subnets, used_networks):
|
|
264
|
+
allocation = ip_allocation(supernet)
|
|
265
|
+
allocation.add_used_subnets(subnets)
|
|
266
|
+
assert sorted([str(s) for s in allocation.used_subnets]) == sorted(used_networks)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@pytest.mark.parametrize(
|
|
270
|
+
("supernet", "subnets"),
|
|
271
|
+
[
|
|
272
|
+
("10.0.0.0/24", ["192.168.1.0/30", "10.0.0.0/25"]),
|
|
273
|
+
("fc00::/32", ["fd00::/64", "fc00::/64"]),
|
|
274
|
+
],
|
|
275
|
+
)
|
|
276
|
+
def test_outside_networks_exception(supernet, subnets):
|
|
277
|
+
with pytest.raises(ValueError, match=r"Subnet .*? does not overlap with .*?"):
|
|
278
|
+
_ = ip_allocation(supernet, subnets, ignore_invalid_subnets=False)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@pytest.mark.parametrize(
|
|
282
|
+
("supernet", "subnets"),
|
|
283
|
+
[
|
|
284
|
+
("10.0.0.0/24", ["192.168.1.0/30", "10.0.0.0/25"]),
|
|
285
|
+
("fc00::/32", ["fd00::/64", "fc00::/64"]),
|
|
286
|
+
],
|
|
287
|
+
)
|
|
288
|
+
def test_outside_networks_added_exception(supernet, subnets):
|
|
289
|
+
allocation = ip_allocation(supernet, ignore_invalid_subnets=False)
|
|
290
|
+
with pytest.raises(ValueError, match=r" does not overlap with "):
|
|
291
|
+
allocation.add_used_subnets(subnets)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@pytest.mark.parametrize(
|
|
295
|
+
("supernet_range", "subnet_ranges"),
|
|
296
|
+
[
|
|
297
|
+
(net_attr(v4_supernet, "range"), net_attr(v6_subnets, "range")),
|
|
298
|
+
(net_attr(v6_supernet, "range"), net_attr(v4_subnets, "range")),
|
|
299
|
+
],
|
|
300
|
+
)
|
|
301
|
+
def test_free_ranges_mismatch(supernet_range, subnet_ranges):
|
|
302
|
+
range_iterator = tools.free_ranges(supernet_range, subnet_ranges)
|
|
303
|
+
with pytest.raises(ValueError, match=r" does not overlap with "):
|
|
304
|
+
next(range_iterator)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@pytest.mark.parametrize(
|
|
308
|
+
("supernet", "subnets", "free_size"),
|
|
309
|
+
[
|
|
310
|
+
(make_nets("10.0.0.0/16"), make_nets(["10.0.0.0/8"]), 29),
|
|
311
|
+
(make_nets("fc00::/32"), make_nets(["fc00::/16"]), 64),
|
|
312
|
+
(make_nets("10.0.0.0/16"), make_nets(["10.0.0.0/17", "10.0.128.0/17"]), 29),
|
|
313
|
+
(make_nets("fc00::/32"), make_nets(["fc00::/33", "fc00:0:8000::/33"]), 64),
|
|
314
|
+
(make_nets("10.0.0.0/16"), make_nets(["10.0.0.0/17", "10.0.0.0/8"]), 29),
|
|
315
|
+
(make_nets("fc00::/32"), make_nets(["fc00::/33", "fc00::/16"]), 64),
|
|
316
|
+
(make_nets("10.0.0.0/16"), make_nets(["10.0.0.0/17", "10.0.0.0/17", "10.0.128.0/17"]), 29),
|
|
317
|
+
(make_nets("fc00::/32"), make_nets(["fc00::/33", "fc00::/33", "fc00:0:8000::/33"]), 64),
|
|
318
|
+
(make_nets("10.0.0.0/16"), make_nets(["10.0.0.0/17", "10.0.128.0/17", "10.0.128.0/17"]), 29),
|
|
319
|
+
(make_nets("fc00::/32"), make_nets(["fc00::/33", "fc00:0:8000::/33", "fc00:0:8000::/33"]), 64),
|
|
320
|
+
(make_nets("10.0.0.0/16"), make_nets(["10.0.0.0/17", "10.0.0.0/24", "10.0.128.0/17"]), 29),
|
|
321
|
+
(make_nets("fc00::/32"), make_nets(["fc00::/33", "fc00::/64", "fc00:0:8000::/33"]), 64),
|
|
322
|
+
],
|
|
323
|
+
)
|
|
324
|
+
class TestNoFreeRanges:
|
|
325
|
+
def test_no_free_ranges(self, supernet, subnets, free_size):
|
|
326
|
+
range_iterator = tools.free_ranges(supernet.range, net_attr(subnets, "range"))
|
|
327
|
+
with pytest.raises(StopIteration):
|
|
328
|
+
next(range_iterator)
|
|
329
|
+
|
|
330
|
+
def test_no_free_subnets(self, supernet, subnets, free_size):
|
|
331
|
+
allocation = ip_allocation(supernet.ipa, net_attr(subnets, "ipa"))
|
|
332
|
+
with pytest.raises(StopIteration):
|
|
333
|
+
next(allocation.get_free_subnets(free_size))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@pytest.mark.parametrize(
|
|
337
|
+
("supernet", "subnets"),
|
|
338
|
+
[
|
|
339
|
+
(net_attr(v4_supernet, "str"), net_attr(v6_subnets, "str")),
|
|
340
|
+
(net_attr(v6_supernet, "str"), net_attr(v4_subnets, "str")),
|
|
341
|
+
],
|
|
342
|
+
)
|
|
343
|
+
def test_version_mismatch(supernet, subnets):
|
|
344
|
+
with pytest.raises(ValueError, match=r"IP Version missmatch"):
|
|
345
|
+
ip_allocation(supernet, subnets)
|
|
346
|
+
|
|
347
|
+
a = ip_allocation(supernet)
|
|
348
|
+
with pytest.raises(ValueError, match=r"IP Version missmatch"):
|
|
349
|
+
a._normalize_networks(subnets)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@pytest.mark.parametrize(
|
|
353
|
+
("networks", "network_class"),
|
|
354
|
+
[
|
|
355
|
+
([net_attr(v4_supernet, "str"), *net_attr(v4_subnets, "str")], ipaddress.IPv4Network),
|
|
356
|
+
([net_attr(v6_supernet, "str"), *net_attr(v6_subnets, "str")], ipaddress.IPv6Network),
|
|
357
|
+
],
|
|
358
|
+
)
|
|
359
|
+
def test_network_normalize(networks, network_class):
|
|
360
|
+
a = ip_allocation(networks[0])
|
|
361
|
+
assert all(isinstance(n, network_class) for n in a._normalize_networks(networks))
|
|
362
|
+
assert all(str(n) == networks[i] for i, n in enumerate(a._normalize_networks(networks)))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@pytest.mark.parametrize(
|
|
366
|
+
("supernet", "subnets", "new_subnets"),
|
|
367
|
+
[
|
|
368
|
+
("10.0.0.0/8", ["10.0.0.0/24", "10.1.0.0/24"], ["10.255.0.0/16", "10.254.30.0/24"]),
|
|
369
|
+
],
|
|
370
|
+
)
|
|
371
|
+
def test_used_subnet_setter(supernet, subnets, new_subnets):
|
|
372
|
+
allocation = ip_allocation(supernet, subnets)
|
|
373
|
+
allocation.used_subnets = new_subnets
|
|
374
|
+
assert sorted([str(n) for n in allocation.used_subnets]) == sorted(new_subnets)
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import ipaddress
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from ipaddons import IPv4Allocation, IPv6Allocation, ip_allocation, tools
|
|
8
|
-
|
|
9
|
-
v4_supernet_string = "10.0.0.0/8"
|
|
10
|
-
v4_supernet = ipaddress.IPv4Network(v4_supernet_string)
|
|
11
|
-
|
|
12
|
-
v4_subnet_strings = [
|
|
13
|
-
"10.0.0.0/16",
|
|
14
|
-
"10.1.1.0/24",
|
|
15
|
-
"10.1.1.16/29",
|
|
16
|
-
"10.1.2.0/25",
|
|
17
|
-
"10.1.2.0/24",
|
|
18
|
-
"10.1.3.0/25",
|
|
19
|
-
"10.1.3.64/26",
|
|
20
|
-
"10.1.3.128/26",
|
|
21
|
-
]
|
|
22
|
-
v4_subnets = [ipaddress.IPv4Network(n) for n in v4_subnet_strings]
|
|
23
|
-
v4_subnet_ranges = [(int(n[0]), int(n[-1])) for n in v4_subnets]
|
|
24
|
-
|
|
25
|
-
v6_supernet_string = "fc00::/32"
|
|
26
|
-
v6_supernet = ipaddress.IPv6Network(v6_supernet_string)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
v6_subnet_strings = [
|
|
30
|
-
"fc00::/48",
|
|
31
|
-
"fc00:a::/56",
|
|
32
|
-
"fc00:b::/56",
|
|
33
|
-
"fc00:b:0:100::/56",
|
|
34
|
-
"fc00:b:0:200::/64",
|
|
35
|
-
"fc00:b:0:100::/56",
|
|
36
|
-
"fc00:c::/44",
|
|
37
|
-
]
|
|
38
|
-
v6_subnets = [ipaddress.IPv6Network(n) for n in v6_subnet_strings]
|
|
39
|
-
v6_subnet_ranges = [(int(n[0]), int(n[-1])) for n in v6_subnets]
|
|
40
|
-
|
|
41
|
-
mergeable_subnets = [
|
|
42
|
-
[
|
|
43
|
-
(int(ipaddress.IPv4Network(n)[0]), int(ipaddress.IPv4Network(n)[-1]))
|
|
44
|
-
for n in ["192.168.0.0/24", "192.168.0.0/25", "192.168.0.128/25", "192.168.0.64/29"]
|
|
45
|
-
],
|
|
46
|
-
[
|
|
47
|
-
(int(ipaddress.IPv6Network(n)[0]), int(ipaddress.IPv6Network(n)[-1]))
|
|
48
|
-
for n in ["fc01::/32", "fc01::/64", "fc01:0:ffff::/48", "fc01:0:cafe::/64"]
|
|
49
|
-
],
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@pytest.mark.parametrize(
|
|
54
|
-
("supernet", "subnets", "allocation_class", "network_class"),
|
|
55
|
-
[
|
|
56
|
-
(v4_supernet, v4_subnets, IPv4Allocation, ipaddress.IPv4Network),
|
|
57
|
-
(v4_supernet_string, v4_subnet_strings, IPv4Allocation, ipaddress.IPv4Network),
|
|
58
|
-
(v6_supernet, v6_subnets, IPv6Allocation, ipaddress.IPv6Network),
|
|
59
|
-
(v6_supernet_string, v6_subnet_strings, IPv6Allocation, ipaddress.IPv6Network),
|
|
60
|
-
(v4_supernet, v4_subnets[:1], IPv4Allocation, ipaddress.IPv4Network),
|
|
61
|
-
(v6_supernet, v6_subnets[:1], IPv6Allocation, ipaddress.IPv6Network),
|
|
62
|
-
(v4_supernet, [], IPv4Allocation, ipaddress.IPv4Network),
|
|
63
|
-
(v6_supernet, [], IPv6Allocation, ipaddress.IPv6Network),
|
|
64
|
-
],
|
|
65
|
-
)
|
|
66
|
-
def test_allocation_classes(supernet, subnets, allocation_class, network_class):
|
|
67
|
-
a = ip_allocation(supernet, used_networks=subnets)
|
|
68
|
-
assert isinstance(a, allocation_class)
|
|
69
|
-
assert all(isinstance(n, network_class) for n in a.used_subnets)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@pytest.mark.parametrize(
|
|
73
|
-
("subnet", "subnet_range"),
|
|
74
|
-
zip(v4_subnets + v6_subnets, v4_subnet_ranges + v6_subnet_ranges, strict=True),
|
|
75
|
-
)
|
|
76
|
-
def test_netrange(subnet, subnet_range):
|
|
77
|
-
assert tools.netrange(subnet) == subnet_range
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@pytest.mark.parametrize(
|
|
81
|
-
("supernet", "subnets", "subnet_ranges"),
|
|
82
|
-
[
|
|
83
|
-
(v4_supernet, v4_subnets, v4_subnet_ranges),
|
|
84
|
-
(v6_supernet, v6_subnets, v6_subnet_ranges),
|
|
85
|
-
],
|
|
86
|
-
)
|
|
87
|
-
def test_allocation_ranges(supernet, subnets, subnet_ranges):
|
|
88
|
-
a = ip_allocation(supernet, used_networks=subnets)
|
|
89
|
-
a._update_used_subnet_ranges(merge=False)
|
|
90
|
-
assert sorted(a._used_network_ranges) == sorted(subnet_ranges)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@pytest.mark.parametrize(
|
|
94
|
-
"subnets",
|
|
95
|
-
mergeable_subnets,
|
|
96
|
-
)
|
|
97
|
-
def test_merge(subnets):
|
|
98
|
-
covering_prefix = subnets[0]
|
|
99
|
-
assert tools.merge_ranges(subnets) == [covering_prefix]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@pytest.mark.parametrize(
|
|
103
|
-
"subnets",
|
|
104
|
-
mergeable_subnets,
|
|
105
|
-
)
|
|
106
|
-
def test_merge_one(subnets):
|
|
107
|
-
assert subnets[:1] == tools.merge_ranges(subnets[:1])
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def test_merge_empty():
|
|
111
|
-
assert tools.merge_ranges([]) == []
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@pytest.mark.parametrize(
|
|
115
|
-
("supernet", "used_nets", "cidr", "first_free"),
|
|
116
|
-
[
|
|
117
|
-
("2001:db8::/32", ["2001:db8::/48", "2001:db8:1::/120"], 64, "2001:db8:1:1::/64"),
|
|
118
|
-
("10.0.0.0/8", ["10.0.0.0/16", "10.1.0.0/24"], 16, "10.2.0.0/16"),
|
|
119
|
-
],
|
|
120
|
-
)
|
|
121
|
-
def test_netsize_iterator(supernet, used_nets, cidr, first_free):
|
|
122
|
-
supernet = ipaddress.ip_network(supernet)
|
|
123
|
-
used_nets = [(int(ipaddress.ip_network(n)[0]), int(ipaddress.ip_network(n)[-1])) for n in used_nets]
|
|
124
|
-
i = tools.net_size_iterator(supernet, cidr, used_nets)
|
|
125
|
-
first_ip, last_ip = next(i)
|
|
126
|
-
net = next(ipaddress.summarize_address_range(ipaddress.ip_address(first_ip), ipaddress.ip_address(last_ip)))
|
|
127
|
-
assert first_free == str(net)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@pytest.mark.parametrize(
|
|
131
|
-
("supernet", "used_nets", "cidr", "last_free"),
|
|
132
|
-
[
|
|
133
|
-
("10.0.0.0/24", ["10.0.0.0/29", "10.0.0.96/30"], 30, "10.0.0.252/30"),
|
|
134
|
-
],
|
|
135
|
-
)
|
|
136
|
-
def test_netsize_iterator_last(supernet, used_nets, cidr, last_free):
|
|
137
|
-
supernet = ipaddress.ip_network(supernet)
|
|
138
|
-
used_nets = [(int(ipaddress.ip_network(n)[0]), int(ipaddress.ip_network(n)[-1])) for n in used_nets]
|
|
139
|
-
i = tools.net_size_iterator(supernet, cidr, used_nets)
|
|
140
|
-
first_ip, last_ip = list(i)[-1]
|
|
141
|
-
net = next(ipaddress.summarize_address_range(ipaddress.ip_address(first_ip), ipaddress.ip_address(last_ip)))
|
|
142
|
-
assert last_free == str(net)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|