From da617f195be9f0811ce48fb8fe69fbac6d600c51 Mon Sep 17 00:00:00 2001 From: Jonathan Lundy Date: Wed, 17 Jun 2020 16:04:11 -0600 Subject: [PATCH] move nettree to its own place. various other changes --- .gitignore | 2 +- data/.rpsl | 8 +- data/schema/NAMESPACE-SCHEMA | 3 +- data/schema/key | 0 utils/registry/.schema | 363 +++++++++++++++++++++++++++++ utils/registry/build-index.py | 119 ++-------- utils/registry/dom/nettree.py | 136 +++++++++++ utils/registry/dom/schema.py | 5 + utils/registry/dom/test_nettree.py | 62 +++++ utils/registry/dom/transact.py | 7 + 10 files changed, 599 insertions(+), 106 deletions(-) delete mode 100644 data/schema/key create mode 100644 utils/registry/.schema create mode 100644 utils/registry/dom/nettree.py create mode 100644 utils/registry/dom/test_nettree.py diff --git a/.gitignore b/.gitignore index 82b5a5b3b..173a33a59 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ whoisd/ __pycache__ .index .links -.netindex +.nettree diff --git a/data/.rpsl b/data/.rpsl index 880e30d75..b72f04752 100644 --- a/data/.rpsl +++ b/data/.rpsl @@ -5,5 +5,9 @@ default-owner: DN42-MNT network-owner: inet6num inet6num network-owner: inet6num route network-owner: inet6num inetnum -network-owner: inetnum inetnum -network-owner: inetnum route +network-owner: inetnum inetnum +network-owner: inetnum route +primary-key: inetnum cidr +primary-key: inet6num cidr +primary-key: person nic-hdl +primary-key: role nic-hdl \ No newline at end of file diff --git a/data/schema/NAMESPACE-SCHEMA b/data/schema/NAMESPACE-SCHEMA index 20a7f8a95..f39e93807 100644 --- a/data/schema/NAMESPACE-SCHEMA +++ b/data/schema/NAMESPACE-SCHEMA @@ -1,9 +1,10 @@ schema: NAMESPACE-SCHEMA -ref: dn42.schema +ref: dn42.namespace key: namespace required single primary schema > [name] key: ns-schema required single > [schema] key: ns-owner required single > [schema] key: default-owner optional single lookup=dn42.mntner > [mntner] key: network-owner optional multiple > [parent-schema] [child-schema] +key: primary-key optional multiple > [schema] [primary] mnt-by: DN42-MNT source: DN42 diff --git a/data/schema/key b/data/schema/key deleted file mode 100644 index e69de29bb..000000000 diff --git a/utils/registry/.schema b/utils/registry/.schema new file mode 100644 index 000000000..2cae33daa --- /dev/null +++ b/utils/registry/.schema @@ -0,0 +1,363 @@ +.BEGIN DN42-MNT +schema: AUT-NUM-SCHEMA +ref: dn42.aut-num +key: aut-num required single primary schema +key: as-name required single +key: descr optional single +key: mnt-by required multiple lookup=dn42.mntner +key: member-of optional multiple lookup=dn42.as-set,dn42.route-set +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: org optional single lookup=dn42.organisation +key: import deprecate multiple +key: export deprecate multiple +key: default deprecate multiple +key: mp-peer deprecate multiple +key: mp-group deprecate multiple +key: mp-import optional multiple +key: mp-export optional multiple +key: mp-default optional multiple +key: geo-loc optional multiple > [lat-c] [long-c] [name] +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: AS-SET-SCHEMA +ref: dn42.as-set +key: as-set required single primary schema +key: descr optional single +key: mnt-by required multiple lookup=dn42.mntner +key: members optional multiple lookup=dn42.aut-num,dn42.as-set +key: mbrs-by-ref optional multiple lookup=dn42.mntner +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: ROUTE6-SCHEMA +ref: dn42.route6 +key: route6 required single primary schema +key: mnt-by required multiple lookup=dn42.mntner +key: origin required multiple lookup=dn42.aut-num +key: member-of optional multiple lookup=dn42.route-set +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: descr optional multiple +key: remarks optional multiple +key: source required single lookup=dn42.registry +key: pingable optional multiple +key: max-length optional single +mnt-by: DN42-MNT +source: DN42 +... +schema: TINC-KEYSET-SCHEMA +ref: dn42.tinc-keyset +key: tinc-keyset required single primary schema +key: descr optional single +key: remarks optional multiple +key: member required multiple lookup=dn42.tinc-key +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: mnt-by required multiple lookup=dn42.mntner +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: ROLE-SCHEMA +ref: dn42.role +key: role required single schema +key: nic-hdl required single primary +key: mnt-by required multiple lookup=dn42.mntner +key: org optional multiple lookup=dn42.organisation +key: admin-c optional multiple lookup=dn42.person +key: tech-c optional multiple lookup=dn42.person +key: abuse-c optional multiple lookup=dn42.person +key: abuse-mailbox optional multiple +key: descr optional single +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: INET6NUM-SCHEMA +ref: dn42.inet6num +key: inet6num required single schema +key: cidr required single primary +key: netname required single +key: nserver optional multiple > [domain-name] +key: country optional multiple +key: descr optional single +key: status optional single > {ALLOCATED|ASSIGNED} {PI|PA|} +key: policy optional single > {open|closed|ask|reserved} +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: zone-c optional multiple lookup=dn42.person,dn42.role +key: ds-rdata optional multiple +key: mnt-by optional multiple lookup=dn42.mntner +key: mnt-lower optional multiple lookup=dn42.mntner +key: mnt-routes optional multiple lookup=dn42.mntner +key: org optional single lookup=dn42.organisation +key: remarks optional multiple +key: source required single lookup=dn42.registry +network-owner: inet6num +network-owner: inetnum +network-owner: route6 +mnt-by: DN42-MNT +source: DN42 +... +schema: MNTNER-SCHEMA +ref: dn42.mntner +key: mntner required single primary schema +key: descr optional single +key: mnt-by required multiple lookup=dn42.mntner +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: auth optional multiple > [method] [value]... +key: org optional multiple lookup=dn42.organisation +key: abuse-mailbox optional single +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: ORGANISATION-SCHEMA +ref: dn42.organisation +key: organisation required single primary schema +key: org-name required single +key: descr optional single +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: abuse-c optional multiple lookup=dn42.person,dn42.role +key: mnt-by required multiple lookup=dn42.mntner +key: mnt-ref optional multiple lookup=dn42.mntner +key: phone optional multiple +key: fax-no optional multiple +key: www optional multiple +key: abuse-mailbox optional multiple +key: e-mail optional multiple +key: geoloc optional multiple +key: language optional multiple +key: remarks optional multiple +key: address optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: TINC-KEY-SCHEMA +ref: dn42.tinc-key +key: tinc-key required single primary schema +key: tinc-host required single +key: tinc-file required single +key: descr optional single +key: remarks optional multiple +key: compression optional single +key: subnet optional multiple +key: tinc-address optional single +key: port optional single +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: mnt-by required multiple lookup=dn42.mntner +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: AS-BLOCK-SCHEMA +ref: dn42.as-block +key: as-block required single primary schema +key: descr optional single +key: policy required single > {open|ask|closed} +key: mnt-by required multiple lookup=dn42.mntner +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: SCHEMA-SCHEMA +ref: dn42.schema +key: schema required single primary schema > [name] +key: ref required single > [schema] +key: key required multiple > [key-name] + {required|optional|recommend|deprecate} + {single|multiple} {primary|} {schema|} + lookup=str '>' [spec]... +key: mnt-by required multiple lookup=dn42.mntner > [mntner] +key: remarks optional multiple > [text]... +key: source required single lookup=dn42.registry +key: network-owner optional multiple > [child-schema] +mnt-by: DN42-MNT +source: DN42 +remarks: # option descriptions + Attribute names must match /[a-zA-Z]([a-zA-Z0-9_\-]*[a-zA-Z0-9])?/. ++ + required + : object required to have at least one + optional + : object not required to have at least one ++ + single + : only one of this type allowed + multiple + : more than one of this type allowed ++ + primary + : use field as lookup key for lookup + * only one allowed per schema + * does not allow newlines ++ + schema + : use field name as the name of the schema + * only one allowed per schema + * does not allow newlines ++ + lookup + : schema match to use for related record ++ + \> option specs + : defines the option specifications for the key. + * must come last in option list ++ + [label] string value. A positional string argument required. + Text inside brackets represent a label for the string and must match the same rules as attribute names. + If follwed by '...' values are gathered as an array. ++ + {enum1|enum2|} enumeration. One option in pipe('|') deliniation is allowed. + If there is a trailing pipe it means the enum is optional. Enum values must match the same rules as attribute names. ++ + 'literal' Literal value. literal text value which must not contain any whitespace or single quotes. +... +schema: ROUTE-SET-SCHEMA +ref: dn42.route-set +key: route-set required single primary schema +key: descr optional single +key: mnt-by required multiple lookup=dn42.mntner +key: members deprecate multiple +key: mp-members optional multiple +key: mbrs-by-ref optional multiple lookup=dn42.mntner +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: REGISTRY-SCHEMA +ref: dn42.registry +key: registry required single primary schema +key: url required multiple +key: descr optional multiple +key: mnt-by required multiple lookup=dn42.mntner +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: KEY-CERT-SCHEMA +ref: dn42.key-cert +key: key-cert required single primary schema +key: method required single > {PGP|X509|MTN} +key: owner required multiple +key: fingerpr required single +key: certif required multiple +key: org optional multiple lookup=dn42.organisation +key: remarks optional multiple +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: mnt-by required multiple lookup=dn42.mntner +key: source required single +mnt-by: DN42-MNT +source: DN42 +... +schema: ROUTE-SCHEMA +ref: dn42.route +key: route required single primary schema +key: mnt-by required multiple lookup=dn42.mntner +key: origin required multiple lookup=dn42.aut-num +key: member-of optional multiple lookup=dn42.route-set +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: descr optional single +key: remarks optional multiple +key: source required single lookup=dn42.registry +key: pingable optional multiple +key: max-length optional single +mnt-by: DN42-MNT +source: DN42 +... +schema: PERSON-SCHEMA +ref: dn42.person +key: person required single schema +key: nic-hdl required single primary +key: mnt-by required multiple lookup=dn42.mntner +key: org optional multiple lookup=dn42.organisation +key: nick optional multiple +key: pgp-fingerprint optional multiple +key: www optional multiple +key: e-mail optional multiple +key: contact optional multiple +key: abuse-mailbox optional multiple +key: phone optional multiple +key: fax-no optional multiple +key: address optional multiple +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +... +schema: INETNUM-SCHEMA +ref: dn42.inetnum +key: inetnum required single schema +key: cidr required single primary +key: netname required single +key: nserver optional multiple > [domain-name] +key: country optional multiple +key: descr optional single +key: status optional single > {ALLOCATED|ASSIGNED} {PI|PA|} +key: policy optional single > {open|closed|ask|reserved} +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: zone-c optional multiple lookup=dn42.person,dn42.role +key: ds-rdata optional multiple +key: mnt-by optional multiple lookup=dn42.mntner +key: mnt-lower optional multiple lookup=dn42.mntner +key: mnt-routes optional multiple lookup=dn42.mntner +key: org optional single lookup=dn42.organisation +key: remarks optional multiple +key: source required single lookup=dn42.registry +network-owner: inetnum +network-owner: route +mnt-by: DN42-MNT +source: DN42 +... +schema: NAMESPACE-SCHEMA +ref: dn42.namespace +key: namespace required single primary schema > [name] +key: ns-schema required single > [schema] +key: ns-owner required single > [schema] +key: default-owner optional single lookup=dn42.mntner > [mntner] +key: network-owner optional multiple > [parent-schema] [child-schema] +key: primary-key optional multiple > [schema] [primary] +mnt-by: DN42-MNT +source: DN42 +... +schema: DNS-SCHEMA +ref: dn42.domain +key: domain required single primary schema +key: nserver required multiple > [domain-name] [ip-addr] +key: descr optional single +key: mnt-by required multiple lookup=dn42.mntner +key: admin-c optional multiple lookup=dn42.person,dn42.role +key: tech-c optional multiple lookup=dn42.person,dn42.role +key: org optional multiple lookup=dn42.organisation +key: country optional single +key: ds-rdata optional multiple +key: remarks optional multiple +key: source required single lookup=dn42.registry +mnt-by: DN42-MNT +source: DN42 +.END diff --git a/utils/registry/build-index.py b/utils/registry/build-index.py index b32034972..96ac0fc44 100755 --- a/utils/registry/build-index.py +++ b/utils/registry/build-index.py @@ -4,13 +4,12 @@ import os import sys -from ipaddress import ip_network, IPv6Network -from dataclasses import dataclass -from typing import TypeVar, Dict, Generator, List, Tuple +from typing import Dict, Generator, List from dom.filedom import FileDOM, read_file from dom.schema import SchemaDOM - +from dom.nettree import NetTree, NetRecord +from dom.transact import TransactDOM def index_files(path: str) -> Generator[FileDOM, None, None]: """generate list of dom files""" @@ -26,87 +25,6 @@ def index_files(path: str) -> Generator[FileDOM, None, None]: yield dom -NET = IPv6Network -NET_LIST = TypeVar('NET_LIST', int, List[NET]) -NET_TREE = Dict[NET, NET_LIST] -V6_NET = ip_network("::/0") -V4_NET = ip_network("::0.0.0.0/96") - - -@dataclass -class NetRecord: - "Network Record" - network: NET - mnters: List[str] - policy: str - status: str - - @property - def object_type(self) -> str: - """object type""" - return "inetnum" if V4_NET.supernet_of(self.network) \ - else "inet6num" - - @property - def object_name(self) -> str: - """object name""" - return self.network.with_prefixlen.replace("/", "_") - - -def in_net(i: NET, nets: List[NET]) -> Tuple[bool, NET]: - "find a network within a list of networks" - found = False - net = None - for n in nets: - if n.supernet_of(i): - found = True - net = n - break - - return found, net - - -def find_tree(ip: NET, nets: NET_TREE): - """Find net in tree""" - net = V6_NET - current = nets[net] - while True: - found, net = in_net(ip, current[1]) - if not found: - return True, current[0] + 1 - - if ip.network == net.network: - return True, current[0] + 2 - - current = nets[net] - continue - - -def make_tree(nets: List[NET]) -> Dict[NET, NET_LIST]: - """build a network tree index""" - root = V6_NET - tree = {root: [-1, []]} - for i in sorted( - sorted(nets, key=lambda x: x.exploded), - key=lambda x: x.prefixlen): - current = tree[root] - - while True: - found, n = in_net(i, current[1]) - - if found: - current = tree[n] - continue - - if current[0] >= 0: - current[1].append(i) - - tree[i] = [current[0] + 1, []] - break - - return tree - - def run(path: str = "."): """run main script""" if not os.path.isdir(os.path.join(path, "schema")): @@ -155,7 +73,7 @@ def run(path: str = "."): file=sys.stderr) print("Writing .index", file=sys.stderr) - print("Writing .linkindex", file=sys.stderr) + print("Writing .links", file=sys.stderr) with open(".index", 'w') as out: with open(".links", 'w') as link_out: for dom in files: @@ -169,32 +87,29 @@ def run(path: str = "."): dom.get(s.primary), dom.src, ",".join(dom.mntner), - sep="\t", + sep="|", file=out) for (link, refs) in s.links.items(): d = dom.get(link) + refs_join = ','.join(refs) if d is not None: print( - f"{dom.name}\t{link}\t{d}\t{','.join(refs)}", + f"{dom.rel}|{dom.name}|{link}|{d}|{refs_join}", file=link_out) - print("Generate .netindex", file=sys.stderr) - tree = make_tree({n.network for n in nets}) + print("Generate .nettree", file=sys.stderr) + tree = NetTree(nets) - netindex = [] - for net in nets: - v = tree[net.network] - netindex.append((v[0], - net.network.network_address.exploded, - net.network.broadcast_address.exploded, - net.network.prefixlen, - net.policy, net.status, ",".join(net.mnters))) + print("Writing .nettree", file=sys.stderr) + tree.write_csv(".nettree") - print("Writing .netindex", file=sys.stderr) - with open(".netindex", "w") as out: - for row in sorted(netindex, key=lambda x: x[0]): - print("\t".join([str(i) for i in row]), file=out) + print("Writing .schema", file=sys.stderr) + s = TransactDOM() + s.mntner = "DN42-MNT" + s.files = schemas.values() + with open(".schema", "w") as out: + print(s, file=out) print("done.", file=sys.stderr) diff --git a/utils/registry/dom/nettree.py b/utils/registry/dom/nettree.py new file mode 100644 index 000000000..c839c78d8 --- /dev/null +++ b/utils/registry/dom/nettree.py @@ -0,0 +1,136 @@ +"Net Tree" + +from ipaddress import ip_network, IPv6Network +from dataclasses import dataclass +from typing import Dict, List, Tuple, Optional + +NET = IPv6Network +V6_NET = ip_network("::/0") +V4_NET = ip_network("::ffff:0.0.0.0/96") + + +@dataclass +class NetRecord: + "Network Record" + network: NET + mnters: List[str] + policy: str + status: str + + @property + def object_type(self) -> str: + """object type""" + return "inetnum" if V4_NET.supernet_of(self.network) \ + else "inet6num" + + @property + def object_name(self) -> str: + """object name""" + if V4_NET.supernet_of(self.network): + n = self.network.network_address.exploded.replace(":", "")[-8:] + return ip_network(( + bytes.fromhex(n), + self.network.prefixlen - 96, + )).with_prefixlen.replace("/", "_") + + return self.network.with_prefixlen.replace("/", "_") + + +@dataclass +class NetList: + "Network List" + index: int + parent: Optional[int] + level: int + net: Optional[NetRecord] + nets: List[NET] + + def in_net(self, i: NET) -> Tuple[bool, NET]: + "find a network within a list of networks" + found = False + net = None + for n in self.nets: + if n.supernet_of(i): + found = True + net = n + break + + return found, net + + +class NetTree: + "Network Tree" + def __init__(self, nets: Optional[List[NET]] = None): + self.tree = {} # type: Dict[NET, NetList] + if nets is not None: + self.make_tree(nets) + + def __getitem__(self, key): + return self.tree[key] + + def find_tree(self, ip: NET) -> Tuple[bool, int]: + """Find net in tree""" + net = V6_NET + current = self.tree[net] + + while True: + found, net = current.in_net(ip) + if not found: + return True, current.level + 1 + + if ip == net: + return True, current.level + 2 + + current = self.tree[net] + continue + + return False, 0 + + def make_tree(self, nets: List[NetRecord]): + """build a network tree index""" + root = V6_NET + self.tree = {root: NetList(0, None, -1, None, [])} + for index, net in enumerate(sorted( + sorted(nets, key=lambda x: x.network), + key=lambda x: x.network.prefixlen)): + + current = self.tree[root] + + while True: + found, n = current.in_net(net.network) + + if found: + current = self.tree[n] + continue + + if current.level >= 0: + current.nets.append(net.network) + + self.tree[net.network] = NetList( + index, current.index, current.level + 1, net, []) + break + + def write_csv(self, fn: str = ".netindex"): + "write tree to csv" + with open(fn, "w") as f: + f.writelines({line+"\n" for line in self._lines()}) + + def __str__(self) -> str: + return "\n".join(self._lines()) + + def _lines(self) -> List[str]: + for v in self.tree.values(): + yield ( + "|".join([str(i) for i in ( + v.index, + v.parent, + v.level, + v.net.network.network_address.exploded, + v.net.network.prefixlen, + v.net.object_type, + v.net.object_name, + v.net.policy, + v.net.status, + ",".join(v.net.mnters), + )]) + ) diff --git a/utils/registry/dom/schema.py b/utils/registry/dom/schema.py index 6902931dc..7168bad9b 100644 --- a/utils/registry/dom/schema.py +++ b/utils/registry/dom/schema.py @@ -70,6 +70,7 @@ class SchemaDOM: self._schema = {} # type: Dict[str, Set[str]] self._spec = {} # type: Dict[str, str] self._links = {} # type: Dict[str, List[str]] + self._dom = dom if dom is not None: self.parse(dom) @@ -82,6 +83,7 @@ class SchemaDOM: def parse(self, f: FileDOM): """Parse a FileDOM into a SchemaDOM""" self.src = self.src if f.src is None else f.src + self._dom = f schema = {} for row in f.dom: @@ -225,6 +227,9 @@ class SchemaDOM: f"in {refs} but does not exist.") return state + def __str__(self) -> str: + return self._dom.__str__() + def read_file(src: str) -> SchemaDOM: """Parses SchemaDOM from file""" diff --git a/utils/registry/dom/test_nettree.py b/utils/registry/dom/test_nettree.py new file mode 100644 index 000000000..39d95257e --- /dev/null +++ b/utils/registry/dom/test_nettree.py @@ -0,0 +1,62 @@ +"Testing NetTree" +import unittest +from ipaddress import ip_network + +from .nettree import NetTree, NetRecord + +records = [ + NetRecord( + ip_network("::/0"), + ["DN42-MNT"], + "closed", + "ALLOCATED"), + NetRecord( + ip_network("::ffff:0.0.0.0/96"), + ["DN42-MNT"], + "closed", + "ALLOCATED"), + NetRecord( + ip_network("::ffff:172.21.64.0/125"), + ["XUU-MNT"], + "closed", + "ALLOCATED"), + NetRecord( + ip_network("fdea:a15a:77b9::/48"), + ["XUU-MNT"], + "closed", + "ALLOCATED"), +] + +text = [ + "0|0|0|0000:0000:0000:0000:0000:0000:0000:0000|0|inet6num|::_0|closed|ALLOCATED|DN42-MNT", # noqa: E501 + "1|0|1|fdea:a15a:77b9:0000:0000:0000:0000:0000|48|inet6num|fdea:a15a:77b9::_48|closed|ALLOCATED|XUU-MNT", # noqa: E501 + "2|0|1|0000:0000:0000:0000:0000:ffff:0000:0000|96|inetnum|0.0.0.0_0|closed|ALLOCATED|DN42-MNT", # noqa: E501 + "3|2|2|0000:0000:0000:0000:0000:ffff:ac15:4000|125|inetnum|172.21.64.0_29|closed|ALLOCATED|XUU-MNT" # noqa: E501 +] + + +class TestNetTree(unittest.TestCase): + "testing NetTree" + def test_nettree(self): + "test NetTree" + tree = NetTree(records) + for (left, right) in zip(str(tree).splitlines(), text): + self.assertEqual(left, right) + + def test_find(self): + "test NetTree" + tree = NetTree(records) + tt = [ + ("fdea:a15a:77b9:ffff::/64", (True, 2)), + ("fdea:a15a:77ba:ffff::/64", (True, 1)), + ("::ffff:172.21.64.0/126", (True, 3)), + ("::ffff:172.21.64.4/126", (True, 3)), + ("::ffff:172.21.64.8/126", (True, 2)), + + ] + + for (net, expect) in tt: + self.assertEqual( + tree.find_tree(ip_network(net)), + expect, + msg="network "+net) diff --git a/utils/registry/dom/transact.py b/utils/registry/dom/transact.py index 06973f2f5..eab77a03a 100644 --- a/utils/registry/dom/transact.py +++ b/utils/registry/dom/transact.py @@ -53,3 +53,10 @@ class TransactDOM(): continue buffer.append(line) + + def __str__(self) -> str: + s = f".BEGIN {self.mntner}\n" + s += "\n".join({f"DELETE {i}" for i in self.delete}) + s += "...\n".join({str(record) for record in self.files}) + s += ".END" + return s