Commit 85cbd4a1 authored by Peter V. Saveliev's avatar Peter V. Saveliev

general: public API refactored

use static re-exports instead of dynamic loading — support
IDEs like PyDev
parent c69694cf
......@@ -8,15 +8,35 @@
import sys
import struct
import logging
from abc import ABCMeta
from pyroute2.ipdb.exceptions import \
DeprecationException, \
CommitException, \
CreateException, \
PartialCommitException
from pyroute2.netlink.exceptions import \
NetlinkError, \
NetlinkDecodeError
from pyroute2.ipdb.exceptions import (DeprecationException,
CommitException,
CreateException,
PartialCommitException)
from pyroute2.netlink.exceptions import (NetlinkError,
NetlinkDecodeError)
from pyroute2.netlink.rtnl.req import (IPRouteRequest,
IPLinkRequest)
from pyroute2.iproute import (IPRoute,
IPBatch,
RawIPRoute)
from pyroute2.ipset import IPSet
from pyroute2.ipdb.main import IPDB
from pyroute2.iwutil import IW
from pyroute2.devlink import DL
from pyroute2.netns.nslink import NetNS
from pyroute2.netns.process.proxy import NSPopen
from pyroute2.netlink.rtnl.iprsocket import IPRSocket
from pyroute2.netlink.taskstats import TaskStats
from pyroute2.netlink.nl80211 import NL80211
from pyroute2.netlink.devlink import DevlinkSocket
from pyroute2.netlink.event.acpi_event import AcpiEventSocket
from pyroute2.netlink.event.dquot import DQuotSocket
from pyroute2.netlink.ipq import IPQSocket
from pyroute2.netlink.diag import DiagSocket
from pyroute2.netlink.generic import GenericNetlinkSocket
from pyroute2.netlink.nfnetlink.nftables import NFTSocket
from pyroute2.cli import Console
log = logging.getLogger(__name__)
# Add a NullHandler to the library's top-level logger to avoid complaints
......@@ -48,105 +68,31 @@ exceptions = [NetlinkError,
CreateException,
PartialCommitException]
__all__ = []
_modules = {'IPRoute': 'pyroute2.iproute',
'IPBatch': 'pyroute2.iproute',
'RawIPRoute': 'pyroute2.iproute',
'IPSet': 'pyroute2.ipset',
'IPDB': 'pyroute2.ipdb.main',
'IW': 'pyroute2.iwutil',
'DL': 'pyroute2.devlink',
'NetNS': 'pyroute2.netns.nslink',
'NSPopen': 'pyroute2.netns.process.proxy',
'IPRSocket': 'pyroute2.netlink.rtnl.iprsocket',
'IPRouteRequest': 'pyroute2.netlink.rtnl.req',
'IPLinkRequest': 'pyroute2.netlink.rtnl.req',
'TaskStats': 'pyroute2.netlink.taskstats',
'NL80211': 'pyroute2.netlink.nl80211',
'DevlinkSocket': 'pyroute2.netlink.devlink',
'AcpiEventSocket': 'pyroute2.netlink.event.acpi_event',
'DQuotSocket': 'pyroute2.netlink.event.dquot',
'IPQSocket': 'pyroute2.netlink.ipq',
'DiagSocket': 'pyroute2.netlink.diag',
'GenericNetlinkSocket': 'pyroute2.netlink.generic',
'NFTSocket': 'pyroute2.netlink.nfnetlink.nftables',
'Console': 'pyroute2.cli'}
_DISCLAIMER = '''\n\nNotice:\n
This is a proxy class. To read full docs, please run
the `help()` method on the instance instead.
Usage of the proxy allows to postpone the module load,
thus providing a safe way to substitute base classes,
if it is required. More details see in the `pyroute2.config`
module.
\n'''
def _bake(name):
class Doc(str):
def __init__(self, registry, *argv, **kwarg):
self.registry = registry
super(Doc, self).__init__(*argv, **kwarg)
def __repr__(self):
return repr(self.registry['doc'])
# reexport classes
classes = [IPRouteRequest,
IPLinkRequest,
IPRoute,
IPBatch,
RawIPRoute,
IPSet,
IPDB,
IW,
DL,
NetNS,
NSPopen,
IPRSocket,
TaskStats,
NL80211,
DevlinkSocket,
AcpiEventSocket,
DQuotSocket,
IPQSocket,
DiagSocket,
GenericNetlinkSocket,
NFTSocket,
Console]
def __str__(self):
return str(self.registry['doc'])
def expandtabs(self, ts=4):
return self.registry['doc'].expandtabs(ts)
class Registry(object):
def __init__(self):
self.target = {}
def __getitem__(self, key):
if not self.target:
module = __import__(_modules[name],
globals(),
locals(),
[name], 0)
self.target['class'] = getattr(module, name)
self.target['doc'] = self.target['class'].__doc__
try:
self.target['doc'] += _DISCLAIMER
except TypeError:
# ignore cases, when __doc__ is not a string, e.g. None
pass
return self.target[key]
@classmethod
def __hook__(cls, C):
if hasattr(C, 'registry'):
try:
return issubclass(C.registry['class'], cls.registry['class'])
except Exception:
pass
return issubclass(C, cls.registry['class'])
def __new__(cls, *argv, **kwarg):
cls.register(cls.registry['class'])
return cls.registry['class'](*argv, **kwarg)
registry = Registry()
doc = Doc(registry)
proxy = ABCMeta('proxy', (object, ), {'__new__': __new__,
'__doc__': doc,
'__subclasshook__': __hook__,
'registry': registry})
return proxy
for name in _modules:
f = _bake(name)
globals()[name] = f
__all__.append(name)
__all__ = []
class __common(object):
......@@ -159,3 +105,4 @@ class __common(object):
globals()['ipdb'].common = __common()
__all__.extend([x.__name__ for x in exceptions])
__all__.extend([x.__name__ for x in classes])
import socket
import platform
import multiprocessing
from pyroute2 import common
from distutils.version import LooseVersion
TransactionalBase = common.Dotkeys
SocketBase = socket.socket
MpPipe = multiprocessing.Pipe
MpQueue = multiprocessing.Queue
......
......@@ -6,7 +6,7 @@ from pyroute2 import config
from pyroute2.common import basestring
from pyroute2.common import dqn2int
from pyroute2.common import View
from pyroute2.config import TransactionalBase
from pyroute2.common import Dotkeys
from pyroute2.netlink import rtnl
from pyroute2.netlink import NLM_F_ACK
from pyroute2.netlink import NLM_F_REQUEST
......@@ -1100,7 +1100,7 @@ class Interface(Transactional):
return self
class InterfacesDict(TransactionalBase):
class InterfacesDict(Dotkeys):
def __init__(self, ipdb):
self.ipdb = ipdb
......
......@@ -2,8 +2,8 @@
'''
import logging
import threading
from pyroute2.config import TransactionalBase
from pyroute2.common import uuid32
from pyroute2.common import Dotkeys
from pyroute2.ipdb.linkedset import LinkedSet
from pyroute2.ipdb.exceptions import CommitException
......@@ -81,7 +81,7 @@ def with_transaction(f):
return update(decorated)
class Transactional(TransactionalBase):
class Transactional(Dotkeys):
'''
Utility class that implements common transactional logic.
'''
......@@ -124,7 +124,7 @@ class Transactional(TransactionalBase):
self._linked_sets = self._linked_sets or set()
#
for i in self._fields:
TransactionalBase.__setitem__(self, i, None)
Dotkeys.__setitem__(self, i, None)
@property
def ro(self):
......@@ -444,7 +444,7 @@ class Transactional(TransactionalBase):
transaction._targets[key] = threading.Event()
else:
# set the item
TransactionalBase.__setitem__(self, key, value)
Dotkeys.__setitem__(self, key, value)
# update on local targets
with self._write_lock:
......@@ -471,7 +471,7 @@ class Transactional(TransactionalBase):
if key in transaction:
del transaction[key]
else:
TransactionalBase.__delitem__(self, key)
Dotkeys.__delitem__(self, key)
def option(self, key, value):
self[key] = value
......
......@@ -229,7 +229,7 @@ from pyroute2.netlink.rtnl.ifinfmsg import ifinfmsg
from pyroute2.netlink.rtnl.ifaddrmsg import ifaddrmsg
from pyroute2.netlink.rtnl.iprsocket import IPRSocket
from pyroute2.netlink.rtnl.iprsocket import IPBatchSocket
from pyroute2.netlink.rtnl.iprsocket import RawIPRSocket
from pyroute2.netlink.rtnl.riprsocket import RawIPRSocket
from pyroute2.common import AF_MPLS
from pyroute2.common import basestring
......
......@@ -97,7 +97,6 @@ from socket import SO_RCVBUF
from socket import SO_SNDBUF
from pyroute2 import config
from pyroute2.config import SocketBase
from pyroute2.common import AddrPool
from pyroute2.common import DEFAULT_RCVBUF
from pyroute2.netlink import nlmsg
......@@ -869,10 +868,10 @@ class NetlinkSocket(NetlinkMixin):
with self.lock:
if self._sock is not None:
self._sock.close()
self._sock = SocketBase(AF_NETLINK,
SOCK_DGRAM,
self.family,
self._fileno)
self._sock = config.SocketBase(AF_NETLINK,
SOCK_DGRAM,
self.family,
self._fileno)
for name in ('getsockname', 'getsockopt', 'makefile',
'setsockopt', 'setblocking', 'settimeout',
'gettimeout', 'shutdown', 'recvfrom',
......
import importlib
import os
import pkgutil
import sys
import json
import errno
import select
import struct
import threading
import subprocess
from fcntl import ioctl
import pkgutil
import importlib
from socket import AF_INET
from socket import AF_INET6
from socket import AF_BRIDGE
from pyroute2 import RawIPRoute
from pyroute2 import config
from pyroute2.common import map_namespace
from pyroute2.common import map_enoent
from pyroute2.common import basestring
from pyroute2.netlink import nla
from pyroute2.netlink import nlmsg
from pyroute2.netlink import nlmsg_atoms
from pyroute2.netlink.rtnl import RTM_VALUES
from pyroute2.netlink.rtnl.iw_event import iw_event
from pyroute2.netlink.exceptions import NetlinkError
from pyroute2.netlink.rtnl.ifinfmsg import plugins
config.data_plugins_pkgs.append(plugins)
......@@ -34,25 +24,6 @@ RTM_NEWLINK = 16
RTM_DELLINK = 17
#
_BONDING_MASTERS = '/sys/class/net/bonding_masters'
_BONDING_SLAVES = '/sys/class/net/%s/bonding/slaves'
_BRIDGE_MASTER = '/sys/class/net/%s/brport/bridge/ifindex'
_BONDING_MASTER = '/sys/class/net/%s/master/ifindex'
IFNAMSIZ = 16
TUNDEV = '/dev/net/tun'
if config.machine in ('i386', 'i686', 'x86_64', 'armv6l', 'armv7l'):
TUNSETIFF = 0x400454ca
TUNSETPERSIST = 0x400454cb
TUNSETOWNER = 0x400454cc
TUNSETGROUP = 0x400454ce
elif config.machine in ('ppc64', 'mips'):
TUNSETIFF = 0x800454ca
TUNSETPERSIST = 0x800454cb
TUNSETOWNER = 0x800454cc
TUNSETGROUP = 0x800454ce
else:
TUNSETIFF = None
##
#
......@@ -845,201 +816,3 @@ class ifinfmsg(ifinfbase, nlmsg):
class ifinfveth(ifinfbase, nla):
pass
def proxy_setlink(msg, nl):
def get_interface(index):
msg = nl.get_links(index)[0]
try:
kind = msg.get_attr('IFLA_LINKINFO').get_attr('IFLA_INFO_KIND')
except AttributeError:
kind = 'unknown'
return {'ifname': msg.get_attr('IFLA_IFNAME'),
'master': msg.get_attr('IFLA_MASTER'),
'kind': kind}
forward = True
# is it a port setup?
master = msg.get_attr('IFLA_MASTER')
if master is not None:
if master == 0:
# port delete
# 1. get the current master
iface = get_interface(msg['index'])
master = get_interface(iface['master'])
cmd = 'del'
else:
# port add
# 1. get the master
master = get_interface(master)
cmd = 'add'
ifname = msg.get_attr('IFLA_IFNAME') or \
get_interface(msg['index'])['ifname']
# 2. manage the port
forward_map = {'team': manage_team_port}
if master['kind'] in forward_map:
func = forward_map[master['kind']]
forward = func(cmd, master['ifname'], ifname, nl)
if forward is not None:
return {'verdict': 'forward',
'data': msg.data}
def sync(f):
'''
A decorator to wrap up external utility calls.
A decorated function receives a netlink message
as a parameter, and then:
1. Starts a monitoring thread
2. Performs the external call
3. Waits for a netlink event specified by `msg`
4. Joins the monitoring thread
If the wrapped function raises an exception, the
monitoring thread will be forced to stop via the
control channel pipe. The exception will be then
forwarded.
'''
def monitor(event, ifname, cmd):
with RawIPRoute() as ipr:
poll = select.poll()
poll.register(ipr, select.POLLIN | select.POLLPRI)
poll.register(cmd, select.POLLIN | select.POLLPRI)
ipr.bind()
while True:
events = poll.poll()
for (fd, event) in events:
if fd == ipr.fileno():
msgs = ipr.get()
for msg in msgs:
if msg.get('event') == event and \
msg.get_attr('IFLA_IFNAME') == ifname:
return
else:
return
def decorated(msg):
rcmd, cmd = os.pipe()
t = threading.Thread(target=monitor,
args=(RTM_VALUES[msg['header']['type']],
msg.get_attr('IFLA_IFNAME'),
rcmd))
t.start()
ret = None
try:
ret = f(msg)
except Exception:
raise
finally:
os.write(cmd, b'q')
t.join()
return ret
return decorated
def proxy_newlink(msg, nl):
kind = None
# get the interface kind
linkinfo = msg.get_attr('IFLA_LINKINFO')
if linkinfo is not None:
kind = [x[1] for x in linkinfo['attrs']
if x[0] == 'IFLA_INFO_KIND']
if kind:
kind = kind[0]
if kind == 'tuntap':
return manage_tuntap(msg)
elif kind == 'team':
return manage_team(msg)
return {'verdict': 'forward',
'data': msg.data}
@map_enoent
@sync
def manage_team(msg):
if msg['header']['type'] != RTM_NEWLINK:
raise ValueError('wrong command type')
config = {'device': msg.get_attr('IFLA_IFNAME'),
'runner': {'name': 'activebackup'},
'link_watch': {'name': 'ethtool'}}
with open(os.devnull, 'w') as fnull:
subprocess.check_call(['teamd', '-d', '-n', '-c', json.dumps(config)],
stdout=fnull,
stderr=fnull)
@map_enoent
def manage_team_port(cmd, master, ifname, nl):
with open(os.devnull, 'w') as fnull:
subprocess.check_call(['teamdctl', master, 'port',
'remove' if cmd == 'del' else 'add', ifname],
stdout=fnull,
stderr=fnull)
@sync
def manage_tuntap(msg):
if TUNSETIFF is None:
raise NetlinkError(errno.EOPNOTSUPP, 'Arch not supported')
if msg['header']['type'] != RTM_NEWLINK:
raise NetlinkError(errno.EOPNOTSUPP, 'Unsupported event')
ifru_flags = 0
linkinfo = msg.get_attr('IFLA_LINKINFO')
infodata = linkinfo.get_attr('IFLA_INFO_DATA')
flags = infodata.get_attr('IFTUN_IFR', None)
if infodata.get_attr('IFTUN_MODE') == 'tun':
ifru_flags |= IFT_TUN
elif infodata.get_attr('IFTUN_MODE') == 'tap':
ifru_flags |= IFT_TAP
else:
raise ValueError('invalid mode')
if flags is not None:
if flags['no_pi']:
ifru_flags |= IFT_NO_PI
if flags['one_queue']:
ifru_flags |= IFT_ONE_QUEUE
if flags['vnet_hdr']:
ifru_flags |= IFT_VNET_HDR
if flags['multi_queue']:
ifru_flags |= IFT_MULTI_QUEUE
ifr = msg.get_attr('IFLA_IFNAME')
if len(ifr) > IFNAMSIZ:
raise ValueError('ifname too long')
ifr += (IFNAMSIZ - len(ifr)) * '\0'
ifr = ifr.encode('ascii')
ifr += struct.pack('H', ifru_flags)
user = infodata.get_attr('IFTUN_UID')
group = infodata.get_attr('IFTUN_GID')
#
fd = os.open(TUNDEV, os.O_RDWR)
try:
ioctl(fd, TUNSETIFF, ifr)
if user is not None:
ioctl(fd, TUNSETOWNER, user)
if group is not None:
ioctl(fd, TUNSETGROUP, group)
ioctl(fd, TUNSETPERSIST, 1)
except Exception:
raise
finally:
os.close(fd)
......@@ -6,13 +6,13 @@ import struct
import threading
import subprocess
from fcntl import ioctl
from pyroute2 import RawIPRoute
from pyroute2 import config
from pyroute2.common import map_enoent
from pyroute2.netlink.rtnl import RTM_VALUES
from pyroute2.netlink.rtnl.marshal import MarshalRtnl
from pyroute2.netlink.rtnl.ifinfmsg import ifinfmsg
from pyroute2.netlink.exceptions import NetlinkError
from pyroute2.netlink.rtnl.riprsocket import RawIPRSocket
# it's simpler to double constants here, than to change all the
......@@ -227,7 +227,7 @@ def sync(f):
forwarded.
'''
def monitor(event, ifname, cmd):
with RawIPRoute() as ipr:
with RawIPRSocket() as ipr:
poll = select.poll()
poll.register(ipr, select.POLLIN | select.POLLPRI)
poll.register(cmd, select.POLLIN | select.POLLPRI)
......
import os
import json
import errno
import select
import struct
import threading
import subprocess
from fcntl import ioctl
from pyroute2 import config
from pyroute2.common import map_enoent
from pyroute2.netlink.rtnl.ifinfmsg import IFT_TUN
from pyroute2.netlink.rtnl.ifinfmsg import IFT_TAP
from pyroute2.netlink.rtnl.ifinfmsg import IFT_NO_PI
from pyroute2.netlink.rtnl.ifinfmsg import IFT_ONE_QUEUE
from pyroute2.netlink.rtnl.ifinfmsg import IFT_VNET_HDR
from pyroute2.netlink.rtnl.ifinfmsg import IFT_MULTI_QUEUE
from pyroute2.netlink.rtnl.ifinfmsg import RTM_NEWLINK
from pyroute2.netlink.rtnl import RTM_VALUES
from pyroute2.netlink.rtnl.riprsocket import RawIPRSocket
from pyroute2.netlink.exceptions import NetlinkError
_BONDING_MASTERS = '/sys/class/net/bonding_masters'
_BONDING_SLAVES = '/sys/class/net/%s/bonding/slaves'
_BRIDGE_MASTER = '/sys/class/net/%s/brport/bridge/ifindex'
_BONDING_MASTER = '/sys/class/net/%s/master/ifindex'
IFNAMSIZ = 16
TUNDEV = '/dev/net/tun'
if config.machine in ('i386', 'i686', 'x86_64', 'armv6l', 'armv7l'):
TUNSETIFF = 0x400454ca
TUNSETPERSIST = 0x400454cb
TUNSETOWNER = 0x400454cc
TUNSETGROUP = 0x400454ce
elif config.machine in ('ppc64', 'mips'):
TUNSETIFF = 0x800454ca
TUNSETPERSIST = 0x800454cb
TUNSETOWNER = 0x800454cc
TUNSETGROUP = 0x800454ce
else:
TUNSETIFF = None
def sync(f):
'''
A decorator to wrap up external utility calls.
A decorated function receives a netlink message
as a parameter, and then:
1. Starts a monitoring thread
2. Performs the external call
3. Waits for a netlink event specified by `msg`
4. Joins the monitoring thread
If the wrapped function raises an exception, the
monitoring thread will be forced to stop via the
control channel pipe. The exception will be then
forwarded.
'''
def monitor(event, ifname, cmd):
with RawIPRSocket() as ipr:
poll = select.poll()
poll.register(ipr, select.POLLIN | select.POLLPRI)
poll.register(cmd, select.POLLIN | select.POLLPRI)
ipr.bind()
while True:
events = poll.poll()
for (fd, event) in events:
if fd == ipr.fileno():
msgs = ipr.get()
for msg in msgs:
if msg.get('event') == event and \
msg.get_attr('IFLA_IFNAME') == ifname:
return
else:
return
def decorated(msg):
rcmd, cmd = os.pipe()
t = threading.Thread(target=monitor,
args=(RTM_VALUES[msg['header']['type']],
msg.get_attr('IFLA_IFNAME'),
rcmd))
t.start()
ret = None
try:
ret = f(msg)
except Exception:
raise
finally:
os.write(cmd, b'q')
t.join()
return ret
return decorated
def proxy_setlink(msg, nl):
def get_interface(index):
msg = nl.get_links(index)[0]
try:
kind = msg.get_attr('IFLA_LINKINFO').get_attr('IFLA_INFO_KIND')
except AttributeError:
kind = 'unknown'
return {'ifname': msg.get_attr('IFLA_IFNAME'),
'master': msg.get_attr('IFLA_MASTER'),
'kind': kind}
forward = True
# is it a port setup?
master = msg.get_attr('IFLA_MASTER')
if master is not None:
if master == 0:
# port delete
# 1. get the current master
iface = get_interface(msg['index'])
master = get_interface(iface['master'])
cmd = 'del'
else:
# port add
# 1. get the master
master = get_interface(master)
cmd = 'add'
ifname = msg.get_attr('IFLA_IFNAME') or \
get_interface(msg['index'])['ifname']
# 2. manage the port
forward_map = {'team': manage_team_port}
if master['kind'] in forward_map:
func = forward_map[master['kind']]
forward = func(cmd, master['ifname'], ifname, nl)
if forward is not None:
return {'verdict': 'forward',
'data': msg.data}
def proxy_newlink(msg, nl):
kind = None
# get the interface kind
linkinfo = msg.get_attr('IFLA_LINKINFO')
if linkinfo is not None:
kind = [x[1] for x in linkinfo['attrs']
if x[0] == 'IFLA_INFO_KIND']
if kind:
kind = kind[0]
if kind == 'tuntap':
return manage_tuntap(msg)