Merge pull request #677 from coreemu/enhancement/consistent-linking

Enhancement/consistent linking
This commit is contained in:
bharnden 2022-03-22 09:47:29 -07:00 committed by GitHub
commit e3466f0669
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1699 additions and 1873 deletions

View file

@ -16,7 +16,7 @@ from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsRequest,
GetNodeConfigServiceRequest,
)
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest, LinkedRequest
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
GetEmaneEventChannelRequest,
@ -1049,6 +1049,36 @@ class CoreGrpcClient:
"""
self.stub.EmanePathlosses(streamer.iter())
def linked(
self,
session_id: int,
node1_id: int,
node2_id: int,
iface1_id: int,
iface2_id: int,
linked: bool,
) -> None:
"""
Link or unlink an existing core wired link.
:param session_id: session containing the link
:param node1_id: first node in link
:param node2_id: second node in link
:param iface1_id: node1 interface
:param iface2_id: node2 interface
:param linked: True to connect link, False to disconnect
:return: nothing
"""
request = LinkedRequest(
session_id=session_id,
node1_id=node1_id,
node2_id=node2_id,
iface1_id=iface1_id,
iface2_id=iface2_id,
linked=linked,
)
self.stub.Linked(request)
def connect(self) -> None:
"""
Open connection to server, must be closed manually.

View file

@ -3,7 +3,7 @@ from queue import Empty, Queue
from typing import Iterable, Optional
from core.api.grpc import core_pb2
from core.api.grpc.grpcutils import convert_link
from core.api.grpc.grpcutils import convert_link_data
from core.emulator.data import (
ConfigData,
EventData,
@ -51,7 +51,7 @@ def handle_link_event(link_data: LinkData) -> core_pb2.Event:
:param link_data: link data
:return: link event that has message type and link information
"""
link = convert_link(link_data)
link = convert_link_data(link_data)
message_type = link_data.message_type.value
link_event = core_pb2.LinkEvent(message_type=message_type, link=link)
return core_pb2.Event(link_event=link_event, source=link_data.source)

View file

@ -1,7 +1,7 @@
import logging
import time
from pathlib import Path
from typing import Any, Dict, List, Tuple, Type, Union
from typing import Any, Dict, List, Optional, Tuple, Type, Union
import grpc
from grpc import ServicerContext
@ -20,6 +20,7 @@ from core.config import ConfigurableOptions
from core.emane.nodes import EmaneNet
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.emulator.links import CoreLink
from core.emulator.session import Session
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
@ -27,7 +28,7 @@ from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.interface import CoreInterface
from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet, PtpNet, WlanNode
from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode
from core.services.coreservices import CoreService
logger = logging.getLogger(__name__)
@ -110,7 +111,7 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
def add_link_data(
link_proto: core_pb2.Link
) -> Tuple[InterfaceData, InterfaceData, LinkOptions, LinkTypes]:
) -> Tuple[InterfaceData, InterfaceData, LinkOptions]:
"""
Convert link proto to link interfaces and options data.
@ -119,7 +120,6 @@ def add_link_data(
"""
iface1_data = link_iface(link_proto.iface1)
iface2_data = link_iface(link_proto.iface2)
link_type = LinkTypes(link_proto.type)
options = LinkOptions()
options_proto = link_proto.options
if options_proto:
@ -134,7 +134,7 @@ def add_link_data(
options.buffer = options_proto.buffer
options.unidirectional = options_proto.unidirectional
options.key = options_proto.key
return iface1_data, iface2_data, options, link_type
return iface1_data, iface2_data, options
def create_nodes(
@ -174,8 +174,8 @@ def create_links(
for link_proto in link_protos:
node1_id = link_proto.node1_id
node2_id = link_proto.node2_id
iface1, iface2, options, link_type = add_link_data(link_proto)
args = (node1_id, node2_id, iface1, iface2, options, link_type)
iface1, iface2, options = add_link_data(link_proto)
args = (node1_id, node2_id, iface1, iface2, options)
funcs.append((session.add_link, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
@ -198,8 +198,8 @@ def edit_links(
for link_proto in link_protos:
node1_id = link_proto.node1_id
node2_id = link_proto.node2_id
iface1, iface2, options, link_type = add_link_data(link_proto)
args = (node1_id, node2_id, iface1.id, iface2.id, options, link_type)
iface1, iface2, options = add_link_data(link_proto)
args = (node1_id, node2_id, iface1.id, iface2.id, options)
funcs.append((session.update_link, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
@ -344,61 +344,84 @@ def get_node_proto(
)
def get_links(node: NodeBase):
def get_links(session: Session, node: NodeBase) -> List[core_pb2.Link]:
"""
Retrieve a list of links for grpc to use.
:param session: session to get links for node
:param node: node to get links from
:return: protobuf links
"""
link_protos = []
for core_link in session.link_manager.node_links(node):
link_protos.extend(convert_core_link(core_link))
if isinstance(node, (WlanNode, EmaneNet)):
for link_data in node.links():
link_protos.append(convert_link_data(link_data))
return link_protos
def convert_iface(iface: CoreInterface) -> core_pb2.Interface:
"""
Convert interface to protobuf.
:param iface: interface to convert
:return: protobuf interface
"""
if isinstance(iface.node, CoreNetwork):
return core_pb2.Interface(id=iface.id)
else:
ip4 = iface.get_ip4()
ip4_mask = ip4.prefixlen if ip4 else None
ip4 = str(ip4.ip) if ip4 else None
ip6 = iface.get_ip6()
ip6_mask = ip6.prefixlen if ip6 else None
ip6 = str(ip6.ip) if ip6 else None
mac = str(iface.mac) if iface.mac else None
return core_pb2.Interface(
id=iface.id,
name=iface.name,
mac=mac,
ip4=ip4,
ip4_mask=ip4_mask,
ip6=ip6,
ip6_mask=ip6_mask,
)
def convert_core_link(core_link: CoreLink) -> List[core_pb2.Link]:
"""
Convert core link to protobuf data.
:param core_link: core link to convert
:return: protobuf link data
"""
links = []
for link in node.links():
link_proto = convert_link(link)
links.append(link_proto)
node1, iface1 = core_link.node1, core_link.iface1
node2, iface2 = core_link.node2, core_link.iface2
unidirectional = core_link.is_unidirectional()
link = convert_link(node1, iface1, node2, iface2, iface1.options, unidirectional)
links.append(link)
if unidirectional:
link = convert_link(
node2, iface2, node1, iface1, iface2.options, unidirectional
)
links.append(link)
return links
def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface:
return core_pb2.Interface(
id=iface_data.id,
name=iface_data.name,
mac=iface_data.mac,
ip4=iface_data.ip4,
ip4_mask=iface_data.ip4_mask,
ip6=iface_data.ip6,
ip6_mask=iface_data.ip6_mask,
)
def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions:
return core_pb2.LinkOptions(
jitter=options_data.jitter,
key=options_data.key,
mburst=options_data.mburst,
mer=options_data.mer,
loss=options_data.loss,
bandwidth=options_data.bandwidth,
burst=options_data.burst,
delay=options_data.delay,
dup=options_data.dup,
buffer=options_data.buffer,
unidirectional=options_data.unidirectional,
)
def convert_link(link_data: LinkData) -> core_pb2.Link:
def convert_link_data(link_data: LinkData) -> core_pb2.Link:
"""
Convert link_data into core protobuf link.
:param link_data: link to convert
:return: core protobuf Link
"""
iface1 = None
if link_data.iface1 is not None:
iface1 = convert_iface(link_data.iface1)
iface1 = convert_iface_data(link_data.iface1)
iface2 = None
if link_data.iface2 is not None:
iface2 = convert_iface(link_data.iface2)
iface2 = convert_iface_data(link_data.iface2)
options = convert_link_options(link_data.options)
return core_pb2.Link(
type=link_data.type.value,
@ -413,6 +436,89 @@ def convert_link(link_data: LinkData) -> core_pb2.Link:
)
def convert_iface_data(iface_data: InterfaceData) -> core_pb2.Interface:
"""
Convert interface data to protobuf.
:param iface_data: interface data to convert
:return: interface protobuf
"""
return core_pb2.Interface(
id=iface_data.id,
name=iface_data.name,
mac=iface_data.mac,
ip4=iface_data.ip4,
ip4_mask=iface_data.ip4_mask,
ip6=iface_data.ip6,
ip6_mask=iface_data.ip6_mask,
)
def convert_link_options(options: LinkOptions) -> core_pb2.LinkOptions:
"""
Convert link options to protobuf.
:param options: link options to convert
:return: link options protobuf
"""
return core_pb2.LinkOptions(
jitter=options.jitter,
key=options.key,
mburst=options.mburst,
mer=options.mer,
loss=options.loss,
bandwidth=options.bandwidth,
burst=options.burst,
delay=options.delay,
dup=options.dup,
buffer=options.buffer,
unidirectional=options.unidirectional,
)
def convert_link(
node1: NodeBase,
iface1: Optional[CoreInterface],
node2: NodeBase,
iface2: Optional[CoreInterface],
options: LinkOptions,
unidirectional: bool,
) -> core_pb2.Link:
"""
Convert link objects to link protobuf.
:param node1: first node in link
:param iface1: node1 interface
:param node2: second node in link
:param iface2: node2 interface
:param options: link options
:param unidirectional: if this link is considered unidirectional
:return: protobuf link
"""
if iface1 is not None:
iface1 = convert_iface(iface1)
if iface2 is not None:
iface2 = convert_iface(iface2)
is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet))
is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet))
if not (is_node1_wireless or is_node2_wireless):
options = convert_link_options(options)
options.unidirectional = unidirectional
else:
options = None
return core_pb2.Link(
type=LinkTypes.WIRED.value,
node1_id=node1.id,
node2_id=node2.id,
iface1=iface1,
iface2=iface2,
options=options,
network_id=None,
label=None,
color=None,
)
def get_net_stats() -> Dict[str, Dict]:
"""
Retrieve status about the current interfaces in the system
@ -490,39 +596,13 @@ def get_service_configuration(service: CoreService) -> NodeServiceData:
)
def iface_to_data(iface: CoreInterface) -> InterfaceData:
ip4 = iface.get_ip4()
ip4_addr = str(ip4.ip) if ip4 else None
ip4_mask = ip4.prefixlen if ip4 else None
ip6 = iface.get_ip6()
ip6_addr = str(ip6.ip) if ip6 else None
ip6_mask = ip6.prefixlen if ip6 else None
return InterfaceData(
id=iface.node_id,
name=iface.name,
mac=str(iface.mac),
ip4=ip4_addr,
ip4_mask=ip4_mask,
ip6=ip6_addr,
ip6_mask=ip6_mask,
)
def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface:
"""
Convenience for converting a core interface to the protobuf representation.
:param node_id: id of node to convert interface for
:param iface: interface to convert
:return: interface proto
"""
if iface.node and iface.node.id == node_id:
_id = iface.node_id
else:
_id = iface.net_id
net_id = iface.net.id if iface.net else None
node_id = iface.node.id if iface.node else None
net2_id = iface.othernet.id if iface.othernet else None
ip4_net = iface.get_ip4()
ip4 = str(ip4_net.ip) if ip4_net else None
ip4_mask = ip4_net.prefixlen if ip4_net else None
@ -531,10 +611,7 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
ip6_mask = ip6_net.prefixlen if ip6_net else None
mac = str(iface.mac) if iface.mac else None
return core_pb2.Interface(
id=_id,
net_id=net_id,
net2_id=net2_id,
node_id=node_id,
id=iface.id,
name=iface.name,
mac=mac,
mtu=iface.mtu,
@ -574,6 +651,12 @@ def get_nem_id(
def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneConfig]]:
"""
Get emane model configuration protobuf data.
:param session: session to get emane model configuration for
:return: dict of emane model protobuf configurations
"""
configs = {}
for _id, model_configs in session.emane.node_configs.items():
for model_name in model_configs:
@ -591,6 +674,12 @@ def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneCo
def get_hooks(session: Session) -> List[core_pb2.Hook]:
"""
Retrieve hook protobuf data for a session.
:param session: session to get hooks for
:return: list of hook protobufs
"""
hooks = []
for state in session.hooks:
state_hooks = session.hooks[state]
@ -601,6 +690,12 @@ def get_hooks(session: Session) -> List[core_pb2.Hook]:
def get_default_services(session: Session) -> List[ServiceDefaults]:
"""
Retrieve the default service sets for a given session.
:param session: session to get default service sets for
:return: list of default service sets
"""
default_services = []
for name, services in session.services.default_services.items():
default_service = ServiceDefaults(node_type=name, services=services)
@ -611,6 +706,14 @@ def get_default_services(session: Session) -> List[ServiceDefaults]:
def get_mobility_node(
session: Session, node_id: int, context: ServicerContext
) -> Union[WlanNode, EmaneNet]:
"""
Get mobility node.
:param session: session to get node from
:param node_id: id of node to get
:param context: grpc context
:return: wlan or emane node
"""
try:
return session.get_node(node_id, WlanNode)
except CoreError:
@ -621,17 +724,26 @@ def get_mobility_node(
def convert_session(session: Session) -> wrappers.Session:
links = []
nodes = []
"""
Convert session to its wrapped version.
:param session: session to convert
:return: wrapped session data
"""
emane_configs = get_emane_model_configs_dict(session)
nodes = []
links = []
for _id in session.nodes:
node = session.nodes[_id]
if not isinstance(node, (PtpNet, CtrlNet)):
node_emane_configs = emane_configs.get(node.id, [])
node_proto = get_node_proto(session, node, node_emane_configs)
nodes.append(node_proto)
node_links = get_links(node)
links.extend(node_links)
if isinstance(node, (WlanNode, EmaneNet)):
for link_data in node.links():
links.append(convert_link_data(link_data))
for core_link in session.link_manager.links():
links.extend(convert_core_link(core_link))
default_services = get_default_services(session)
x, y, z = session.location.refxyz
lat, lon, alt = session.location.refgeo
@ -665,6 +777,15 @@ def convert_session(session: Session) -> wrappers.Session:
def configure_node(
session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext
) -> None:
"""
Configure a node using all provided protobuf data.
:param session: session for node
:param node: node protobuf data
:param core_node: session node
:param context: grpc context
:return: nothing
"""
for emane_config in node.emane_configs:
_id = utils.iface_config_id(node.id, emane_config.iface_id)
config = {k: v.value for k, v in emane_config.config.items()}

View file

@ -26,7 +26,7 @@ from core.api.grpc.configservices_pb2 import (
GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse,
)
from core.api.grpc.core_pb2 import ExecuteScriptResponse
from core.api.grpc.core_pb2 import ExecuteScriptResponse, LinkedRequest, LinkedResponse
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
EmaneLinkResponse,
@ -76,17 +76,12 @@ from core.configservice.base import ConfigServiceBootError
from core.emane.modelmanager import EmaneModelManager
from core.emulator.coreemu import CoreEmu
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import (
EventTypes,
ExceptionLevels,
LinkTypes,
MessageFlags,
)
from core.emulator.enumerations import EventTypes, ExceptionLevels, MessageFlags
from core.emulator.session import NT, Session
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, NodeBase
from core.nodes.network import WlanNode
from core.nodes.network import CoreNetwork, WlanNode
from core.services.coreservices import ServiceManager
logger = logging.getLogger(__name__)
@ -564,12 +559,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
ifaces = []
for iface_id in node.ifaces:
iface = node.ifaces[iface_id]
iface_proto = grpcutils.iface_to_proto(request.node_id, iface)
iface_proto = grpcutils.iface_to_proto(iface)
ifaces.append(iface_proto)
emane_configs = grpcutils.get_emane_model_configs_dict(session)
node_emane_configs = emane_configs.get(node.id, [])
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
links = get_links(node)
links = get_links(session, node)
return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links)
def MoveNode(
@ -705,18 +700,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node2_id = request.link.node2_id
self.get_node(session, node1_id, context, NodeBase)
self.get_node(session, node2_id, context, NodeBase)
iface1_data, iface2_data, options, link_type = grpcutils.add_link_data(
request.link
)
iface1_data, iface2_data, options = grpcutils.add_link_data(request.link)
node1_iface, node2_iface = session.add_link(
node1_id, node2_id, iface1_data, iface2_data, options, link_type
node1_id, node2_id, iface1_data, iface2_data, options
)
iface1_data = None
if node1_iface:
iface1_data = grpcutils.iface_to_data(node1_iface)
if isinstance(node1_iface.node, CoreNetwork):
iface1_data = InterfaceData(id=node1_iface.id)
else:
iface1_data = node1_iface.get_data()
iface2_data = None
if node2_iface:
iface2_data = grpcutils.iface_to_data(node2_iface)
if isinstance(node2_iface.node, CoreNetwork):
iface2_data = InterfaceData(id=node2_iface.id)
else:
iface2_data = node2_iface.get_data()
source = request.source if request.source else None
link_data = LinkData(
message_type=MessageFlags.ADD,
@ -731,9 +730,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
iface1_proto = None
iface2_proto = None
if node1_iface:
iface1_proto = grpcutils.iface_to_proto(node1_id, node1_iface)
iface1_proto = grpcutils.iface_to_proto(node1_iface)
if node2_iface:
iface2_proto = grpcutils.iface_to_proto(node2_id, node2_iface)
iface2_proto = grpcutils.iface_to_proto(node2_iface)
return core_pb2.AddLinkResponse(
result=True, iface1=iface1_proto, iface2=iface2_proto
)
@ -1163,7 +1162,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self, request: core_pb2.GetInterfacesRequest, context: ServicerContext
) -> core_pb2.GetInterfacesResponse:
"""
Retrieve all the interfaces of the system including bridges, virtual ethernet, and loopback
Retrieve all the interfaces of the system including bridges, virtual ethernet,
and loopback.
:param request: get-interfaces request
:param context: context object
@ -1188,32 +1188,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logger.debug("emane link: %s", request)
session = self.get_session(request.session_id, context)
nem1 = request.nem1
iface1 = session.emane.get_iface(nem1)
if not iface1:
context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found")
node1 = iface1.node
nem2 = request.nem2
iface2 = session.emane.get_iface(nem2)
if not iface2:
context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found")
node2 = iface2.node
if iface1.net == iface2.net:
if request.linked:
flag = MessageFlags.ADD
else:
flag = MessageFlags.DELETE
color = session.get_link_color(iface1.net.id)
link = LinkData(
message_type=flag,
type=LinkTypes.WIRELESS,
node1_id=node1.id,
node2_id=node2.id,
network_id=iface1.net.id,
color=color,
)
flag = MessageFlags.ADD if request.linked else MessageFlags.DELETE
link = session.emane.get_nem_link(request.nem1, request.nem2, flag)
if link:
session.broadcast_link(link)
return EmaneLinkResponse(result=True)
else:
@ -1302,15 +1279,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if not isinstance(wlan.model, BasicRangeModel):
context.abort(
grpc.StatusCode.NOT_FOUND,
f"wlan node {request.wlan} does not using BasicRangeModel",
f"wlan node {request.wlan} is not using BasicRangeModel",
)
node1 = self.get_node(session, request.node1_id, context, CoreNode)
node2 = self.get_node(session, request.node2_id, context, CoreNode)
node1_iface, node2_iface = None, None
for net, iface1, iface2 in node1.commonnets(node2):
if net == wlan:
node1_iface = iface1
node2_iface = iface2
for iface in node1.get_ifaces(control=False):
if iface.net == wlan:
node1_iface = iface
break
for iface in node2.get_ifaces(control=False):
if iface.net == wlan:
node2_iface = iface
break
result = False
if node1_iface and node2_iface:
@ -1335,3 +1315,16 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context)
session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2)
return EmanePathlossesResponse()
def Linked(
self, request: LinkedRequest, context: ServicerContext
) -> LinkedResponse:
session = self.get_session(request.session_id, context)
session.linked(
request.node1_id,
request.node2_id,
request.iface1_id,
request.iface2_id,
request.linked,
)
return LinkedResponse()