Example #1
0
    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader('connect',
                                      api_version='1.0',
                                      base_class=ConnectNode)

        self.nmlnode_node_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()
Example #2
0
    def __init__(self, timestamp, nmlmanager, **kwargs):

        self.node_loader = NodeLoader('docker',
                                      api_version='1.0',
                                      base_class=DockerNode)

        self.nmlnode_node_map = OrderedDict()
        self.nmlbiport_iface_map = OrderedDict()
        self.nmlbilink_nmlbiports_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

        # Create netns folder
        privileged_cmd('mkdir -p /var/run/netns')
Example #3
0
    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader(
            'connect', api_version='1.0', base_class=ConnectNode
        )

        self.nmlnode_node_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()
    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader(
            'docker', api_version='1.0', base_class=DockerNode
        )

        self.nmlnode_node_map = OrderedDict()
        self.nmlbiport_iface_map = OrderedDict()
        self.nmlbilink_nmlbiports_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

        # Create netns folder
        privileged_cmd('mkdir -p /var/run/netns')
Example #5
0
class ConnectPlatform(BasePlatform):
    """
    FIXME: Document.
    """
    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader('connect',
                                      api_version='1.0',
                                      base_class=ConnectNode)

        self.nmlnode_node_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

    def pre_build(self):
        """
        See :meth:`BasePlatform.pre_build` for more information.
        """

    def add_node(self, node):
        """
        See :meth:`BasePlatform.add_node` for more information.
        """
        # Lookup for node of given type
        node_type = node.metadata.get('type', 'host')
        if node_type not in self.available_node_types:
            raise Exception('Unknown node type {}'.format(node_type))

        # Create instance of node type and start
        enode = self.available_node_types[node_type](node.identifier,
                                                     **node.metadata)

        # Register node
        self.nmlnode_node_map[node.identifier] = enode

        # Start node
        enode.start()

        return enode

    def add_biport(self, node, biport):
        """
        See :meth:`BasePlatform.add_biport` for more information.
        """
        # FIXME: Save this port for later validation in post_build.
        return biport.identifier

    def add_bilink(self, nodeport_a, nodeport_b, bilink):
        """
        Add a link between two nodes.

        See :meth:`BasePlatform.add_bilink` for more information.
        """
        # FIXME: Save this link for later validation in post_build.

    def post_build(self):
        """
        See :meth:`BasePlatform.post_build` for more information.
        """
        # FIXME: Check that the final topology is the same as the known /
        # hardwired one.

    def destroy(self):
        """
        See :meth:`BasePlatform.destroy` for more information.
        """
        for enode in self.nmlnode_node_map.values():
            try:
                enode.stop()
            except:
                log.error(format_exc())

    def rollback(self, stage, enodes, exception):
        """
        See :meth:`BasePlatform.rollback` for more information.
        """
        log.info('Topology Connect rollback called...')
        self.destroy()

    def relink(self, link_id):
        """
        See :meth:`BasePlatform.relink` for more information.
        """
        raise RuntimeError(
            'relink is not currently supported in this Engine Platform.')

    def unlink(self, link_id):
        """
        See :meth:`BasePlatform.unlink` for more information.
        """
        raise RuntimeError(
            'unlink is not currently supported in this Engine Platform.')
class DockerPlatform(BasePlatform):
    """
    Plugin to build a topology using Docker.

    See :class:`topology.platforms.base.BasePlatform` for more information.
    """

    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader(
            'docker', api_version='1.0', base_class=DockerNode
        )

        self.nmlnode_node_map = OrderedDict()
        self.nmlbiport_iface_map = OrderedDict()
        self.nmlbilink_nmlbiports_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

        # Create netns folder
        privileged_cmd('mkdir -p /var/run/netns')

    def pre_build(self):
        """
        See :meth:`BasePlatform.pre_build` for more information.
        """

    def add_node(self, node):
        """
        Add a new DockerNode.

        See :meth:`BasePlatform.add_node` for more information.
        """
        # Lookup for node of given type
        node_type = node.metadata.get('type', 'host')
        if node_type not in self.available_node_types:
            raise Exception('Unknown node type {}'.format(node_type))

        # Create instance of node type and start
        enode = self.available_node_types[node_type](
            node.identifier, **node.metadata
        )
        enode.start()

        # Install container netns locally
        privileged_cmd(
            'ln -s /proc/{pid}/ns/net /var/run/netns/{pid}',
            pid=enode._pid
        )

        # Register and return node
        self.nmlnode_node_map[node.identifier] = enode
        return enode

    def add_biport(self, node, biport):
        """
        Add a port to the docker node.

        See :meth:`BasePlatform.add_biport` for more information.
        """
        enode = self.nmlnode_node_map[node.identifier]
        eport = enode.notify_add_biport(node, biport)

        # Register this port for later creation
        self.nmlbiport_iface_map[biport.identifier] = {
            'created': False,
            'iface': eport,
            'netns': enode._pid,
            'owner': node.identifier,
            'label': biport.metadata.get('label', biport.identifier)
        }

        return eport

    def add_bilink(self, nodeport_a, nodeport_b, bilink):
        """
        Add a link between two nodes.

        See :meth:`BasePlatform.add_bilink` for more information.
        """
        node_a, port_a = nodeport_a
        node_b, port_b = nodeport_b

        # Get enodes
        enode_a = self.nmlnode_node_map[node_a.identifier]
        enode_b = self.nmlnode_node_map[node_b.identifier]

        # Determine temporal interfaces names
        tmp_iface_a = tmp_iface()
        tmp_iface_b = tmp_iface()

        # Determine final interface names
        iface_a = self.nmlbiport_iface_map[port_a.identifier]['iface']
        iface_b = self.nmlbiport_iface_map[port_b.identifier]['iface']

        # Create links between nodes:
        #   docs.docker.com/articles/networking/#building-a-point-to-point-connection # noqa
        commands = """\
        ip link add {tmp_iface_a} type veth peer name {tmp_iface_b}
        ip link set {tmp_iface_a} netns {enode_a._pid}
        ip link set {tmp_iface_b} netns {enode_b._pid}
        ip netns exec {enode_a._pid} ip link set {tmp_iface_a} name {iface_a}
        ip netns exec {enode_b._pid} ip link set {tmp_iface_b} name {iface_b}\
        """
        privileged_cmd(commands, **locals())

        # Notify enodes of created interfaces
        enode_a.notify_add_bilink(nodeport_a, bilink)
        enode_b.notify_add_bilink(nodeport_b, bilink)

        # Mark interfaces as created
        self.nmlbiport_iface_map[port_a.identifier]['created'] = True
        self.nmlbiport_iface_map[port_b.identifier]['created'] = True

        # Register this links
        self.nmlbilink_nmlbiports_map[bilink.identifier] = (
            port_a.identifier, port_b.identifier
        )

        # Apply some attributes
        for enode, port, iface in \
                ((enode_a, port_a, iface_a), (enode_b, port_b, iface_b)):

            prefix = 'ip netns exec {pid} '.format(pid=enode._pid)

            # Set ipv4 and ipv6 addresses
            for version in [4, 6]:
                attribute = 'ipv{}'.format(version)
                if attribute not in port.metadata:
                    continue

                addr = port.metadata[attribute]
                cmd = 'ip -{version} addr add {addr} dev {iface}'.format(
                    **locals()
                )
                privileged_cmd(prefix + cmd)

            # Bring-up or down
            if bilink.metadata.get('up', None) is None and \
                    port.metadata.get('up', None) is None:
                continue

            up = bilink.metadata.get('up', True) and \
                port.metadata.get('up', True)

            state = 'up' if up else 'down'
            cmd = 'ip link set dev {iface} {state}'.format(**locals())
            privileged_cmd(prefix + cmd)

    def post_build(self):
        """
        Ports are created for each node automatically while adding links.
        Creates the rest of the ports (no-linked ports)

        See :meth:`BasePlatform.post_build` for more information.
        """
        # Create remaining interfaces
        cmd_tpl = 'ip netns exec {netns} ip tuntap add dev {iface} mode tap'
        for port_spec in self.nmlbiport_iface_map.values():

            # Ignore already created interfaces
            if port_spec['created']:
                continue

            # Create port as dummy tuntap device
            privileged_cmd(cmd_tpl, **port_spec)

            # Mark as created
            port_spec['created'] = True

        # Notify nodes of the post_build event
        for enode in self.nmlnode_node_map.values():
            enode.notify_post_build()

    def destroy(self):
        """
        See :meth:`BasePlatform.destroy` for more information.
        """
        # Request termination of all containers
        for enode in self.nmlnode_node_map.values():
            enode.stop()

        # Remove the linked netns
        for enode in self.nmlnode_node_map.values():
            privileged_cmd('rm /var/run/netns/{pid}', pid=enode._pid)

    def rollback(self, stage, enodes, exception):
        """
        See :meth:`BasePlatform.rollback` for more information.
        """
        self.destroy()

    def _common_link(self, link_id, action):
        """
        Common action to relink / unlink.

        :param str link_id: Identifier of the link to modify.
        :param bool action: True if up, False if down.
        """
        if link_id not in self.nmlbilink_nmlbiports_map:
            raise Exception('Unknown link "{}"'.format(link_id))

        # iterate endpoints
        for port_id in self.nmlbilink_nmlbiports_map[link_id]:

            # Get specification for this endpoint
            port_spec = self.nmlbiport_iface_map[port_id]

            # Get node for the owner of this port
            enode = self.nmlnode_node_map[port_spec['owner']]
            enode.set_port_state(port_spec['label'], action)

    def relink(self, link_id):
        """
        See :meth:`BasePlatform.relink` for more information.
        """
        self._common_link(link_id, True)

    def unlink(self, link_id):
        """
        See :meth:`BasePlatform.unlink` for more information.
        """
        self._common_link(link_id, False)
Example #7
0
class DockerPlatform(BasePlatform):
    """
    Plugin to build a topology using Docker.

    See :class:`topology.platforms.base.BasePlatform` for more information.
    """

    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader(
            'docker', api_version='1.0', base_class=DockerNode
        )

        self.nmlnode_node_map = OrderedDict()
        self.nmlbiport_iface_map = OrderedDict()
        self.nmlbilink_nmlbiports_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

        # Create netns folder
        privileged_cmd('mkdir -p /var/run/netns')

    def pre_build(self):
        """
        See :meth:`BasePlatform.pre_build` for more information.
        """

    def add_node(self, node):
        """
        Add a new DockerNode.

        See :meth:`BasePlatform.add_node` for more information.
        """
        # Lookup for node of given type
        node_type = node.metadata.get('type', 'host')
        if node_type not in self.available_node_types:
            raise Exception('Unknown node type {}'.format(node_type))

        # Create instance of node type and start
        enode = self.available_node_types[node_type](
            node.identifier, **node.metadata
        )

        # Register node
        self.nmlnode_node_map[node.identifier] = enode

        # Start node
        enode.start()

        # Install container netns locally
        privileged_cmd(
            'ln -s /proc/{pid}/ns/net /var/run/netns/{pid}',
            pid=enode._pid
        )

        # Manage network and their netns creation
        network_handlers = {
            'docker': create_docker_network,
            'platform': create_platform_network
        }

        for category, config in enode._get_network_config()['mapping'].items():

            managed_by = config['managed_by']
            handler = network_handlers.get(managed_by, None)

            if handler is None:
                raise RuntimeError(
                    'Unknown "managed_by" handler {}'.format(managed_by)
                )

            handler(enode, category, config)

        return enode

    def add_biport(self, node, biport):
        """
        Add a port to the docker node.

        See :meth:`BasePlatform.add_biport` for more information.
        """
        enode = self.nmlnode_node_map[node.identifier]
        eport = enode.notify_add_biport(node, biport)

        # Get network configuration of given port
        network_config = enode._get_network_config()
        category_config = network_config['mapping'][
            network_config['default_category']
        ]

        # Register this port for later creation
        self.nmlbiport_iface_map[biport.identifier] = {
            'created': False,
            'iface_base': eport,
            'prefix': category_config['prefix'],
            'iface': '{}{}'.format(category_config['prefix'], eport),
            'container_ns': enode._pid,
            'netns': category_config['netns'],
            'owner': node.identifier,
            'label': biport.metadata.get('label', biport.identifier)
        }

        return eport

    def add_bilink(self, nodeport_a, nodeport_b, bilink):
        """
        Add a link between two nodes.

        See :meth:`BasePlatform.add_bilink` for more information.
        """
        node_a, port_a = nodeport_a
        node_b, port_b = nodeport_b

        # Get enodes
        enode_a = self.nmlnode_node_map[node_a.identifier]
        enode_b = self.nmlnode_node_map[node_b.identifier]

        # Determine temporal interfaces names
        tmp_iface_a = tmp_iface()
        tmp_iface_b = tmp_iface()

        # Get port spec dictionary
        port_spec_a = self.nmlbiport_iface_map[port_a.identifier]
        port_spec_b = self.nmlbiport_iface_map[port_b.identifier]

        # Determine final interface names
        iface_a = port_spec_a['iface']
        iface_b = port_spec_b['iface']

        # Create links between nodes:
        #   docs.docker.com/v1.5/articles/networking/#building-a-point-to-point-connection # noqa
        commands = """\
        ip link add {tmp_iface_a} type veth peer name {tmp_iface_b}
        ip link set {tmp_iface_a} netns {enode_a._pid} name {iface_a}
        ip link set {tmp_iface_b} netns {enode_b._pid} name {iface_b}\
        """
        privileged_cmd(commands, **locals())

        # Apply some attributes to nodes and ports
        for enode, port, port_spec, iface in (
            (enode_a, port_a, port_spec_a, iface_a),
            (enode_b, port_b, port_spec_b, iface_b)
        ):

            # Move interfaces to correct network namespace
            netns = port_spec['netns']
            if netns:
                enode._docker_exec(
                    'ip link set dev {iface} netns {netns}'.format(
                        **locals()
                    )
                )
                cmd_prefix = 'ip netns exec {netns} '.format(**locals())
            else:
                cmd_prefix = ''

            # Set ipv4 and ipv6 addresses
            for version in [4, 6]:
                attribute = 'ipv{}'.format(version)
                if attribute not in port.metadata:
                    continue

                addr = port.metadata[attribute]
                cmd = (
                    '{cmd_prefix}ip -{version} addr add {addr} dev {iface}'
                ).format(**locals())
                enode._docker_exec(cmd)

            # Bring-up or down
            if bilink.metadata.get('up', None) is None and \
                    port.metadata.get('up', None) is None:
                continue

            up = bilink.metadata.get('up', True) and \
                port.metadata.get('up', True)

            state = 'up' if up else 'down'
            cmd = '{cmd_prefix}ip link set dev {iface} {state}'.format(
                **locals()
            )
            enode._docker_exec(cmd)

        # Notify enodes of created interfaces
        enode_a.notify_add_bilink(nodeport_a, bilink)
        enode_b.notify_add_bilink(nodeport_b, bilink)

        # Mark interfaces as created
        self.nmlbiport_iface_map[port_a.identifier]['created'] = True
        self.nmlbiport_iface_map[port_b.identifier]['created'] = True

        # Register these links
        self.nmlbilink_nmlbiports_map[bilink.identifier] = (
            port_a.identifier, port_b.identifier
        )

    def post_build(self):
        """
        Ports are created for each node automatically while adding links.
        Creates the rest of the ports (no-linked ports)

        See :meth:`BasePlatform.post_build` for more information.
        """
        # Create remaining interfaces
        cmd_tpl = (
            'ip netns exec {container_ns} '
            'ip tuntap add dev {iface} mode tap'
        )
        for port_spec in self.nmlbiport_iface_map.values():

            # Ignore already created interfaces
            if port_spec['created']:
                continue

            # Create port as dummy tuntap device
            privileged_cmd(cmd_tpl, **port_spec)

            # Move port to network namespace if required
            if port_spec['netns']:
                enode = self.nmlnode_node_map[port_spec['owner']]
                enode._docker_exec(
                    'ip link set dev {iface} netns {netns}'.format(
                        **port_spec
                    )
                )

            # Mark as created
            port_spec['created'] = True

        # Notify nodes of the post_build event
        for enode in self.nmlnode_node_map.values():
            enode.notify_post_build()

    def destroy(self):
        """
        See :meth:`BasePlatform.destroy` for more information.
        """
        # NOTE: Implementation is split on purpose

        # Request termination of all containers
        for enode in self.nmlnode_node_map.values():
            try:
                enode.stop()
            except:
                log.error(format_exc())

        # Remove the linked netns
        for enode in self.nmlnode_node_map.values():
            try:
                privileged_cmd('rm /var/run/netns/{pid}', pid=enode._pid)
            except:
                log.error(format_exc())

        # Remove all docker-managed networks
        for enode in self.nmlnode_node_map.values():
            try:
                network_config = enode._get_network_config()['mapping']

                for category, config in network_config.items():
                    if config['managed_by'] == 'docker':

                        netname = '{}_{}'.format(
                            enode._container_name,
                            category
                        )

                        enode._client.remove_network(net_id=netname)
            except:
                log.error(format_exc())

    def rollback(self, stage, enodes, exception):
        """
        See :meth:`BasePlatform.rollback` for more information.
        """
        self.destroy()

    def _common_link(self, link_id, action):
        """
        Common action to relink / unlink.

        :param str link_id: Identifier of the link to modify.
        :param bool action: True if up, False if down.
        """
        if link_id not in self.nmlbilink_nmlbiports_map:
            raise Exception('Unknown link "{}"'.format(link_id))

        # iterate endpoints
        for port_id in self.nmlbilink_nmlbiports_map[link_id]:

            # Get specification for this endpoint
            port_spec = self.nmlbiport_iface_map[port_id]

            # Get node for the owner of this port
            enode = self.nmlnode_node_map[port_spec['owner']]
            enode.set_port_state(port_spec['label'], action)

    def relink(self, link_id):
        """
        See :meth:`BasePlatform.relink` for more information.
        """
        self._common_link(link_id, True)

    def unlink(self, link_id):
        """
        See :meth:`BasePlatform.unlink` for more information.
        """
        self._common_link(link_id, False)
Example #8
0
class ConnectPlatform(BasePlatform):
    """
    FIXME: Document.
    """

    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader(
            'connect', api_version='1.0', base_class=ConnectNode
        )

        self.nmlnode_node_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

    def pre_build(self):
        """
        See :meth:`BasePlatform.pre_build` for more information.
        """

    def add_node(self, node):
        """
        See :meth:`BasePlatform.add_node` for more information.
        """
        # Lookup for node of given type
        node_type = node.metadata.get('type', 'host')
        if node_type not in self.available_node_types:
            raise Exception('Unknown node type {}'.format(node_type))

        # Create instance of node type and start
        enode = self.available_node_types[node_type](
            node.identifier, **node.metadata
        )

        # Register node
        self.nmlnode_node_map[node.identifier] = enode

        # Start node
        enode.start()

        return enode

    def add_biport(self, node, biport):
        """
        See :meth:`BasePlatform.add_biport` for more information.
        """
        # FIXME: Save this port for later validation in post_build.
        return biport.identifier

    def add_bilink(self, nodeport_a, nodeport_b, bilink):
        """
        Add a link between two nodes.

        See :meth:`BasePlatform.add_bilink` for more information.
        """
        # FIXME: Save this link for later validation in post_build.

    def post_build(self):
        """
        See :meth:`BasePlatform.post_build` for more information.
        """
        # FIXME: Check that the final topology is the same as the known /
        # hardwired one.

    def destroy(self):
        """
        See :meth:`BasePlatform.destroy` for more information.
        """
        for enode in self.nmlnode_node_map.values():
            try:
                enode.stop()
            except:
                log.error(format_exc())

    def rollback(self, stage, enodes, exception):
        """
        See :meth:`BasePlatform.rollback` for more information.
        """
        log.info('Topology Connect rollback called...')
        self.destroy()

    def relink(self, link_id):
        """
        See :meth:`BasePlatform.relink` for more information.
        """
        raise RuntimeError(
            'relink is not currently supported in this Engine Platform.'
        )

    def unlink(self, link_id):
        """
        See :meth:`BasePlatform.unlink` for more information.
        """
        raise RuntimeError(
            'unlink is not currently supported in this Engine Platform.'
        )
Example #9
0
class DockerPlatform(BasePlatform):
    """
    Plugin to build a topology using Docker.

    See :class:`topology.platforms.platform.BasePlatform` for more information.
    """
    def __init__(self, timestamp, nmlmanager, **kwargs):

        self.node_loader = NodeLoader('docker',
                                      api_version='1.0',
                                      base_class=DockerNode)

        self.nmlnode_node_map = OrderedDict()
        self.nmlbiport_iface_map = OrderedDict()
        self.nmlbilink_nmlbiports_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

        # Create netns folder
        privileged_cmd('mkdir -p /var/run/netns')

    def pre_build(self):
        """
        See :meth:`BasePlatform.pre_build` for more information.
        """

    def add_node(self, node):
        """
        Add a new DockerNode.

        See :meth:`BasePlatform.add_node` for more information.
        """
        # Lookup for node of given type
        node_type = node.metadata.get('type', 'host')
        if node_type not in self.available_node_types:
            raise Exception('Unknown node type {}'.format(node_type))

        # Create instance of node type and start
        enode = self.available_node_types[node_type](node.identifier,
                                                     **node.metadata)

        # Register node
        self.nmlnode_node_map[node.identifier] = enode

        # Start node
        enode.start()

        # Install container netns locally
        privileged_cmd('ln -s /proc/{pid}/ns/net /var/run/netns/{pid}',
                       pid=enode._pid)

        # Manage network and their netns creation
        network_handlers = {
            'docker': create_docker_network,
            'platform': create_platform_network
        }

        for category, config in enode._get_network_config()['mapping'].items():

            managed_by = config['managed_by']
            handler = network_handlers.get(managed_by, None)

            if handler is None:
                raise RuntimeError(
                    'Unknown "managed_by" handler {}'.format(managed_by))

            handler(enode, category, config)

        return enode

    def add_biport(self, node, biport):
        """
        Add a port to the docker node.

        See :meth:`BasePlatform.add_biport` for more information.
        """
        enode = self.nmlnode_node_map[node.identifier]
        eport = enode.notify_add_biport(node, biport)

        # Get network configuration of given port
        network_config = enode._get_network_config()

        category = biport.metadata.get('category',
                                       network_config['default_category'])
        category_config = network_config['mapping'][category]

        # If this port is from a docker-managed network, then it will be
        # created when this node was connected to the network
        created = True if category_config['managed_by'] == 'docker' else False

        # Sanity check to make sure that this biport identifier is not
        # a duplicate
        if biport.identifier in self.nmlbiport_iface_map:
            raise ValueError('Biport identifier already used: {}'.format(
                biport.identifier))

        # Register this port for later creation
        self.nmlbiport_iface_map[biport.identifier] = {
            'created': created,
            'iface_base': eport,
            'prefix': category_config['prefix'],
            'iface': '{}{}'.format(category_config['prefix'], eport),
            'container_ns': enode._pid,
            'netns': category_config['netns'],
            'owner': node.identifier,
            'label': biport.metadata.get('label', biport.identifier)
        }

        return eport

    def add_bilink(self, nodeport_a, nodeport_b, bilink):
        """
        Add a link between two nodes.

        See :meth:`BasePlatform.add_bilink` for more information.
        """
        node_a, port_a = nodeport_a
        node_b, port_b = nodeport_b

        # Get enodes
        enode_a = self.nmlnode_node_map[node_a.identifier]
        enode_b = self.nmlnode_node_map[node_b.identifier]

        # Determine temporal interfaces names
        tmp_iface_a = tmp_iface()
        tmp_iface_b = tmp_iface()

        # Get port spec dictionary
        port_spec_a = self.nmlbiport_iface_map[port_a.identifier]
        port_spec_b = self.nmlbiport_iface_map[port_b.identifier]

        # If any of the ports is already created then don't do anything here
        # FIXME: We should probably let the user know if one of the ports is
        # created and the other is not, as this case is undefined and
        # unsupported
        if port_spec_a['created'] or port_spec_b['created']:
            return

        # Determine final interface names
        iface_a = port_spec_a['iface']
        iface_b = port_spec_b['iface']

        # Create links between nodes:
        #   docs.docker.com/v1.5/articles/networking/#building-a-point-to-point-connection # noqa
        commands = """\
        ip link add {tmp_iface_a} type veth peer name {tmp_iface_b}
        ip link set {tmp_iface_a} netns {enode_a._pid} name {iface_a}
        ip link set {tmp_iface_b} netns {enode_b._pid} name {iface_b}\
        """
        privileged_cmd(commands, **locals())

        # Apply some attributes to nodes and ports
        for enode, port, port_spec, iface in ((enode_a, port_a, port_spec_a,
                                               iface_a),
                                              (enode_b, port_b, port_spec_b,
                                               iface_b)):

            # Move interfaces to correct network namespace
            netns = port_spec['netns']
            if netns:
                enode._docker_exec(
                    'ip link set dev {iface} netns {netns}'.format(**locals()))
                cmd_prefix = 'ip netns exec {netns} '.format(**locals())
            else:
                cmd_prefix = ''

            # Set ipv4 and ipv6 addresses
            for version in [4, 6]:
                attribute = 'ipv{}'.format(version)
                if attribute not in port.metadata:
                    continue

                addr = port.metadata[attribute]
                cmd = ('{cmd_prefix}ip -{version} addr add {addr} dev {iface}'
                       ).format(**locals())
                enode._docker_exec(cmd)

            # Bring-up or down
            if bilink.metadata.get('up', None) is None and \
                    port.metadata.get('up', None) is None:
                continue

            up = bilink.metadata.get('up', True) and \
                port.metadata.get('up', True)

            state = 'up' if up else 'down'
            cmd = '{cmd_prefix}ip link set dev {iface} {state}'.format(
                **locals())
            enode._docker_exec(cmd)

        # Notify enodes of created interfaces
        enode_a.notify_add_bilink(nodeport_a, bilink)
        enode_b.notify_add_bilink(nodeport_b, bilink)

        # Mark interfaces as created
        self.nmlbiport_iface_map[port_a.identifier]['created'] = True
        self.nmlbiport_iface_map[port_b.identifier]['created'] = True

        # Register these links
        self.nmlbilink_nmlbiports_map[bilink.identifier] = (port_a.identifier,
                                                            port_b.identifier)

    def post_build(self):
        """
        Ports are created for each node automatically while adding links.
        Creates the rest of the ports (no-linked ports)

        See :meth:`BasePlatform.post_build` for more information.
        """
        # Create remaining interfaces
        cmd_tpl = ('ip netns exec {container_ns} '
                   'ip tuntap add dev {iface} mode tap')
        for port_spec in self.nmlbiport_iface_map.values():

            # Ignore already created interfaces
            if port_spec['created']:
                continue

            # Create port as dummy tuntap device
            privileged_cmd(cmd_tpl, **port_spec)

            # Move port to network namespace if required
            if port_spec['netns']:
                enode = self.nmlnode_node_map[port_spec['owner']]
                enode._docker_exec(
                    'ip link set dev {iface} netns {netns}'.format(
                        **port_spec))

            # Mark as created
            port_spec['created'] = True

        # Notify nodes of the post_build event
        for enode in self.nmlnode_node_map.values():
            enode.notify_post_build()

    def destroy(self):
        """
        See :meth:`BasePlatform.destroy` for more information.
        """
        # NOTE: Implementation is split on purpose

        # Request termination of all containers
        for enode in self.nmlnode_node_map.values():
            try:
                enode.stop()
            except Exception:
                log.error(format_exc())

        # Remove the linked netns
        for enode in self.nmlnode_node_map.values():
            try:
                privileged_cmd('rm /var/run/netns/{pid}', pid=enode._pid)
            except Exception:
                log.error(format_exc())

        # Save the names of all docker-managed networks
        networks_to_remove = set()
        for enode in self.nmlnode_node_map.values():
            try:
                network_config = enode._get_network_config()['mapping']

                for category, config in network_config.items():
                    if config['managed_by'] == 'docker':
                        netname = config.get('connect_to', None)
                        if netname is None:

                            netname = '{}_{}'.format(enode._container_name,
                                                     category)

                        networks_to_remove.add(netname)

            except Exception:
                log.error(format_exc())

        # Remove all docker-managed networks
        dockerclient = APIClient(version='auto')
        for netname in networks_to_remove:
            dockerclient.remove_network(net_id=netname)

    def rollback(self, stage, enodes, exception):
        """
        See :meth:`BasePlatform.rollback` for more information.
        """
        self.destroy()

    def _common_link(self, link_id, action):
        """
        Common action to relink / unlink.

        :param str link_id: Identifier of the link to modify.
        :param bool action: True if up, False if down.
        """
        if link_id not in self.nmlbilink_nmlbiports_map:
            raise Exception('Unknown link "{}"'.format(link_id))

        # iterate endpoints
        for port_id in self.nmlbilink_nmlbiports_map[link_id]:

            # Get specification for this endpoint
            port_spec = self.nmlbiport_iface_map[port_id]

            # Get node for the owner of this port
            enode = self.nmlnode_node_map[port_spec['owner']]
            enode.set_port_state(port_spec['label'], action)

    def relink(self, link_id):
        """
        See :meth:`BasePlatform.relink` for more information.
        """
        self._common_link(link_id, True)

    def unlink(self, link_id):
        """
        See :meth:`BasePlatform.unlink` for more information.
        """
        self._common_link(link_id, False)
Example #10
0
class DockerPlatform(BasePlatform):
    """
    Plugin to build a topology using Docker.

    See :class:`topology.platforms.base.BasePlatform` for more information.
    """
    def __init__(self, timestamp, nmlmanager):

        self.node_loader = NodeLoader('docker',
                                      api_version='1.0',
                                      base_class=DockerNode)

        self.nmlnode_node_map = OrderedDict()
        self.nmlbiport_iface_map = OrderedDict()
        self.nmlbilink_nmlbiports_map = OrderedDict()
        self.available_node_types = self.node_loader.load_nodes()

        # Create netns folder
        privileged_cmd('mkdir -p /var/run/netns')

    def pre_build(self):
        """
        See :meth:`BasePlatform.pre_build` for more information.
        """

    def add_node(self, node):
        """
        Add a new DockerNode.

        See :meth:`BasePlatform.add_node` for more information.
        """
        # Lookup for node of given type
        node_type = node.metadata.get('type', 'host')
        if node_type not in self.available_node_types:
            raise Exception('Unknown node type {}'.format(node_type))

        # Create instance of node type and start
        enode = self.available_node_types[node_type](node.identifier,
                                                     **node.metadata)

        # Register node
        self.nmlnode_node_map[node.identifier] = enode

        # Start node
        enode.start()

        # Install container netns locally
        privileged_cmd('ln -s /proc/{pid}/ns/net /var/run/netns/{pid}',
                       pid=enode._pid)

        return enode

    def add_biport(self, node, biport):
        """
        Add a port to the docker node.

        See :meth:`BasePlatform.add_biport` for more information.
        """
        enode = self.nmlnode_node_map[node.identifier]
        eport = enode.notify_add_biport(node, biport)

        # Register this port for later creation
        self.nmlbiport_iface_map[biport.identifier] = {
            'created': False,
            'iface': eport,
            'netns': enode._pid,
            'owner': node.identifier,
            'label': biport.metadata.get('label', biport.identifier)
        }

        return eport

    def add_bilink(self, nodeport_a, nodeport_b, bilink):
        """
        Add a link between two nodes.

        See :meth:`BasePlatform.add_bilink` for more information.
        """
        node_a, port_a = nodeport_a
        node_b, port_b = nodeport_b

        node_type_a = node_a.metadata.get('type', 'host')
        node_type_b = node_b.metadata.get('type', 'host')
        if node_type_a == 'oobmhost' or node_type_b == 'oobmhost':
            self.nmlbiport_iface_map[port_a.identifier]['created'] = True
            self.nmlbiport_iface_map[port_b.identifier]['created'] = True
            return

        # Get enodes
        enode_a = self.nmlnode_node_map[node_a.identifier]
        enode_b = self.nmlnode_node_map[node_b.identifier]

        # Determine temporal interfaces names
        tmp_iface_a = tmp_iface()
        tmp_iface_b = tmp_iface()

        # Determine final interface names
        iface_a = self.nmlbiport_iface_map[port_a.identifier]['iface']
        iface_b = self.nmlbiport_iface_map[port_b.identifier]['iface']

        # Create links between nodes:
        #   docs.docker.com/articles/networking/#building-a-point-to-point-connection # noqa
        commands = """\
        ip link add {tmp_iface_a} type veth peer name {tmp_iface_b}
        ip link set {tmp_iface_a} netns {enode_a._pid}
        ip link set {tmp_iface_b} netns {enode_b._pid}
        ip netns exec {enode_a._pid} ip link set {tmp_iface_a} name {iface_a}
        ip netns exec {enode_b._pid} ip link set {tmp_iface_b} name {iface_b}\
        """
        privileged_cmd(commands, **locals())

        # Notify enodes of created interfaces
        enode_a.notify_add_bilink(nodeport_a, bilink)
        enode_b.notify_add_bilink(nodeport_b, bilink)

        # Mark interfaces as created
        self.nmlbiport_iface_map[port_a.identifier]['created'] = True
        self.nmlbiport_iface_map[port_b.identifier]['created'] = True

        # Register this links
        self.nmlbilink_nmlbiports_map[bilink.identifier] = (port_a.identifier,
                                                            port_b.identifier)

        # Apply some attributes
        for enode, port, iface in \
                ((enode_a, port_a, iface_a), (enode_b, port_b, iface_b)):

            prefix = 'ip netns exec {pid} '.format(pid=enode._pid)

            # Set ipv4 and ipv6 addresses
            for version in [4, 6]:
                attribute = 'ipv{}'.format(version)
                if attribute not in port.metadata:
                    continue

                addr = port.metadata[attribute]
                cmd = 'ip -{version} addr add {addr} dev {iface}'.format(
                    **locals())
                privileged_cmd(prefix + cmd)

            # Bring-up or down
            if bilink.metadata.get('up', None) is None and \
                    port.metadata.get('up', None) is None:
                continue

            up = bilink.metadata.get('up', True) and \
                port.metadata.get('up', True)

            state = 'up' if up else 'down'
            cmd = 'ip link set dev {iface} {state}'.format(**locals())
            privileged_cmd(prefix + cmd)

    def post_build(self):
        """
        Ports are created for each node automatically while adding links.
        Creates the rest of the ports (no-linked ports)

        See :meth:`BasePlatform.post_build` for more information.
        """
        # Create remaining interfaces
        cmd_tpl = 'ip netns exec {netns} ip tuntap add dev {iface} mode tap'
        for port_spec in self.nmlbiport_iface_map.values():

            # Ignore already created interfaces
            if port_spec['created']:
                continue

            # Create port as dummy tuntap device
            privileged_cmd(cmd_tpl, **port_spec)

            # Mark as created
            port_spec['created'] = True

        # Notify nodes of the post_build event
        for enode in self.nmlnode_node_map.values():
            enode.notify_post_build()

    def destroy(self):
        """
        See :meth:`BasePlatform.destroy` for more information.
        """
        # NOTE: Implementation is split on purpose

        # Request termination of all containers
        for enode in self.nmlnode_node_map.values():
            try:
                enode.stop()
            except:
                log.error(format_exc())

        # Remove the linked netns
        for enode in self.nmlnode_node_map.values():
            try:
                privileged_cmd('rm /var/run/netns/{pid}', pid=enode._pid)
            except:
                log.error(format_exc())

    def rollback(self, stage, enodes, exception):
        """
        See :meth:`BasePlatform.rollback` for more information.
        """
        self.destroy()

    def _common_link(self, link_id, action):
        """
        Common action to relink / unlink.

        :param str link_id: Identifier of the link to modify.
        :param bool action: True if up, False if down.
        """
        if link_id not in self.nmlbilink_nmlbiports_map:
            raise Exception('Unknown link "{}"'.format(link_id))

        # iterate endpoints
        for port_id in self.nmlbilink_nmlbiports_map[link_id]:

            # Get specification for this endpoint
            port_spec = self.nmlbiport_iface_map[port_id]

            # Get node for the owner of this port
            enode = self.nmlnode_node_map[port_spec['owner']]
            enode.set_port_state(port_spec['label'], action)

    def relink(self, link_id):
        """
        See :meth:`BasePlatform.relink` for more information.
        """
        self._common_link(link_id, True)

    def unlink(self, link_id):
        """
        See :meth:`BasePlatform.unlink` for more information.
        """
        self._common_link(link_id, False)