Skip to content

Commit

Permalink
ipdb: provide IPDB API to cover kuryr needs
Browse files Browse the repository at this point in the history
+ tests
  • Loading branch information
svinota committed Feb 2, 2025
1 parent 0006ffe commit 505f72f
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pyroute2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from pyroute2.conntrack import Conntrack, ConntrackEntry
from pyroute2.devlink import DL
from pyroute2.ethtool.ethtool import Ethtool
from pyroute2.ipdb import IPDB, CommitException, CreateException
from pyroute2.iproute import (
AsyncIPRoute,
ChaoticIPRoute,
Expand Down Expand Up @@ -52,7 +53,6 @@
from pyroute2.netlink.rtnl.iprsocket import AsyncIPRSocket, IPRSocket
from pyroute2.netlink.taskstats import TaskStats
from pyroute2.netlink.uevent import UeventSocket
from pyroute2.noipdb import IPDB, CommitException, CreateException
from pyroute2.nslink.nspopen import NSPopen
from pyroute2.plan9.client import Plan9ClientSocket
from pyroute2.plan9.server import Plan9ServerSocket
Expand Down
81 changes: 69 additions & 12 deletions pyroute2/noipdb.py → pyroute2/ipdb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import errno
import logging

from pyroute2.ndb.main import NDB
from pyroute2.netlink.exceptions import NetlinkError

log = logging.getLogger(__name__)

Expand All @@ -14,8 +16,12 @@ class CommitException(Exception):


class ObjectProxy(dict):
def __init__(self, obj):

_translate_keys = {}

def __init__(self, obj, ready=True):
self._obj = obj
self._ready = ready

def __getattribute__(self, key):
if key[:4] == 'set_':
Expand All @@ -31,15 +37,21 @@ def set_value(value):
return super(ObjectProxy, self).__getattribute__(key)

def __setattr__(self, key, value):
if key == '_obj':
if key in ('_obj', '_ready', '_translate_keys'):
super(ObjectProxy, self).__setattr__(key, value)
else:
super(ObjectProxy, self).__getattribute__('_obj')[key] = value

def __getitem__(self, key):
tk = super().__getattribute__('_translate_keys')
if isinstance(key, str) and key in tk:
return super().__getattribute__('_obj')[tk[key](self)]
return super(ObjectProxy, self).__getattribute__('_obj')[key]

def __setitem__(self, key, value):
tk = super().__getattribute__('_translate_keys')
if isinstance(key, str) and key in tk:
super().__getattribute__('_obj')[tk[key](self)] = value
super(ObjectProxy, self).__getattribute__('_obj')[key] = value

def __enter__(self):
Expand All @@ -58,6 +70,9 @@ def __contains__(self, key):
def get_ndb_object(self):
return self._obj

def get(self, key, *argv):
return self._obj.get(key, *argv)

def keys(self):
return self._obj.keys()

Expand All @@ -76,12 +91,25 @@ def _mode(self):


class Interface(ObjectProxy):
def add_ip(self, *argv, **kwarg):
self._obj.add_ip(*argv, **kwarg)

_translate_keys = {
'mode': lambda x: f'{x["kind"]}_mode'
}

def add_ip(self, address=None, prefixlen=None, **kwarg):
if address is not None:
kwarg['address'] = address
if prefixlen is not None:
kwarg['prefixlen'] = prefixlen
self._obj.add_ip(spec=kwarg)
return self

def del_ip(self, *argv, **kwarg):
self._obj.del_ip(*argv, **kwarg)
def del_ip(self, address=None, prefixlen=None, **kwarg):
if address is not None:
kwarg['address'] = address
if prefixlen is not None:
kwarg['prefixlen'] = prefixlen
self._obj.del_ip(spec=kwarg)
return self

def add_port(self, *argv, **kwarg):
Expand All @@ -93,7 +121,14 @@ def del_port(self, *argv, **kwarg):
return self

def commit(self, *argv, **kwarg):
self._obj.commit(*argv, **kwarg)
try:
self._obj.commit(*argv, **kwarg)
except Exception as e:
if self._ready:
raise CommitException(e)
else:
raise CreateException(e)
self._ready = True
return self

def up(self):
Expand All @@ -114,7 +149,9 @@ def if_master(self):

@property
def ipaddr(self):
return tuple(self._obj.ipaddr.dump().select('address', 'prefixlen'))
report = self._obj.ipaddr.dump()
report.select_fields('address', 'prefixlen')
return tuple(report)


class Interfaces(ObjectProxy):
Expand All @@ -123,8 +160,6 @@ class Interfaces(ObjectProxy):
differently in IPDB and NDB. IPDB saves the failed object in the database,
while the NDB database contains only the system reflection, and the failed
object may stay only being referenced by a variable.
`KeyError: 'object exists'` vs. `CreateException`
'''

def __getitem__(self, key):
Expand All @@ -136,9 +171,24 @@ def __iter__(self):
def add(self, *argv, **kwarg):
return self.create(*argv, **kwarg)

def get(self, spec, *argv):
try:
return self[spec]
except KeyError:
if len[argv] > 0:
return argv[0]
raise

def create(self, *argv, **kwarg):
log.warning(self.text_create)
return Interface(self._obj.create(*argv, **kwarg))
key = dict(
filter(lambda x: x[0] in ('ifname', 'index'), kwarg.items())
)
if key in self:
if kwarg.get('reuse'):
return self[key]
raise CreateException(NetlinkError(errno.EEXIST, 'object exists'))
return Interface(self._obj.create(*argv, **kwarg), ready=False)

def keys(self):
ret = []
Expand Down Expand Up @@ -171,6 +221,7 @@ class IPDB(object):
'''

def __init__(self, *argv, **kwarg):
sources = kwarg.pop('sources', [{'target': 'localhost'}])
if argv or kwarg:
log.warning(
'%s does not support IPDB parameters, ignoring',
Expand All @@ -183,9 +234,15 @@ def __init__(self, *argv, **kwarg):
self.__class__.__name__,
)

self._ndb = NDB()
self._ndb = NDB(sources=sources)
self.interfaces = Interfaces(self._ndb.interfaces)

def __enter__(self):
return self

def __exit__(self, *_):
self.release()

@property
def nl(self):
log.warning(self.text_nl)
Expand Down
5 changes: 5 additions & 0 deletions tests/test_integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ def link(request, tmpdir, nsname):
ipr.link('del', index=link['index'])
except:
pass


@pytest.fixture
def ifname(link):
return link.get('ifname')
89 changes: 85 additions & 4 deletions tests/test_integration/test_kuryr.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,88 @@
import pytest
from net_tools import interface_exists

import pyroute2
from pyroute2 import IPDB
from pyroute2.common import uifname
from pyroute2.netlink.rtnl.ifinfmsg import ifinfmsg

# from pyroute2.common import uifname
TADDR = '00:11:22:33:44:55'
KIND = 'ipvlan'
IPVLAN_MODE_L2 = ifinfmsg.ifinfo.data_map['ipvlan'].modes['IPVLAN_MODE_L2']


@pytest.fixture
def ipdb(link):
with IPDB(sources=[{'target': 'localhost', 'netns': link.netns}]) as ip:
yield ip


@pytest.mark.parametrize(
'exc',
(
pyroute2.NetlinkError,
pyroute2.CreateException,
pyroute2.CommitException,
),
)
def test_exception_types(exc):
assert issubclass(exc, Exception)


def test_ipdb_create_exception(link, ipdb):
with pytest.raises(pyroute2.CreateException):
ipdb.create(ifname=link.get('ifname'), kind='dummy').commit()


def test_ipdb_create_reuse(link, ipdb):
ipdb.create(ifname=link.get('ifname'), kind='dummy', reuse=True).commit()


@pytest.mark.parametrize(
'method,argv,check',
(
('set_mtu', [1000], lambda x: x['mtu'] == 1000),
('set_address', [TADDR], lambda x: x['address'] == TADDR),
('add_ip', ['10.1.2.3', 24], lambda x: '10.1.2.3/24' in x.ipaddr),
('up', [], lambda x: x['flags'] & 1),
),
)
def test_ipdb_iface_methods(link, ipdb, method, argv, check):
iface = ipdb.interfaces[link.get('ifname')]
with iface:
getattr(iface, method)(*argv)
assert check(iface)


def test_utils_remove(link, ifname, ipdb):
index = ipdb.interfaces.get(ifname, {}).get('index', None)
assert isinstance(index, int)
with ipdb.interfaces[ifname] as iface:
iface.remove()
assert ifname not in ipdb.interfaces
assert not interface_exists(ifname, link.netns, timeout=0.1)


def test_get_iface(link, ifname, ipdb):
with ipdb.interfaces[ifname] as link:
link.set_address(TADDR)
target = None
for name, data in ipdb.interfaces.items():
if data['address'] == TADDR:
target = data['ifname']
assert target == ifname


def test_exceptions():
assert issubclass(pyroute2.NetlinkError, Exception)
assert issubclass(pyroute2.CreateException, Exception)
assert issubclass(pyroute2.CommitException, Exception)
def test_create_ipvlan(link, ifname, ipdb):
ipvlname = uifname()
with ipdb.create(
ifname=ipvlname,
kind=KIND,
link=ipdb.interfaces[ifname],
ipvlan_mode=IPVLAN_MODE_L2,
) as iface:
assert iface['mode'] == IPVLAN_MODE_L2
assert iface['ifname'] == ipvlname
assert iface['link'] == link.get('index')
assert iface['kind'] == KIND

0 comments on commit 505f72f

Please sign in to comment.