Merge pull request #468 from coreemu/cleanup/session-add-link

daemon: refactored add_link,update_link,delete_link to have more spec…
This commit is contained in:
bharnden 2020-06-11 16:26:58 -07:00 committed by GitHub
commit f626564200
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 232 additions and 355 deletions

View file

@ -767,7 +767,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value),
)
link_type = None
link_type = LinkTypes.WIRED
link_type_value = message.get_tlv(LinkTlvs.TYPE.value)
if link_type_value is not None:
link_type = LinkTypes(link_type_value)

View file

@ -12,7 +12,7 @@ import subprocess
import tempfile
import threading
import time
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar
from core import constants, utils
from core.configservice.manager import ConfigServiceManager
@ -193,76 +193,28 @@ class Session:
raise CoreError(f"invalid node class: {_class}")
return node_type
def _link_nodes(
self, node_one_id: int, node_two_id: int
) -> Tuple[
Optional[CoreNode],
Optional[CoreNode],
Optional[CoreNetworkBase],
Optional[CoreNetworkBase],
]:
"""
Convenience method for retrieving nodes within link data.
:param node_one_id: node one id
:param node_two_id: node two id
:return: nodes, network nodes if present, and tunnel if present
"""
logging.debug(
"link message between node1(%s) and node2(%s)", node_one_id, node_two_id
)
# values to fill
net_one = None
net_two = None
# retrieve node one
node_one = self.get_node(node_one_id, NodeBase)
node_two = self.get_node(node_two_id, NodeBase)
if isinstance(node_one, CoreNetworkBase):
if not net_one:
net_one = node_one
else:
net_two = node_one
node_one = None
if isinstance(node_two, CoreNetworkBase):
if not net_one:
net_one = node_two
else:
net_two = node_two
node_two = None
logging.debug(
"link node types n1(%s) n2(%s) net1(%s) net2(%s)",
node_one,
node_two,
net_one,
net_two,
)
return node_one, node_two, net_one, net_two
def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None:
def _link_wireless(
self, node_one: CoreNodeBase, node_two: CoreNodeBase, connect: bool
) -> None:
"""
Objects to deal with when connecting/disconnecting wireless links.
:param objects: possible objects to deal with
:param node_one: node one for wireless link
:param node_two: node two for wireless link
:param connect: link interfaces if True, unlink otherwise
:return: nothing
:raises core.CoreError: when objects to link is less than 2, or no common
networks are found
"""
objects = [x for x in objects if x]
if len(objects) < 2:
raise CoreError(f"wireless link failure: {objects}")
logging.debug(
"handling wireless linking objects(%s) connect(%s)", objects, connect
logging.info(
"handling wireless linking node1(%s) node2(%s): %s",
node_one.name,
node_two.name,
connect,
)
common_networks = objects[0].commonnets(objects[1])
common_networks = node_one.commonnets(node_one)
if not common_networks:
raise CoreError("no common network found for wireless link/unlink")
for common_network, interface_one, interface_two in common_networks:
if not isinstance(common_network, (WlanNode, EmaneNet)):
logging.info(
@ -270,13 +222,6 @@ class Session:
common_network,
)
continue
logging.info(
"wireless linking connect(%s): %s - %s",
connect,
interface_one,
interface_two,
)
if connect:
common_network.link(interface_one, interface_two)
else:
@ -305,105 +250,70 @@ class Session:
"""
if not options:
options = LinkOptions()
node1 = self.get_node(node_one_id, NodeBase)
node2 = self.get_node(node_two_id, NodeBase)
node1_interface = None
node2_interface = None
# get node objects identified by link data
node_one, node_two, net_one, net_two = self._link_nodes(
node_one_id, node_two_id
)
if node_one:
node_one.lock.acquire()
if node_two:
node_two.lock.acquire()
node_one_interface = None
node_two_interface = None
try:
# wireless link
if options.type == LinkTypes.WIRELESS:
objects = [node_one, node_two, net_one, net_two]
self._link_wireless(objects, connect=True)
# wired link
# wireless link
if options.type == LinkTypes.WIRELESS:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
self._link_wireless(node1, node2, connect=True)
else:
# 2 nodes being linked, ptp network
if all([node_one, node_two]) and not net_one:
logging.info(
"adding link for peer to peer nodes: %s - %s",
node_one.name,
node_two.name,
)
start = self.state.should_start()
net_one = self.create_node(PtpNet, start=start)
# node to network
if node_one and net_one:
logging.info(
"adding link from node to network: %s - %s",
node_one.name,
net_one.name,
)
ifindex = node_one.newnetif(net_one, interface_one)
node_one_interface = node_one.netif(ifindex)
wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not wireless_net:
net_one.linkconfig(node_one_interface, options)
# network to node
if node_two and net_one:
logging.info(
"adding link from network to node: %s - %s",
node_two.name,
net_one.name,
)
ifindex = node_two.newnetif(net_one, interface_two)
node_two_interface = node_two.netif(ifindex)
wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not options.unidirectional and not wireless_net:
net_one.linkconfig(node_two_interface, options)
# network to network
if net_one and net_two:
logging.info(
"adding link from network to network: %s - %s",
net_one.name,
net_two.name,
)
interface = net_one.linknet(net_two)
node_one_interface = interface
net_one.linkconfig(interface, options)
if not options.unidirectional:
interface.swapparams("_params_up")
net_two.linkconfig(interface, options)
interface.swapparams("_params_up")
# a tunnel node was found for the nodes
addresses = []
if not node_one and all([net_one, interface_one]):
addresses.extend(interface_one.get_addresses())
if not node_two and all([net_two, interface_two]):
addresses.extend(interface_two.get_addresses())
# tunnel node logic
key = options.key
if key and isinstance(net_one, TunnelNode):
logging.info("setting tunnel key for: %s", net_one.name)
net_one.setkey(key)
if addresses:
net_one.addrconfig(addresses)
if key and isinstance(net_two, TunnelNode):
logging.info("setting tunnel key for: %s", net_two.name)
net_two.setkey(key)
if addresses:
net_two.addrconfig(addresses)
finally:
if node_one:
node_one.lock.release()
if node_two:
node_two.lock.release()
raise CoreError(
f"cannot wireless link node1({type(node1)}) node2({type(node2)})"
)
# wired link
else:
# peer to peer link
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
logging.info("linking ptp: %s - %s", node1.name, node2.name)
start = self.state.should_start()
ptp = self.create_node(PtpNet, start=start)
node1_interface = node1.newnetif(ptp, interface_one)
node2_interface = node2.newnetif(ptp, interface_two)
ptp.linkconfig(node1_interface, options)
if not options.unidirectional:
ptp.linkconfig(node2_interface, options)
# link node to net
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
node1_interface = node1.newnetif(node2, interface_one)
if not isinstance(node2, (EmaneNet, WlanNode)):
node2.linkconfig(node1_interface, options)
# link net to node
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
node2_interface = node2.newnetif(node1, interface_two)
wireless_net = isinstance(node1, (EmaneNet, WlanNode))
if not options.unidirectional and not wireless_net:
node1.linkconfig(node2_interface, options)
# network to network
elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase
):
logging.info(
"linking network to network: %s - %s", node1.name, node2.name
)
node1_interface = node1.linknet(node2)
node1.linkconfig(node1_interface, options)
if not options.unidirectional:
node1_interface.swapparams("_params_up")
node2.linkconfig(node1_interface, options)
node1_interface.swapparams("_params_up")
else:
raise CoreError(
f"cannot link node1({type(node1)}) node2({type(node2)})"
)
# configure tunnel nodes
key = options.key
if isinstance(node1, TunnelNode):
logging.info("setting tunnel key for: %s", node1.name)
node1.setkey(key, interface_one)
if isinstance(node2, TunnelNode):
logging.info("setting tunnel key for: %s", node2.name)
node2.setkey(key, interface_two)
self.sdt.add_link(node_one_id, node_two_id)
return node_one_interface, node_two_interface
return node1_interface, node2_interface
def delete_link(
self,
@ -424,93 +334,52 @@ class Session:
:return: nothing
:raises core.CoreError: when no common network is found for link being deleted
"""
# get node objects identified by link data
node_one, node_two, net_one, net_two = self._link_nodes(
node_one_id, node_two_id
node1 = self.get_node(node_one_id, NodeBase)
node2 = self.get_node(node_two_id, NodeBase)
logging.info(
"deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)",
link_type.name,
node1.name,
interface_one_id,
node2.name,
interface_two_id,
)
if node_one:
node_one.lock.acquire()
if node_two:
node_two.lock.acquire()
try:
# wireless link
if link_type == LinkTypes.WIRELESS:
objects = [node_one, node_two, net_one, net_two]
self._link_wireless(objects, connect=False)
# wired link
# wireless link
if link_type == LinkTypes.WIRELESS:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
self._link_wireless(node1, node2, connect=False)
else:
if all([node_one, node_two]):
# TODO: fix this for the case where ifindex[1,2] are not specified
# a wired unlink event, delete the connecting bridge
interface_one = node_one.netif(interface_one_id)
interface_two = node_two.netif(interface_two_id)
# get interfaces from common network, if no network node
# otherwise get interfaces between a node and network
if not interface_one and not interface_two:
common_networks = node_one.commonnets(node_two)
for (
network,
common_interface_one,
common_interface_two,
) in common_networks:
if (net_one and network == net_one) or not net_one:
interface_one = common_interface_one
interface_two = common_interface_two
break
if all([interface_one, interface_two]) and any(
[interface_one.net, interface_two.net]
):
if interface_one.net != interface_two.net and all(
[interface_one.up, interface_two.up]
):
raise CoreError("no common network found")
logging.info(
"deleting link node(%s):interface(%s) node(%s):interface(%s)",
node_one.name,
interface_one.name,
node_two.name,
interface_two.name,
)
net_one = interface_one.net
interface_one.detachnet()
interface_two.detachnet()
if net_one.numnetif() == 0:
self.delete_node(net_one.id)
node_one.delnetif(interface_one.netindex)
node_two.delnetif(interface_two.netindex)
elif node_one and net_one:
interface = node_one.netif(interface_one_id)
if interface:
logging.info(
"deleting link node(%s):interface(%s) node(%s)",
node_one.name,
interface.name,
net_one.name,
)
interface.detachnet()
node_one.delnetif(interface.netindex)
elif node_two and net_one:
interface = node_two.netif(interface_two_id)
if interface:
logging.info(
"deleting link node(%s):interface(%s) node(%s)",
node_two.name,
interface.name,
net_one.name,
)
interface.detachnet()
node_two.delnetif(interface.netindex)
finally:
if node_one:
node_one.lock.release()
if node_two:
node_two.lock.release()
raise CoreError(
"cannot delete wireless link "
f"node1({type(node1)}) node2({type(node2)})"
)
# wired link
else:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
interface1 = node1.netif(interface_one_id)
interface2 = node2.netif(interface_two_id)
if not interface1:
raise CoreError(
f"node({node1.name}) missing interface({interface_one_id})"
)
if not interface2:
raise CoreError(
f"node({node2.name}) missing interface({interface_two_id})"
)
if interface1.net != interface2.net:
raise CoreError(
f"node1({node1.name}) node2({node2.name}) "
"not connected to same net"
)
ptp = interface1.net
node1.delnetif(interface_one_id)
node2.delnetif(interface_two_id)
self.delete_node(ptp.id)
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
node1.delnetif(interface_one_id)
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
node2.delnetif(interface_two_id)
self.sdt.delete_link(node_one_id, node_two_id)
def update_link(
@ -530,84 +399,79 @@ class Session:
:param interface_two_id: interface id for node two
:param options: data to update link with
:return: nothing
:raises core.CoreError: when updating a wireless type link, when there is a unknown
link between networks
:raises core.CoreError: when updating a wireless type link, when there is a
unknown link between networks
"""
if not options:
options = LinkOptions()
# get node objects identified by link data
node_one, node_two, net_one, net_two = self._link_nodes(
node_one_id, node_two_id
node1 = self.get_node(node_one_id, NodeBase)
node2 = self.get_node(node_two_id, NodeBase)
logging.info(
"update link(%s) node(%s):interface(%s) node(%s):interface(%s)",
options.type.name,
node1.name,
interface_one_id,
node2.name,
interface_two_id,
)
if node_one:
node_one.lock.acquire()
if node_two:
node_two.lock.acquire()
try:
# wireless link
if options.type == LinkTypes.WIRELESS:
raise CoreError("cannot update wireless link")
else:
if not node_one and not node_two:
if net_one and net_two:
# modify link between nets
interface = net_one.getlinknetif(net_two)
upstream = False
if not interface:
upstream = True
interface = net_two.getlinknetif(net_one)
if not interface:
raise CoreError("modify unknown link between nets")
if upstream:
interface.swapparams("_params_up")
net_one.linkconfig(interface, options)
interface.swapparams("_params_up")
else:
net_one.linkconfig(interface, options)
if not options.unidirectional:
if upstream:
net_two.linkconfig(interface, options)
else:
interface.swapparams("_params_up")
net_two.linkconfig(interface, options)
interface.swapparams("_params_up")
else:
raise CoreError("modify link for unknown nodes")
elif not node_one:
# node1 = layer 2node, node2 = layer3 node
interface = node_two.netif(interface_two_id)
net_one.linkconfig(interface, options)
elif not node_two:
# node2 = layer 2node, node1 = layer3 node
interface = node_one.netif(interface_one_id)
net_one.linkconfig(interface, options)
# wireless link
if options.type == LinkTypes.WIRELESS:
raise CoreError("cannot update wireless link")
else:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
interface1 = node1.netif(interface_one_id)
interface2 = node2.netif(interface_two_id)
if not interface1:
raise CoreError(
f"node({node1.name}) missing interface({interface_one_id})"
)
if not interface2:
raise CoreError(
f"node({node2.name}) missing interface({interface_two_id})"
)
if interface1.net != interface2.net:
raise CoreError(
f"node1({node1.name}) node2({node2.name}) "
"not connected to same net"
)
ptp = interface1.net
ptp.linkconfig(interface1, options, interface2)
if not options.unidirectional:
ptp.linkconfig(interface2, options, interface1)
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
interface = node1.netif(interface_one_id)
node2.linkconfig(interface, options)
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
interface = node2.netif(interface_two_id)
node1.linkconfig(interface, options)
elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase
):
interface = node1.getlinknetif(node2)
upstream = False
if not interface:
upstream = True
interface = node2.getlinknetif(node1)
if not interface:
raise CoreError("modify unknown link between nets")
if upstream:
interface.swapparams("_params_up")
node1.linkconfig(interface, options)
interface.swapparams("_params_up")
else:
common_networks = node_one.commonnets(node_two)
if not common_networks:
raise CoreError("no common network found")
for net_one, interface_one, interface_two in common_networks:
if (
interface_one_id is not None
and interface_one_id != node_one.getifindex(interface_one)
):
continue
net_one.linkconfig(interface_one, options, interface_two)
if not options.unidirectional:
net_one.linkconfig(interface_two, options, interface_one)
finally:
if node_one:
node_one.lock.release()
if node_two:
node_two.lock.release()
node1.linkconfig(interface, options)
if not options.unidirectional:
if upstream:
node2.linkconfig(interface, options)
else:
interface.swapparams("_params_up")
node2.linkconfig(interface, options)
interface.swapparams("_params_up")
else:
raise CoreError(
f"cannot update link node1({type(node1)}) node2({type(node2)})"
)
def _next_node_id(self) -> int:
"""
@ -1345,17 +1209,15 @@ class Session:
:return: True if node deleted, False otherwise
"""
# delete node and check for session shutdown if a node was removed
logging.info("deleting node(%s)", _id)
node = None
with self._nodes_lock:
if _id in self.nodes:
node = self.nodes.pop(_id)
logging.info("deleted node(%s)", node.name)
if node:
node.shutdown()
self.sdt.delete_node(_id)
self.check_shutdown()
return node is not None
def delete_nodes(self) -> None:
@ -1767,15 +1629,15 @@ class Session:
try:
ip4 = control_net.prefix[node.id]
ip4_mask = control_net.prefix.prefixlen
interface = InterfaceData(
interface_data = InterfaceData(
id=control_net.CTRLIF_IDX_BASE + net_index,
name=f"ctrl{net_index}",
mac=utils.random_mac(),
ip4=ip4,
ip4_mask=ip4_mask,
)
ifindex = node.newnetif(control_net, interface)
node.netif(ifindex).control = True
interface = node.newnetif(control_net, interface_data)
interface.control = True
except ValueError:
msg = f"Control interface not added to node {node.id}. "
msg += f"Invalid control network prefix ({control_net.prefix}). "

View file

@ -355,10 +355,11 @@ class CoreNodeBase(NodeBase):
:return: nothing
"""
if ifindex not in self._netif:
raise ValueError(f"ifindex {ifindex} does not exist")
raise CoreError(f"node({self.name}) ifindex({ifindex}) does not exist")
netif = self._netif.pop(ifindex)
logging.info("node(%s) removing interface(%s)", self.name, netif.name)
netif.detachnet()
netif.shutdown()
del netif
def netif(self, ifindex: int) -> Optional[CoreInterface]:
"""
@ -473,6 +474,18 @@ class CoreNodeBase(NodeBase):
"""
raise NotImplementedError
def newnetif(
self, net: "CoreNetworkBase", interface: InterfaceData
) -> CoreInterface:
"""
Create a new network interface.
:param net: network to associate with
:param interface: interface data for new interface
:return: interface index
"""
raise NotImplementedError
class CoreNode(CoreNodeBase):
"""
@ -846,7 +859,9 @@ class CoreNode(CoreNodeBase):
interface_name = self.ifname(ifindex)
self.node_net_client.device_up(interface_name)
def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int:
def newnetif(
self, net: "CoreNetworkBase", interface: InterfaceData
) -> CoreInterface:
"""
Create a new network interface.
@ -868,16 +883,16 @@ class CoreNode(CoreNodeBase):
netif.sethwaddr(interface.mac)
for address in addresses:
netif.addaddr(address)
return ifindex
else:
ifindex = self.newveth(interface.id, interface.name)
self.attachnet(ifindex, net)
if interface.mac:
self.sethwaddr(ifindex, interface.mac)
for address in addresses:
self.addaddr(ifindex, address)
self.ifup(ifindex)
return ifindex
self.attachnet(ifindex, net)
if interface.mac:
self.sethwaddr(ifindex, interface.mac)
for address in addresses:
self.addaddr(ifindex, address)
self.ifup(ifindex)
netif = self.netif(ifindex)
return netif
def addfile(self, srcname: str, filename: str) -> None:
"""

View file

@ -12,7 +12,7 @@ import netaddr
from core import utils
from core.constants import EBTABLES_BIN, TC_BIN
from core.emulator.data import LinkData, NodeData
from core.emulator.emudata import LinkOptions
from core.emulator.emudata import InterfaceData, LinkOptions
from core.emulator.enumerations import (
LinkTypes,
MessageFlags,
@ -697,15 +697,19 @@ class GreTapBridge(CoreNetwork):
)
self.attach(self.gretap)
def setkey(self, key: int) -> None:
def setkey(self, key: int, interface_data: InterfaceData) -> None:
"""
Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
:param key: gre key
:param interface_data: interface data for setting up tunnel key
:return: nothing
"""
self.grekey = key
addresses = interface_data.get_addresses()
if addresses:
self.addrconfig(addresses)
class CtrlNet(CoreNetwork):

View file

@ -157,7 +157,7 @@ class PhysicalNode(CoreNodeBase):
self.ifindex += 1
return ifindex
def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int:
def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface:
logging.info("creating interface")
addresses = interface.get_addresses()
ifindex = interface.id
@ -171,12 +171,12 @@ class PhysicalNode(CoreNodeBase):
# tunnel to net not built yet, so build it now and adopt it
_, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server)
self.adoptnetif(remote_tap, ifindex, interface.mac, addresses)
return ifindex
return remote_tap
else:
# this is reached when configuring services (self.up=False)
netif = GreTap(node=self, name=name, session=self.session, start=False)
self.adoptnetif(netif, ifindex, interface.mac, addresses)
return ifindex
return netif
def privatedir(self, path: str) -> None:
if path[0] != "/":
@ -297,7 +297,7 @@ class Rj45Node(CoreNodeBase):
self.up = False
self.restorestate()
def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int:
def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface:
"""
This is called when linking with another node. Since this node
represents an interface, we do not create another object here,
@ -320,7 +320,7 @@ class Rj45Node(CoreNodeBase):
self.interface.attachnet(net)
for addr in interface.get_addresses():
self.addaddr(addr)
return ifindex
return self.interface
def delnetif(self, ifindex: int) -> None:
"""

View file

@ -54,12 +54,11 @@ class TestNodes:
node = session.add_node(CoreNode)
switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
interface = node.netif(index)
interface = node.newnetif(switch, interface_data)
mac = "aa:aa:aa:ff:ff:ff"
# when
node.sethwaddr(index, mac)
node.sethwaddr(interface.netindex, mac)
# then
assert interface.hwaddr == mac
@ -69,25 +68,23 @@ class TestNodes:
node = session.add_node(CoreNode)
switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
node.netif(index)
interface = node.newnetif(switch, interface_data)
mac = "aa:aa:aa:ff:ff:fff"
# when
with pytest.raises(CoreError):
node.sethwaddr(index, mac)
node.sethwaddr(interface.netindex, mac)
def test_node_addaddr(self, session: Session):
# given
node = session.add_node(CoreNode)
switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
interface = node.netif(index)
interface = node.newnetif(switch, interface_data)
addr = "192.168.0.1/24"
# when
node.addaddr(index, addr)
node.addaddr(interface.netindex, addr)
# then
assert interface.addrlist[0] == addr
@ -97,13 +94,12 @@ class TestNodes:
node = session.add_node(CoreNode)
switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
node.netif(index)
interface = node.newnetif(switch, interface_data)
addr = "256.168.0.1/24"
# when
with pytest.raises(CoreError):
node.addaddr(index, addr)
node.addaddr(interface.netindex, addr)
@pytest.mark.parametrize("net_type", NET_TYPES)
def test_net(self, session, net_type):