| """ |
| Copyright (C) 2011-2015 Jiri Pirko <jiri@resnulli.us> |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| """ |
| |
| __author__ = """ |
| jiri@resnulli.us (Jiri Pirko) |
| """ |
| |
| import capi |
| import select |
| import struct |
| |
| TEAM_ANY_CHANGE = capi.TEAM_ANY_CHANGE |
| TEAM_PORT_CHANGE = capi.TEAM_PORT_CHANGE |
| TEAM_OPTION_CHANGE = capi.TEAM_OPTION_CHANGE |
| |
| class TeamError(Exception): |
| pass |
| |
| class TeamLibError(TeamError): |
| def __init__(self, msg, err = 0): |
| self._msg = msg |
| self._err = err |
| |
| def __str__(self): |
| msg = self._msg |
| if self._err: |
| msg += " Err=%d" % self._err |
| return msg |
| |
| class TeamUnknownOptionTypeError(TeamError): |
| pass |
| |
| class TeamNetDeviceIndexNameConverter(object): |
| """ |
| Class does conversion between various network device identificators. |
| Note both functions have DeviceID as input parameter. |
| That can be either: interface index (int) |
| interface name (str) |
| TeamNetDevice (or inheritor) instance |
| """ |
| def __init__(self, th): |
| self._th = th |
| |
| def get_ifindex(self, dev_id): |
| """ |
| Get interface index. |
| """ |
| if isinstance(dev_id, int): |
| return dev_id |
| elif isinstance(dev_id, TeamNetDevice): |
| return dev_id.ifindex |
| elif isinstance(dev_id, str): |
| return capi.team_ifname2ifindex(self._th, dev_id) |
| raise TeamError("Cannot convert to interface index.") |
| |
| def dev_ifname(self, dev_id): |
| """ |
| Get interface name. |
| """ |
| if isinstance(dev_id, str): |
| return dev_id |
| elif isinstance(dev_id, TeamNetDevice): |
| ifindex = dev_id.ifindex |
| elif isinstance(dev_id, int): |
| ifindex = dev_id |
| else: |
| raise TeamError("Cannot convert to interface name.") |
| return capi.team_ifindex2ifname(self._th, ifindex, 32) |
| |
| class TeamNetDevice(object): |
| """ |
| Class for manipulating generic network device. |
| """ |
| def __init__(self, th, ifindex = 0): |
| self._th = th |
| self._conv = TeamNetDeviceIndexNameConverter(th) |
| self.ifindex = ifindex |
| |
| def __str__(self): |
| return self.ifname |
| |
| @property |
| def ifindex(self): |
| return self._ifindex |
| |
| @ifindex.setter |
| def ifindex(self, ifindex): |
| self._ifindex = ifindex |
| if self.ifindex: |
| self.ifname = self._conv.dev_ifname(ifindex) |
| |
| def get_hwaddr(self): |
| err, hwaddr = capi.team_hwaddr_get(self._th, self.ifindex, 6) |
| if err: |
| raise TeamLibError("Failed to get hardware address", err) |
| return ":".join(map(lambda x: "%02X" % x, struct.unpack('BBBBBB', hwaddr))) |
| |
| def set_hwaddr(self, hwaddr_str): |
| pack = struct.pack('BBBBBB', *map(lambda x : int(x, 16), hwaddr_str.split(":"))) |
| err = capi.team_hwaddr_set(self._th, self.ifindex, pack) |
| if err: |
| raise TeamLibError("Failed to set hardware address", err) |
| |
| class TeamPort(TeamNetDevice): |
| """ |
| Class stores port data and serves for port modification. |
| """ |
| def update(self, lib_port): |
| """ |
| Update option by give library structure. |
| """ |
| self.speed = capi.team_get_port_speed(lib_port) |
| self.duplex = capi.team_get_port_duplex(lib_port) |
| self.changed = capi.team_is_port_changed(lib_port) |
| self.linkup = capi.team_is_port_link_up(lib_port) |
| self.removed = capi.team_is_port_removed(lib_port) |
| |
| class TeamPortListIterator(object): |
| """ |
| Iterator class for TeamPortList class for iterating over all listed ports. |
| """ |
| def __init__(self, ports): |
| self._ports = ports |
| self._cursor = 0 |
| |
| def __iter__(self): |
| iter |
| |
| def next(self): |
| """ Get next item in dict """ |
| if self._cursor == len(self._ports): |
| raise StopIteration |
| else: |
| key = self._ports.keys()[self._cursor] |
| self._cursor += 1 |
| return self._ports[key] |
| |
| class TeamPortList(object): |
| """ |
| Class contains list of ports present on team. Dictionary is used |
| internaly since port interface index is unique. |
| """ |
| def __init__(self, th): |
| self._th = th |
| self._conv = TeamNetDeviceIndexNameConverter(th) |
| self._ports = {} |
| self.update() |
| |
| def __len__(self): |
| return len(self._ports) |
| |
| def __iter__(self): |
| return TeamPortListIterator(self._ports) |
| |
| def get_port(self, port_dev_id): |
| """ |
| Get port instance identified by DeviceID. |
| """ |
| return self._ports[self._conv.get_ifindex(port_dev_id)] |
| |
| def update(self): |
| """ |
| Fetch fresh data from library and adjust update dictionary and port |
| instances by it. |
| """ |
| lib_port_ifindex_list = [] |
| lib_port = capi.team_get_next_port(self._th, None) |
| while lib_port: |
| ifindex = capi.team_get_port_ifindex(lib_port) |
| lib_port_ifindex_list.append(ifindex) |
| try: |
| port = self.get_port(ifindex) |
| except KeyError: |
| port = TeamPort(self._th, ifindex) |
| self._ports[ifindex] = port |
| port.update(lib_port) |
| lib_port = capi.team_get_next_port(self._th, lib_port) |
| |
| for ifindex in self._ports.keys(): |
| if not ifindex in lib_port_ifindex_list: |
| del self._ports[ifindex] |
| |
| class TeamOption(object): |
| """ |
| Class stores option data and serves for value modification. |
| """ |
| def __init__(self, th, name): |
| self._th = th |
| self.name = name |
| |
| def _get_option_value(self, lib_option): |
| opt_type = capi.team_get_option_type(lib_option) |
| if opt_type == capi.TEAM_OPTION_TYPE_U32: |
| return capi.team_get_option_value_u32(lib_option) |
| elif opt_type == capi.TEAM_OPTION_TYPE_STRING: |
| return capi.team_get_option_value_string(lib_option) |
| else: |
| raise TeamUnknownOptionTypeError() |
| |
| def update(self, lib_option): |
| """ |
| Update option by give library structure. |
| """ |
| self.value = self._get_option_value(lib_option) |
| self.changed = capi.team_is_option_changed(lib_option) |
| |
| def set_value(self, value): |
| """ |
| Set option value. |
| """ |
| if isinstance(value, int): |
| err = capi.team_set_option_value_by_name_u32(self._th, self.name, |
| value) |
| elif isinstance(value, str): |
| err = capi.team_set_option_value_by_name_string(self._th, |
| self.name, |
| value) |
| else: |
| raise TeamUnknownOptionTypeError() |
| if err: |
| raise TeamLibError("Failed to set option", err) |
| |
| class TeamOptionListIterator(object): |
| """ |
| Iterator class for TeamOptionList class for iterating over all options. |
| """ |
| def __init__(self, options): |
| self._options = options |
| self._cursor = 0 |
| |
| def __iter__(self): |
| iter |
| |
| def next(self): |
| """ Get next item in dict """ |
| if self._cursor == len(self._options): |
| raise StopIteration |
| else: |
| key = self._options.keys()[self._cursor] |
| self._cursor += 1 |
| return self._options[key] |
| |
| class TeamOptionList(object): |
| """ |
| Class contains list of options present on team. Dictionary is used |
| internaly since option name is unique. |
| """ |
| def __init__(self, th): |
| self._th = th |
| self._options = {} |
| self.update() |
| |
| def __len__(self): |
| return len(self._options) |
| |
| def __iter__(self): |
| return TeamOptionListIterator(self._options) |
| |
| def get_option(self, opt_name): |
| """ |
| Get option instance identified by name. |
| """ |
| return self._options[opt_name] |
| |
| def update(self): |
| """ |
| Fetch fresh data from library and adjust update dictionary and option |
| instances by it. |
| """ |
| lib_option_name_list = [] |
| lib_option = capi.team_get_next_option(self._th, None) |
| while lib_option: |
| opt_name = capi.team_get_option_name(lib_option) |
| lib_option_name_list.append(opt_name) |
| try: |
| option = self.get_option(opt_name) |
| except KeyError: |
| option = TeamOption(self._th, opt_name) |
| self._options[opt_name] = option |
| try: |
| option.update(lib_option) |
| except TeamUnknownOptionTypeError: |
| del self._options[opt_name] |
| lib_option = capi.team_get_next_option(self._th, lib_option) |
| |
| for opt_name in self._options.keys(): |
| if not opt_name in lib_option_name_list: |
| del self._options[opt_name] |
| |
| class TeamChangeHandler(object): |
| def __init__(self, func, func_priv, type_mask): |
| self._func = func |
| self._func_priv = func_priv |
| self._type_mask = type_mask |
| |
| def call(self, curr_type_mask): |
| if self._type_mask & curr_type_mask: |
| return self._func(self._func_priv) |
| else: |
| return 0 |
| |
| class TeamChangeHandlerList(object): |
| def __init__(self): |
| self._list = [] |
| |
| def add(self, handler): |
| if handler in self._list: |
| raise TeamError("Failed to register change handler. Handler is already registered.") |
| self._list.append(handler) |
| |
| def remove(self, handler): |
| if not handler in self._list: |
| raise TeamError("Failed to unregister change handler. Handler is not registered.") |
| self._list.remove(handler) |
| |
| def call(self, type_mask): |
| for handler in self._list: |
| ret = handler.call(type_mask) |
| if ret != 0: |
| return ret |
| |
| |
| class Team(TeamNetDevice): |
| """ |
| Class representing one team device instance. |
| Paramaters passed to constructor allows: |
| create == True ... Create new team device if it does not already exist. |
| recreate == True ... Same as create but in case device exists already, |
| it's removed first. |
| destroy == True ... Remove team device in close function. |
| """ |
| def __init__(self, teamdev, create = False, recreate = False, destroy = False): |
| th = capi.team_alloc() |
| if not th: |
| raise TeamLibError("Failed to allocate team handle.") |
| |
| super(Team, self).__init__(th) |
| |
| if isinstance(teamdev, str): |
| err = 0 |
| if recreate: |
| err = capi.team_recreate(th, teamdev) |
| elif create: |
| err = capi.team_create(th, teamdev) |
| if err: |
| raise TeamLibError("Failed to create team.", err) |
| |
| ifindex = self._conv.get_ifindex(teamdev) if teamdev else 0 |
| err = capi.team_init(th, ifindex) |
| if err: |
| raise TeamLibError("Failed to init team.", err) |
| |
| self.ifindex = ifindex |
| self._destroy = destroy |
| self._change_handler_list = TeamChangeHandlerList() |
| self._port_list = TeamPortList(th) |
| self._option_list = TeamOptionList(th) |
| |
| self._change_handler = capi.team_change_handler(self._change_handler_func, |
| TEAM_ANY_CHANGE) |
| capi.py_team_change_handler_register(self._th, self._change_handler, None) |
| |
| |
| def close(self): |
| """ |
| Do class cleanup |
| """ |
| if self._destroy: |
| err = capi.team_destroy(self._th) |
| if err: |
| raise TeamLibError("Failed to destroy team.", err) |
| |
| capi.py_team_change_handler_unregister(self._th, |
| self._change_handler, None) |
| capi.team_free(self._th) |
| |
| def kill_loop(self): |
| self._kill_loop = True |
| |
| def loop_forever(self): |
| self._kill_loop = False |
| fd = capi.team_get_event_fd(self._th) |
| while True: |
| try: |
| ret = select.select([fd], [], []) |
| if fd in ret[0]: |
| capi.team_handle_events(self._th) |
| except KeyboardInterrupt: |
| return |
| except select.error as e: |
| if e[0] == 4: |
| if self._kill_loop: |
| return |
| except: |
| raise |
| |
| def check_events(self): |
| capi.team_check_events(self._th) |
| |
| def _change_handler_func(self, func_priv, type_mask): |
| if type_mask & TEAM_PORT_CHANGE: |
| self._port_list.update() |
| if type_mask & TEAM_OPTION_CHANGE: |
| self._option_list.update() |
| self._change_handler_list.call(type_mask) |
| |
| def change_handler_register(self, change_handler): |
| self._change_handler_list.add(change_handler) |
| |
| def change_handler_unregister(self, change_handler): |
| self._change_handler_list.remove(change_handler) |
| |
| def get_mode_name(self): |
| err, name = capi.team_get_mode_name(self._th) |
| if err: |
| raise TeamLibError("Failed to get mode name.", err) |
| return name |
| |
| def set_mode_name(self, name): |
| err = capi.team_set_mode_name(self._th, name) |
| if err: |
| raise TeamLibError("Failed to set mode name.", err) |
| |
| def get_active_port(self): |
| err, port_ifindex = capi.team_get_active_port(self._th) |
| if err: |
| raise TeamLibError("Failed to get active port.", err) |
| return self.get_port(port_ifindex) |
| |
| def set_active_port(self, dev_port_id): |
| err = capi.team_set_active_port(self._th, |
| self._conv.get_ifindex(dev_port_id)) |
| if err: |
| raise TeamLibError("Failed to set active port.", err) |
| |
| def port_list(self): |
| return self._port_list |
| |
| def get_port(self, port_dev_id): |
| return self._port_list.get_port(port_dev_id) |
| |
| def port_add(self, port_dev_id): |
| err = capi.team_port_add(self._th, |
| self._conv.get_ifindex(port_dev_id)) |
| if err: |
| raise TeamLibError("Failed to add port.", err) |
| |
| def port_remove(self, port_dev_id): |
| err = capi.team_port_remove(self._th, |
| self._conv.get_ifindex(port_dev_id)) |
| if err: |
| raise TeamLibError("Failed to remove port.", err) |
| |
| def option_list(self): |
| return self._option_list |
| |
| def get_option(self, opt_name): |
| return self._option_list.get_option(opt_name) |