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, **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 __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')
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)
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)
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.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)
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)