Skip to content

Commit

Permalink
netplan: export an IP addresses iterator API
Browse files Browse the repository at this point in the history
It enabled us to retrieve the list of IPs, V4 and V6) from a given
netdef via Python bindings.

The same iterator will consume both ip4_addresses and ip6_addresses in
this order.
  • Loading branch information
daniloegea committed Jul 18, 2023
1 parent b36e41d commit a52fc7e
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
46 changes: 46 additions & 0 deletions netplan/libnetplan.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ def __init__(self, np_state, ptr):
# the GC invoking netplan_state_free
self._parent = np_state

@property
def addresses(self):
return _NetdefAddressesIterator(self._ptr)

@property
def has_match(self):
return bool(lib.netplan_netdef_has_match(self._ptr))
Expand Down Expand Up @@ -668,6 +672,48 @@ def __next__(self):
return NetDefinition(self.np_state, next_value)


class _NetdefAddressesIterator:
_abi_loaded = False

@classmethod
def _load_abi(cls):
if cls._abi_loaded:
return

if not hasattr(lib, '_netplan_new_netdef_addresses_iter'): # pragma: nocover (hard to unit-test against the WRONG lib)
raise NetplanException('''
The current version of libnetplan does not allow iterating by IP addresses.
Please ensure that both the netplan CLI package and its library are up to date.
''')
lib._netplan_new_netdef_addresses_iter.argtypes = [_NetplanNetDefinitionP]
lib._netplan_new_netdef_addresses_iter.restype = c_void_p

lib._netplan_netdef_addresses_iter_next.argtypes = [c_void_p]
lib._netplan_netdef_addresses_iter_next.restype = c_char_p

lib._netplan_netdef_addresses_free_iter.argtypes = [c_void_p]
lib._netplan_netdef_addresses_free_iter.restype = None

cls._abi_loaded = True

def __init__(self, netdef):
self._load_abi()
self.netdef = netdef
self.iterator = lib._netplan_new_netdef_addresses_iter(netdef)

def __del__(self):
lib._netplan_netdef_addresses_free_iter(self.iterator)

def __iter__(self):
return self

def __next__(self):
next_value = lib._netplan_netdef_addresses_iter_next(self.iterator)
if not next_value:
raise StopIteration
return next_value.decode('utf-8')


lib.netplan_util_create_yaml_patch.argtypes = [c_char_p, c_char_p, c_int, _NetplanErrorPP]
lib.netplan_util_create_yaml_patch.restype = c_int

Expand Down
35 changes: 35 additions & 0 deletions src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,41 @@ get_unspecified_address(int ip_family)
return (ip_family == AF_INET) ? "0.0.0.0" : "::";
}

struct netdef_addresses_iter {
guint ip4_index;
guint ip6_index;
NetplanNetDefinition* netdef;
};

NETPLAN_INTERNAL struct netdef_addresses_iter*
_netplan_new_netdef_addresses_iter(NetplanNetDefinition* netdef)
{
struct netdef_addresses_iter* it = g_malloc0(sizeof(struct netdef_addresses_iter));
it->ip4_index = 0;
it->ip6_index = 0;
it->netdef = netdef;

return it;
}

NETPLAN_INTERNAL char*
_netplan_netdef_addresses_iter_next(struct netdef_addresses_iter* it)
{
if (it->netdef->ip4_addresses && it->ip4_index < it->netdef->ip4_addresses->len)
return g_array_index(it->netdef->ip4_addresses, char*, it->ip4_index++);

if (it->netdef->ip6_addresses && it->ip6_index < it->netdef->ip6_addresses->len)
return g_array_index(it->netdef->ip6_addresses, char*, it->ip6_index++);

return NULL;
}

NETPLAN_INTERNAL void
_netplan_netdef_addresses_free_iter(struct netdef_addresses_iter* it)
{
g_free(it);
}

struct netdef_pertype_iter {
NetplanDefType type;
GHashTableIter iter;
Expand Down
25 changes: 25 additions & 0 deletions tests/test_libnetplan.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,31 @@ def test_iter_ethernets(self):
self.assertSetEqual(set(["eth0", "eth1"]), set(d.id for d in libnetplan._NetdefIterator(state, "ethernets")))


class TestNetdefAddressesIterator(TestBase):
def test_with_empty_ip_addresses(self):
state = state_from_yaml(self.confdir, '''network:
ethernets:
eth0:
dhcp4: true''')

netdef = next(libnetplan._NetdefIterator(state, "ethernets"))
self.assertSetEqual(set(), set(ip for ip in netdef.addresses))

def test_iter_ethernets(self):
state = state_from_yaml(self.confdir, '''network:
ethernets:
eth0:
addresses:
- 192.168.0.1/24
- 172.16.0.1/24
- 1234:4321:abcd::cdef/96
- abcd::1234/64''')

expected = set(["1234:4321:abcd::cdef/96", "abcd::1234/64", "192.168.0.1/24", "172.16.0.1/24"])
netdef = next(libnetplan._NetdefIterator(state, "ethernets"))
self.assertSetEqual(expected, set(ip for ip in netdef.addresses))


class TestParser(TestBase):
def test_load_yaml_from_fd_empty(self):
parser = libnetplan.Parser()
Expand Down

0 comments on commit a52fc7e

Please sign in to comment.