Example #1
0
 def __init__(self, instance_number):
     """
     :param instance_number: the controller instance number
     :param net: the subnet allocated for the fibbing nodes
     """
     self.leader = False
     self.instance = instance_number
     self.name = 'c%s' % instance_number
     self.nodes = {}
     self.bridge = Bridge('br0', self.name)
     self.root = None
     net = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
     controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
     host_prefix = net.max_prefixlen - controller_prefix
     controller_base = (int(net.network_address) +
                        (instance_number << host_prefix))
     controller_net = ip_address(controller_base)
     self.net = ip_network('%s/%s' % (controller_net, controller_prefix))
     self.graph_thread = daemon_thread(target=self.infer_graph,
                                       name="Graph inference thread")
     self.json_proxy = SJMPServer(hostname=CFG.get(DEFAULTSECT,
                                                   'json_hostname'),
                                  port=CFG.getint(DEFAULTSECT,
                                                  'json_port'),
                                  invoke=self.proxy_connected,
                                  target=FakeNodeProxyImplem(self))
     self.json_thread = daemon_thread(target=self.json_proxy.communicate,
                                      name="JSON proxy thread")
     # Used to assign unique router-id to each node
     self.next_id = 1
     self.links = []
     # The fibbing routes
     self.routes = {}
     self.route_mappings = {}
Example #2
0
 def __init__(self, instance_number):
     """
     :param instance_number: the controller instance number
     :param net: the subnet allocated for the fibbing nodes
     """
     self.leader = False
     self.instance = instance_number
     self.name = 'c%s' % instance_number
     self.nodes = {}
     self.bridge = Bridge('br0', self.name)
     self.root = None
     net = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
     controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
     host_prefix = net.max_prefixlen - controller_prefix
     controller_base = (int(net.network_address) +
                        (instance_number << host_prefix))
     controller_net = ip_address(controller_base)
     self.net = ip_network('%s/%s' % (controller_net, controller_prefix))
     self.graph_thread = Thread(target=self.infer_graph, name="Graph inference thread")
     self.json_proxy = SJMPServer(hostname=CFG.get(DEFAULTSECT, 'json_hostname'),
                                  port=CFG.getint(DEFAULTSECT, 'json_port'),
                                  invoke=self.proxy_connected,
                                  target=FakeNodeProxyImplem(self))
     self.json_thread = Thread(target=self.json_proxy.communicate)
     # Used to assign unique router-id to each node
     self.next_id = 1
     self.links = []
     # The fibbing routes
     self.routes = {}
     self.route_mappings = {}
Example #3
0
class FibbingManager(object):
    def __init__(self, instance_number):
        """
        :param instance_number: the controller instance number
        :param net: the subnet allocated for the fibbing nodes
        """
        self.leader = False
        self.instance = instance_number
        self.name = 'c%s' % instance_number
        self.nodes = {}
        self.bridge = Bridge('br0', self.name)
        self.root = None
        net = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        host_prefix = net.max_prefixlen - controller_prefix
        controller_base = (int(net.network_address) +
                           (instance_number << host_prefix))
        controller_net = ip_address(controller_base)
        self.net = ip_network('%s/%s' % (controller_net, controller_prefix))
        self.graph_thread = Thread(target=self.infer_graph, name="Graph inference thread")
        self.json_proxy = SJMPServer(hostname=CFG.get(DEFAULTSECT, 'json_hostname'),
                                     port=CFG.getint(DEFAULTSECT, 'json_port'),
                                     invoke=self.proxy_connected,
                                     target=FakeNodeProxyImplem(self))
        self.json_thread = Thread(target=self.json_proxy.communicate)
        # Used to assign unique router-id to each node
        self.next_id = 1
        self.links = []
        # The fibbing routes
        self.routes = {}
        self.route_mappings = {}

    def start(self, phys_ports, nodecount=None):
        """
        Start the fibbing network
        :param nodecount: Pre-allocate nodecount fibbing nodes
        """
        # Create root node
        self.root = self.add_node(id='root', cls=RootRouter, start=False)
        del self.nodes[self.root.id]  # The root node should not originate LSA
        self.graph_thread.start()
        self.json_thread.start()
        # And map all physical ports to it
        ports = gen_physical_ports(phys_ports)
        for name, addr in ports:
            link = PhysicalLink(self.root, name, addr)
            self.root.add_physical_link(link)
        self.root.start()
        # Create additional nodes if requested
        while nodecount > 0:
            self.add_node()
            nodecount -= 1

    def add_node(self, id=None, cls=Router, start=True):
        """
        Add a new fibbing node to the network and start it
        :param id: The name of the new node
        :param cls: The class to use to instantiate it
        :param start: Automatically start the node
        :return: The new node instance
        """
        # Create a node
        n = self.create_node(id, cls)
        # Link it to the bridge
        l = self.link(self.bridge, n)
        # Generate unique ip for its interface that's connected to the bridge
        router_ip = ip_interface('%s/%s' % (self.net[self.next_id], self.net.prefixlen))
        self.next_id += 1
        l.dst.set_ip(router_ip)
        if start:
            n.start()
        return n

    def create_node(self, id=None, cls=Router):
        """
        Create a new node
        :param id: The name of the new node
        :param cls: The class to use to instantiate it
        """
        n = cls(id=id, prefix=self.name, namespaced=True)
        self.nodes[n.id] = n
        log.info('Created node %s', n.id)
        return n

    def __getitem__(self, item):
        try:
            return self.nodes[item]
        except KeyError as e:
            if item == 'root':
                return self.root
            else:
                raise e

    def link(self, src, dst):
        """
        Create a veth Link between two nodes
        :param src: The source node of the link
        :param dst: The destination node of the link
        """
        log.debug('Linking %s to %s', src.id, dst.id)
        l = Link(src, dst)
        # Register the new link
        self.links.append(l)
        return l

    def print_net(self):
        """
        Log a representation of the fibbing network state
        """
        log.info('----------------------')
        log.info('Network of %s nodes', len(self.nodes))
        log.info('----------------------')
        log.info('')
        r_info = str(self.root)
        for l in r_info.split('\n'):
            log.info(l)
        log.info('|')
        bid_str = '%s --+' % self.bridge.id
        prefix = ' ' * (len(bid_str) - 1)
        log.info(bid_str)
        for n in self.nodes.values():
            if n == self.root:
                continue
            log.info('%s|', prefix)
            log.info('%s+--- %s', prefix, n)

    def print_routes(self):
        """
        Log all fibbing routes that are installed in the network
        """
        log.info('----------------------')
        log.info('%s fibbing routes', len(self.routes))
        log.info('----------------------')
        for route in self.routes.values():
            log.info(route)

    def cleanup(self):
        """
        Cleanup all namespaces/links/...
        """
        self.json_proxy.stop()
        for link in self.links:
            link.delete()
        self.root.delete()
        for node in self.nodes.values():
            node.delete()
        self.bridge.delete()

    def install_route(self, network, points, advertize):
        """
        Install and advertize a fibbing route
        :param network: the network prefix to attract
        :param points: a list of (address, metric) of points
        """
        net = ip_network(network)
        # Retrieve existing route if any
        try:
            route = self.routes[net]
        except KeyError:
            route = FibbingRoute(net, [])
            self.routes[net] = route
            self.route_mappings[net] = set()
        # Get used nodes mapping for this prefix
        mappings = self.route_mappings[net]
        # Increase node count if needed
        size = len(route) + len(points)
        while size > len(self.nodes):
            self.add_node()
        # Get available node list for this prefix
        nodes = [n for n in self.nodes.values() if n not in mappings]
        # Generate attraction points
        attraction_points = [AttractionPoint(addr, metric, nodes.pop())
                             for addr, metric in points if addr]
        # Update used nodes mapping
        for p in attraction_points:
            mappings.add(p.node)
        # Advertize them
        route.append(attraction_points, advertize)

    def remove_route(self, network):
        """
        Remove a route
        :param network: The prefix to remove
        """
        net = ip_network(network)
        try:
            route = self.routes[net]
            self.remove_route_part(net, *[p for p in route])
        except KeyError:
            log.debug('No route for network %s', net)

    def remove_route_part(self, network, advertize, *elems):
        """
        Remove elements of a route
        :param network: The prefix of the route
        :param elems: The list of forwarding address to remove
        """
        log.debug('Removing route for prefix %s, elements: %s', network, elems)
        net = ip_network(network)
        try:
            route = self.routes[net]
            for e in elems:
                node = route.retract(e, advertize)
                if node:
                    self.route_mappings[net].remove(node)
            if len(route) == 0:
                self.route_mappings.pop(net)
                self.routes.pop(net)
        except KeyError:
            log.debug('No route for network %s', network)

    def infer_graph(self):
        self.root.parse_lsdblog()

    def _get_proxy_routes(self, points):
        for prefix, parts in groupby(sorted(points, key=itemgetter(3)), key=itemgetter(3)):
            route = []
            for p in parts:
                src, dst, cost = p[0], p[1], p[2]
                if cost == -1:
                    cost = 1
                else:
                    src = None
                fwd_addr = self.root.get_fwd_address(src, dst)
                route.append((fwd_addr, str(cost)))
            yield prefix, route

    def proxy_add(self, points):
        """
        :param points: (source, fwd, cost, prefix)*
        """
        self.leader = self.instance == self.lsdb.get_leader()
        log.info('Shapeshifter added attraction points: %s', points)
        for prefix, route in self._get_proxy_routes(points):
            self.install_route(prefix, route, self.leader)

    def proxy_remove(self, points):
        """
        :param points: (source, fwd, cost, prefix)*
        """
        self.leader = self.instance == self.lsdb.get_leader()
        log.info('Shapeshifter removed attraction points: %s', points)
        for prefix, route in self._get_proxy_routes(points):
            # We don't need the cost
            self.remove_route_part(prefix, self.leader, *(r[0] for r in route))

    def proxy_connected(self, session):
        self.root.send_lsdblog_to(session)

    @property
    def lsdb(self):
        return self.root.lsdb
Example #4
0
class FibbingManager(object):
    def __init__(self, instance_number):
        """
        :param instance_number: the controller instance number
        :param net: the subnet allocated for the fibbing nodes
        """
        self.leader = False
        self.instance = instance_number
        self.name = 'c%s' % instance_number
        self.nodes = {}
        self.bridge = Bridge('br0', self.name)
        self.root = None
        net = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        host_prefix = net.max_prefixlen - controller_prefix
        controller_base = (int(net.network_address) +
                           (instance_number << host_prefix))
        controller_net = ip_address(controller_base)
        self.net = ip_network('%s/%s' % (controller_net, controller_prefix))
        self.graph_thread = daemon_thread(target=self.infer_graph,
                                          name="Graph inference thread")
        self.json_proxy = SJMPServer(hostname=CFG.get(DEFAULTSECT,
                                                      'json_hostname'),
                                     port=CFG.getint(DEFAULTSECT,
                                                     'json_port'),
                                     invoke=self.proxy_connected,
                                     target=FakeNodeProxyImplem(self))
        self.json_thread = daemon_thread(target=self.json_proxy.communicate,
                                         name="JSON proxy thread")
        # Used to assign unique router-id to each node
        self.next_id = 1
        self.links = []
        # The fibbing routes
        self.routes = {}
        self.route_mappings = {}

    def start(self, phys_ports, nodecount=None):
        """
        Start the fibbing network
        :param nodecount: Pre-allocate nodecount fibbing nodes
        """
        # Create root node
        self.root = self.add_node(id='root', cls=RootRouter, start=False)
        self.root.lsdb.set_leader_watchdog(self)
        del self.nodes[self.root.id]  # The root node should not originate LSA
        self.graph_thread.start()
        self.json_thread.start()
        # And map all physical ports to it
        ports = gen_physical_ports(phys_ports)
        for name, addr in ports:
            link = PhysicalLink(self.root, name, addr)
            self.root.add_physical_link(link)
        self.root.start()
        # Create additional nodes if requested
        if nodecount is None:
            nodecount = CFG.getint(DEFAULTSECT, 'initial_node_count')
        while nodecount > 0:
            self.add_node()
            nodecount -= 1

    def add_node(self, id=None, cls=Router, start=True):
        """
        Add a new fibbing node to the network and start it
        :param id: The name of the new node
        :param cls: The class to use to instantiate it
        :param start: Automatically start the node
        :return: The new node instance
        """
        # Create a node
        n = self.create_node(id, cls)
        # Link it to the bridge
        l = self.link(self.bridge, n)
        # Generate unique ip for its interface that's connected to the bridge
        router_ip = ip_interface('%s/%s' % (self.net[self.next_id],
                                            self.net.prefixlen))
        self.next_id += 1
        l.dst.set_ip(router_ip)
        if start:
            n.start()
        return n

    def create_node(self, id=None, cls=Router):
        """
        Create a new node
        :param id: The name of the new node
        :param cls: The class to use to instantiate it
        """
        n = cls(id=id, prefix=self.name, namespaced=True)
        self.nodes[n.id] = n
        log.info('Created node %s', n.id)
        return n

    def __getitem__(self, item):
        try:
            return self.nodes[item]
        except KeyError as e:
            if item == 'root':
                return self.root
            else:
                raise e

    def link(self, src, dst):
        """
        Create a veth Link between two nodes
        :param src: The source node of the link
        :param dst: The destination node of the link
        """
        log.debug('Linking %s to %s', src.id, dst.id)
        l = Link(src, dst)
        # Register the new link
        self.links.append(l)
        return l

    def print_net(self):
        """
        Log a representation of the fibbing network state
        """
        log.info('----------------------')
        log.info('Network of %s nodes', len(self.nodes))
        log.info('----------------------')
        log.info('')
        r_info = str(self.root)
        for l in r_info.split('\n'):
            log.info(l)
        log.info('|')
        bid_str = '%s --+' % self.bridge.id
        prefix = ' ' * (len(bid_str) - 1)
        log.info(bid_str)
        for n in self.nodes.values():
            if n == self.root:
                continue
            log.info('%s|', prefix)
            log.info('%s+--- %s', prefix, n)

    def print_routes(self):
        """
        Log all fibbing routes that are installed in the network
        """
        log.info('----------------------')
        log.info('%s fibbing routes', len(self.routes))
        log.info('----------------------')
        for route in self.routes.values():
            log.info(route)

    def cleanup(self):
        """
        Cleanup all namespaces/links/...
        """
        self.json_proxy.stop()
        for link in self.links:
            link.delete()
        self.root.delete()
        for node in self.nodes.values():
            node.delete()
        self.bridge.delete()

    def install_route(self, network, points, advertize):
        """
        Install and advertize a fibbing route
        :param network: the network prefix to attract
        :param points: a list of (address, metric) of points
        """
        try:
            net = ip_network(network)
        except ValueError:
            log.error('%s is not a valid IP network', network)
            return
        try:
            points_data = [(ip_address(addr), int(metric))
                           for addr, metric in points]
        except ValueError as e:
            log.error("Failed to parse an attraction point: %s", e)
            return
        # Retrieve existing route if any
        try:
            route = self.routes[net]
        except KeyError:
            route = FibbingRoute(net, [])
            self.routes[net] = route
            self.route_mappings[net] = set()
        # Get used nodes mapping for this prefix
        mappings = self.route_mappings[net]
        # Increase node count if needed
        size = len(route) + len(points)
        while size > len(self.nodes):
            self.add_node()
        # Get available node list for this prefix
        nodes = [n for n in self.nodes.values() if n not in mappings]
        # Generate attraction points
        attraction_points = [AttractionPoint(addr, metric, nodes.pop())
                             for addr, metric in points_data]
        # Update used nodes mapping
        for p in attraction_points:
            mappings.add(p.node)
        # Advertize them
        route.append(attraction_points, advertize)

    def remove_route(self, network):
        """
        Remove a route
        :param network: The prefix to remove
        """
        net = ip_network(network)
        try:
            route = self.routes[net]
            self.remove_route_part(net, *[p for p in route])
        except KeyError:
            log.debug('No route for network %s', net)

        except Exception as other_exception:
            log.exception(other_exception)

    def remove_route_part(self, network, advertize, *elems):
        """
        Remove elements of a route
        :param network: The prefix of the route
        :param elems: The list of forwarding addresses to remove
        """
        log.debug('Removing route for prefix %s, elements: %s', network, elems)
        net = ip_network(network)
        try:
            route = self.routes[net]
            for e in elems:
                node = route.retract(e, advertize)
                if node:
                    self.route_mappings[net].remove(node)
            if len(route) == 0:
                self.route_mappings.pop(net)
                self.routes.pop(net)
        except KeyError:
            log.debug('No route for network %s', network)

    def remove_session_routes(self, session=None):
        """Removes the routes that were forced by the application in the session.
        This function is ment to be invoked when application disconnects, but for the moment we will
        use it in self.proxy_connected

        TODO: Keep track of which routes were forced by the application in the current session
        TODO: Make this function be invoked in the SJMPServer when the application disconnects
        """
        log.info("Removing routes from current session...")
        map(self.remove_route, self.routes.keys())

    def infer_graph(self):
        self.root.parse_lsdblog()

    def _get_proxy_routes(self, points):
        for prefix, parts in groupby(sorted(points, key=itemgetter(3)),
                                     key=itemgetter(3)):
            route = []
            for p in parts:
                src, dst, cost = p[0], p[1], p[2]
                if cost >= 0:
                    src = None
                fwd_addr = self.root.get_fwd_address(src, dst)
                # Can have multiple private addresses per interface, handle
                # here the selection ...
                if isinstance(fwd_addr, list):
                    try:
                        fwd_addr = fwd_addr[cost]
                    except IndexError:
                        log.warning('Required private forwarding address index'
                                    ' is out of bounds. Wanted index %s '
                                    '- Have %s elements.',
                                    abs(cost), len(fwd_addr))
                        fwd_addr = fwd_addr[0]
                    cost = 1
                try:
                    fwd_addr = str(ip_interface(fwd_addr).ip)
                except ValueError:
                    log.debug('Forwarding address for %s-%s has no netmask: %s',
                              src, dst, fwd_addr)
                route.append((fwd_addr, str(cost)))
            yield prefix, route

    def proxy_add(self, points):
        """
        :param points: (source, fwd, cost, prefix)*
        """
        log.info('Shapeshifter added attraction points: %s', points)
        for prefix, route in self._get_proxy_routes(points):
            self.install_route(prefix, route, self.leader)

    def proxy_remove(self, points):
        """
        :param points: (source, fwd, cost, prefix)*
        """
        log.info('Shapeshifter removed attraction points: %s', points)
        for prefix, route in self._get_proxy_routes(points):
            # We don't need the cost
            self.remove_route_part(prefix, self.leader, *(r[0] for r in route))

    def proxy_connected(self, session):
        self.root.send_lsdblog_to(session)

        self.remove_session_routes(session)

    @property
    def lsdb(self):
        return self.root.lsdb

    def check_leader(self, instance):
        was_leader = self.leader
        self.leader = self.instance == self.lsdb.get_leader()
        if self.leader and not was_leader:  # We are the new leader
            log.info('Elected as leader')
            for route in self.routes.values():
                route.advertize()
        elif was_leader and not self.leader:
            log.info('No longer leader')