daemon: initial pass to revamp how node linking and link management is done, provides a consistent way to link all wired nodes and allows them to be configured for tc for the same behavior across the board
This commit is contained in:
parent
d684b8eb5a
commit
cd7f1a641e
19 changed files with 1393 additions and 1556 deletions
256
daemon/core/emulator/links.py
Normal file
256
daemon/core/emulator/links.py
Normal file
|
@ -0,0 +1,256 @@
|
|||
"""
|
||||
Provides functionality for maintaining information about known links
|
||||
for a session.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional, Tuple, ValuesView
|
||||
|
||||
from core.emulator.data import LinkData, LinkOptions
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags
|
||||
from core.errors import CoreError
|
||||
from core.nodes.base import NodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import PtpNet
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
LinkKeyType = Tuple[int, Optional[int], int, Optional[int]]
|
||||
|
||||
|
||||
def create_key(
|
||||
node1: NodeBase,
|
||||
iface1: Optional[CoreInterface],
|
||||
node2: NodeBase,
|
||||
iface2: Optional[CoreInterface],
|
||||
) -> LinkKeyType:
|
||||
"""
|
||||
Creates a unique key for tracking links.
|
||||
|
||||
:param node1: first node in link
|
||||
:param iface1: node1 interface
|
||||
:param node2: second node in link
|
||||
:param iface2: node2 interface
|
||||
:return: link key
|
||||
"""
|
||||
iface1_id = iface1.id if iface1 else None
|
||||
iface2_id = iface2.id if iface2 else None
|
||||
if node1.id < node2.id:
|
||||
return node1.id, iface1_id, node2.id, iface2_id
|
||||
else:
|
||||
return node2.id, iface2_id, node1.id, iface1_id
|
||||
|
||||
|
||||
@dataclass
|
||||
class CoreLink:
|
||||
"""
|
||||
Provides a core link data structure.
|
||||
"""
|
||||
|
||||
node1: NodeBase
|
||||
iface1: Optional[CoreInterface]
|
||||
node2: NodeBase
|
||||
iface2: Optional[CoreInterface]
|
||||
ptp: PtpNet = None
|
||||
label: str = None
|
||||
color: str = None
|
||||
|
||||
def key(self) -> LinkKeyType:
|
||||
"""
|
||||
Retrieve the key for this link.
|
||||
|
||||
:return: link key
|
||||
"""
|
||||
return create_key(self.node1, self.iface1, self.node2, self.iface2)
|
||||
|
||||
def is_unidirectional(self) -> bool:
|
||||
"""
|
||||
Checks if this link is considered unidirectional, due to current
|
||||
iface configurations.
|
||||
|
||||
:return: True if unidirectional, False otherwise
|
||||
"""
|
||||
unidirectional = False
|
||||
if self.iface1 and self.iface2:
|
||||
unidirectional = self.iface1.options != self.iface2.options
|
||||
return unidirectional
|
||||
|
||||
def options(self) -> LinkOptions:
|
||||
"""
|
||||
Retrieve the options for this link.
|
||||
|
||||
:return: options for this link
|
||||
"""
|
||||
if self.is_unidirectional():
|
||||
options = self.iface1.options
|
||||
else:
|
||||
if self.iface1:
|
||||
options = self.iface1.options
|
||||
else:
|
||||
options = self.iface2.options
|
||||
return options
|
||||
|
||||
def get_data(self, message_type: MessageFlags, source: str = None) -> LinkData:
|
||||
"""
|
||||
Create link data for this link.
|
||||
|
||||
:param message_type: link data message type
|
||||
:param source: source for this data
|
||||
:return: link data
|
||||
"""
|
||||
iface1_data = self.iface1.get_data() if self.iface1 else None
|
||||
iface2_data = self.iface2.get_data() if self.iface2 else None
|
||||
return LinkData(
|
||||
message_type=message_type,
|
||||
type=LinkTypes.WIRED,
|
||||
node1_id=self.node1.id,
|
||||
node2_id=self.node2.id,
|
||||
iface1=iface1_data,
|
||||
iface2=iface2_data,
|
||||
options=self.options(),
|
||||
label=self.label,
|
||||
color=self.color,
|
||||
source=source,
|
||||
)
|
||||
|
||||
def get_data_unidirectional(self, source: str = None) -> LinkData:
|
||||
"""
|
||||
Create other unidirectional link data.
|
||||
|
||||
:param source: source for this data
|
||||
:return: unidirectional link data
|
||||
"""
|
||||
iface1_data = self.iface1.get_data() if self.iface1 else None
|
||||
iface2_data = self.iface2.get_data() if self.iface2 else None
|
||||
return LinkData(
|
||||
message_type=MessageFlags.NONE,
|
||||
type=LinkTypes.WIRED,
|
||||
node1_id=self.node2.id,
|
||||
node2_id=self.node1.id,
|
||||
iface1=iface2_data,
|
||||
iface2=iface1_data,
|
||||
options=self.iface2.options,
|
||||
label=self.label,
|
||||
color=self.color,
|
||||
source=source,
|
||||
)
|
||||
|
||||
|
||||
class LinkManager:
|
||||
"""
|
||||
Provides core link management.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Create a LinkManager instance.
|
||||
"""
|
||||
self._links: Dict[LinkKeyType, CoreLink] = {}
|
||||
self._node_links: Dict[int, Dict[LinkKeyType, CoreLink]] = {}
|
||||
|
||||
def add(self, core_link: CoreLink) -> None:
|
||||
"""
|
||||
Add a core link to be tracked.
|
||||
|
||||
:param core_link: link to track
|
||||
:return: nothing
|
||||
"""
|
||||
node1, iface1 = core_link.node1, core_link.iface1
|
||||
node2, iface2 = core_link.node2, core_link.iface2
|
||||
if core_link.key() in self._links:
|
||||
raise CoreError(
|
||||
f"node1({node1.name}) iface1({iface1.id}) "
|
||||
f"node2({node2.name}) iface2({iface2.id}) link already exists"
|
||||
)
|
||||
logger.info(
|
||||
"adding link from node(%s:%s) to node(%s:%s)",
|
||||
node1.name,
|
||||
iface1.name if iface1 else None,
|
||||
node2.name,
|
||||
iface2.name if iface2 else None,
|
||||
)
|
||||
self._links[core_link.key()] = core_link
|
||||
node1_links = self._node_links.setdefault(node1.id, {})
|
||||
node1_links[core_link.key()] = core_link
|
||||
node2_links = self._node_links.setdefault(node2.id, {})
|
||||
node2_links[core_link.key()] = core_link
|
||||
|
||||
def delete(
|
||||
self,
|
||||
node1: NodeBase,
|
||||
iface1: Optional[CoreInterface],
|
||||
node2: NodeBase,
|
||||
iface2: Optional[CoreInterface],
|
||||
) -> CoreLink:
|
||||
"""
|
||||
Remove a link from being tracked.
|
||||
|
||||
:param node1: first node in link
|
||||
:param iface1: node1 interface
|
||||
:param node2: second node in link
|
||||
:param iface2: node2 interface
|
||||
:return: removed core link
|
||||
"""
|
||||
key = create_key(node1, iface1, node2, iface2)
|
||||
if key not in self._links:
|
||||
raise CoreError(
|
||||
f"node1({node1.name}) iface1({iface1.id}) "
|
||||
f"node2({node2.name}) iface2({iface2.id}) is not linked"
|
||||
)
|
||||
logger.info(
|
||||
"deleting link from node(%s:%s) to node(%s:%s)",
|
||||
node1.name,
|
||||
iface1.name if iface1 else None,
|
||||
node2.name,
|
||||
iface2.name if iface2 else None,
|
||||
)
|
||||
node1_links = self._node_links[node1.id]
|
||||
node1_links.pop(key)
|
||||
node2_links = self._node_links[node2.id]
|
||||
node2_links.pop(key)
|
||||
return self._links.pop(key)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Resets and clears all tracking information.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self._links.clear()
|
||||
self._node_links.clear()
|
||||
|
||||
def get_link(
|
||||
self,
|
||||
node1: NodeBase,
|
||||
iface1: Optional[CoreInterface],
|
||||
node2: NodeBase,
|
||||
iface2: Optional[CoreInterface],
|
||||
) -> Optional[CoreLink]:
|
||||
"""
|
||||
Retrieve a link for provided values.
|
||||
|
||||
:param node1: first node in link
|
||||
:param iface1: interface for node1
|
||||
:param node2: second node in link
|
||||
:param iface2: interface for node2
|
||||
:return: core link if present, None otherwise
|
||||
"""
|
||||
key = create_key(node1, iface1, node2, iface2)
|
||||
return self._links.get(key)
|
||||
|
||||
def links(self) -> ValuesView[CoreLink]:
|
||||
"""
|
||||
Retrieve all known links
|
||||
|
||||
:return: iterator for all known links
|
||||
"""
|
||||
return self._links.values()
|
||||
|
||||
def node_links(self, node: NodeBase) -> ValuesView[CoreLink]:
|
||||
"""
|
||||
Retrieve all links for a given node.
|
||||
|
||||
:param node: node to get links for
|
||||
:return: node links
|
||||
"""
|
||||
return self._node_links.get(node.id, {}).values()
|
Loading…
Add table
Add a link
Reference in a new issue