Merge pull request #548 from coreemu/develop

merging 7.4.0
This commit is contained in:
bharnden 2021-01-11 09:10:57 -08:00 committed by GitHub
commit d98a9a5a91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 247 additions and 122 deletions

View file

@ -1,3 +1,26 @@
## 2021-01-11 CORE 7.4.0
* Installation
* fixed issue for automated install assuming ID_LIKE is always present in /etc/os-release
* gRPC API
* fixed issue stopping session and not properly going to data collect state
* fixed issue to have start session properly create a directory before configuration state
* core-pygui
* fixed issue handling deletion of wired link to a switch
* avoid saving edge metadata to xml when values are default
* fixed issue editing node mac addresses
* added support for configuring interface names
* fixed issue with potential node names to allow hyphens and remove under bars
* \#531 - fixed issue changing distributed nodes back to local
* core-daemon
* fixed issue to properly handle deleting links from a network to network node
* updated xml to support writing and reading link buffer configurations
* reverted change and removed mac learning from wlan, due to promiscuous like behavior
* fixed issue creating control interfaces when starting services
* fixed deadlock issue when clearing a session using sdt
* \#116 - fixed issue for wlans handling multiple mobility scripts at once
* \#539 - fixed issue in udp tlv api
## 2020-12-02 CORE 7.3.0
* core-daemon

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 7.3.0)
AC_INIT(core, 7.4.0)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])

View file

@ -221,9 +221,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# clear previous state and setup for creation
session.clear()
session.set_state(EventTypes.CONFIGURATION_STATE)
if not os.path.exists(session.session_dir):
os.mkdir(session.session_dir)
session.set_state(EventTypes.CONFIGURATION_STATE)
# location
if request.HasField("location"):
@ -315,6 +315,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("stop session: %s", request)
session = self.get_session(request.session_id, context)
session.data_collect()
session.shutdown()
return core_pb2.StopSessionResponse(result=True)

View file

@ -2029,7 +2029,7 @@ class CoreUdpHandler(CoreHandler):
for session_id in sessions:
session = self.server.mainserver.coreemu.sessions.get(session_id)
if session:
logging.debug("session handling message: %s", session.session_id)
logging.debug("session handling message: %s", session.id)
self.session = session
self.handle_message(message)
self.broadcast(message)

View file

@ -12,7 +12,6 @@ from core.emulator.data import LinkOptions
from core.emulator.enumerations import ConfigDataTypes
from core.errors import CoreError
from core.location.mobility import WirelessModel
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
from core.xml import emanexml
@ -119,13 +118,12 @@ class EmaneModel(WirelessModel):
"""
logging.debug("emane model(%s) has no post setup tasks", self.name)
def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None:
def update(self, moved_ifaces: List[CoreInterface]) -> None:
"""
Invoked from MobilityModel when nodes are moved; this causes
emane location events to be generated for the nodes in the moved
list, making EmaneModels compatible with Ns2ScriptedMobility.
:param moved: moved nodes
:param moved_ifaces: interfaces that were moved
:return: nothing
"""

View file

@ -144,6 +144,7 @@ class CoreEmu:
result = False
if session:
logging.info("shutting session down: %s", _id)
session.data_collect()
session.shutdown()
result = True
else:

View file

@ -106,6 +106,9 @@ class EventTypes(Enum):
def should_start(self) -> bool:
return self.value > self.DEFINITION_STATE.value
def already_collected(self) -> bool:
return self.value >= self.DATACOLLECT_STATE.value
class ExceptionLevels(Enum):
"""

View file

@ -13,7 +13,7 @@ import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union
from core import constants, utils
from core.configservice.manager import ConfigServiceManager
@ -369,6 +369,19 @@ class Session:
node1.delete_iface(iface1_id)
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
node2.delete_iface(iface2_id)
elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase
):
for iface in node1.get_ifaces(control=False):
if iface.othernet == node2:
node1.detach(iface)
iface.shutdown()
break
for iface in node2.get_ifaces(control=False):
if iface.othernet == node1:
node2.detach(iface)
iface.shutdown()
break
self.sdt.delete_link(node1_id, node2_id)
def update_link(
@ -558,11 +571,11 @@ class Session:
if isinstance(node, WlanNode):
self.mobility.set_model_config(_id, BasicRangeModel.name)
# boot nodes after runtime, CoreNodes, Physical, and RJ45 are all nodes
is_boot_node = isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node)
# boot nodes after runtime CoreNodes and PhysicalNodes
is_boot_node = isinstance(node, (CoreNode, PhysicalNode))
if self.state == EventTypes.RUNTIME_STATE and is_boot_node:
self.write_nodes()
self.add_remove_control_iface(node=node, remove=False)
self.add_remove_control_iface(node, remove=False)
self.services.boot_services(node)
self.sdt.add_node(node)
@ -757,10 +770,11 @@ class Session:
"""
Shutdown all session nodes and remove the session directory.
"""
if self.state == EventTypes.SHUTDOWN_STATE:
logging.info("session(%s) state(%s) already shutdown", self.id, self.state)
return
logging.info("session(%s) state(%s) shutting down", self.id, self.state)
if self.state != EventTypes.SHUTDOWN_STATE:
self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True)
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
# clear out current core session
self.clear()
# shutdown sdt
@ -1115,13 +1129,16 @@ class Session:
"""
Clear the nodes dictionary, and call shutdown for each node.
"""
nodes_ids = []
with self.nodes_lock:
funcs = []
while self.nodes:
_, node = self.nodes.popitem()
self.sdt.delete_node(node.id)
nodes_ids.append(node.id)
funcs.append((node.shutdown, [], {}))
utils.threadpool(funcs)
for node_id in nodes_ids:
self.sdt.delete_node(node_id)
def write_nodes(self) -> None:
"""
@ -1245,6 +1262,14 @@ class Session:
:return: nothing
"""
if self.state.already_collected():
logging.info(
"session(%s) state(%s) already data collected", self.id, self.state
)
return
logging.info("session(%s) state(%s) data collection", self.id, self.state)
self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True)
# stop event loop
self.event_loop.stop()
@ -1266,10 +1291,8 @@ class Session:
self.update_control_iface_hosts(remove=True)
# remove all four possible control networks
self.add_remove_control_net(0, remove=True)
self.add_remove_control_net(1, remove=True)
self.add_remove_control_net(2, remove=True)
self.add_remove_control_net(3, remove=True)
for i in range(4):
self.add_remove_control_net(i, remove=True)
def short_session_id(self) -> str:
"""
@ -1290,7 +1313,6 @@ class Session:
:return: nothing
"""
logging.info("booting node(%s): %s", node.name, [x.name for x in node.services])
self.add_remove_control_iface(node=node, remove=False)
self.services.boot_services(node)
node.start_config_services()
@ -1305,11 +1327,10 @@ class Session:
with self.nodes_lock:
funcs = []
start = time.monotonic()
for _id in self.nodes:
node = self.nodes[_id]
if isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node):
args = (node,)
funcs.append((self.boot_node, args, {}))
for node in self.nodes.values():
if isinstance(node, (CoreNode, PhysicalNode)):
self.add_remove_control_iface(node, remove=False)
funcs.append((self.boot_node, (node,), {}))
results, exceptions = utils.threadpool(funcs)
total = time.monotonic() - start
logging.debug("boot run time: %s", total)
@ -1457,7 +1478,7 @@ class Session:
def add_remove_control_iface(
self,
node: CoreNode,
node: Union[CoreNode, PhysicalNode],
net_index: int = 0,
remove: bool = False,
conf_required: bool = True,

View file

@ -582,6 +582,8 @@ class CoreClient:
# create edges config
edges_config = []
for edge in self.links.values():
if not edge.is_customized():
continue
edge_config = dict(token=edge.token, width=edge.width, color=edge.color)
edges_config.append(edge_config)
edges_config = json.dumps(edges_config)

View file

@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Dict, Optional
import netaddr
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node
from core.api.grpc.wrappers import Interface, Node
from core.gui import nodeutils, validation
from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog
@ -21,8 +21,11 @@ if TYPE_CHECKING:
from core.gui.app import Application
from core.gui.graph.node import CanvasNode
IFACE_NAME_LEN: int = 15
DEFAULT_SERVER: str = "localhost"
def check_ip6(parent, name: str, value: str) -> bool:
def check_ip6(parent: tk.BaseWidget, name: str, value: str) -> bool:
if not value:
return True
title = f"IP6 Error for {name}"
@ -47,7 +50,7 @@ def check_ip6(parent, name: str, value: str) -> bool:
return True
def check_ip4(parent, name: str, value: str) -> bool:
def check_ip4(parent: tk.BaseWidget, name: str, value: str) -> bool:
if not value:
return True
title = f"IP4 Error for {name}"
@ -84,16 +87,88 @@ def mac_auto(is_auto: tk.BooleanVar, entry: ttk.Entry, mac: tk.StringVar) -> Non
class InterfaceData:
def __init__(
self,
name: tk.StringVar,
is_auto: tk.BooleanVar,
mac: tk.StringVar,
ip4: tk.StringVar,
ip6: tk.StringVar,
) -> None:
self.name: tk.StringVar = name
self.is_auto: tk.BooleanVar = is_auto
self.mac: tk.StringVar = mac
self.ip4: tk.StringVar = ip4
self.ip6: tk.StringVar = ip6
def validate(self, parent: tk.BaseWidget, iface: Interface) -> bool:
valid_name = self._validate_name(parent, iface)
valid_ip4 = self._validate_ip4(parent, iface)
valid_ip6 = self._validate_ip6(parent, iface)
valid_mac = self._validate_mac(parent, iface)
return all([valid_name, valid_ip4, valid_ip6, valid_mac])
def _validate_name(self, parent: tk.BaseWidget, iface: Interface) -> bool:
name = self.name.get()
title = f"Interface Name Error for {iface.name}"
if not name:
messagebox.showerror(title, "Name cannot be empty", parent=parent)
return False
if len(name) > IFACE_NAME_LEN:
messagebox.showerror(
title,
f"Name cannot be greater than {IFACE_NAME_LEN} chars",
parent=parent,
)
return False
for x in name:
if x.isspace() or x == "/":
messagebox.showerror(
title, "Name cannot contain space or /", parent=parent
)
return False
iface.name = name
return True
def _validate_ip4(self, parent: tk.BaseWidget, iface: Interface) -> bool:
ip4_net = self.ip4.get()
if not check_ip4(parent, iface.name, ip4_net):
return False
if ip4_net:
ip4, ip4_mask = ip4_net.split("/")
ip4_mask = int(ip4_mask)
else:
ip4, ip4_mask = "", 0
iface.ip4 = ip4
iface.ip4_mask = ip4_mask
return True
def _validate_ip6(self, parent: tk.BaseWidget, iface: Interface) -> bool:
ip6_net = self.ip6.get()
if not check_ip6(parent, iface.name, ip6_net):
return False
if ip6_net:
ip6, ip6_mask = ip6_net.split("/")
ip6_mask = int(ip6_mask)
else:
ip6, ip6_mask = "", 0
iface.ip6 = ip6
iface.ip6_mask = ip6_mask
return True
def _validate_mac(self, parent: tk.BaseWidget, iface: Interface) -> bool:
mac = self.mac.get()
auto_mac = self.is_auto.get()
if auto_mac:
iface.mac = None
else:
if not netaddr.valid_mac(mac):
title = f"MAC Error for {iface.name}"
messagebox.showerror(title, "Invalid MAC Address", parent=parent)
return False
else:
mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded)
iface.mac = str(mac)
return True
class NodeConfigDialog(Dialog):
def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None:
@ -109,7 +184,7 @@ class NodeConfigDialog(Dialog):
self.name: tk.StringVar = tk.StringVar(value=self.node.name)
self.type: tk.StringVar = tk.StringVar(value=self.node.model)
self.container_image: tk.StringVar = tk.StringVar(value=self.node.image)
server = "localhost"
server = DEFAULT_SERVER
if self.node.server:
server = self.node.server
self.server: tk.StringVar = tk.StringVar(value=server)
@ -176,7 +251,7 @@ class NodeConfigDialog(Dialog):
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Server")
label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY)
servers = ["localhost"]
servers = [DEFAULT_SERVER]
servers.extend(list(sorted(self.app.core.servers.keys())))
combobox = ttk.Combobox(
frame, textvariable=self.server, values=servers, state=combo_state
@ -229,6 +304,14 @@ class NodeConfigDialog(Dialog):
button.grid(row=row, sticky=tk.EW, columnspan=3, pady=PADY)
row += 1
label = ttk.Label(tab, text="Name")
label.grid(row=row, column=0, padx=PADX, pady=PADY)
name = tk.StringVar(value=iface.name)
entry = ttk.Entry(tab, textvariable=name, state=state)
entry.var = name
entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW)
row += 1
label = ttk.Label(tab, text="MAC")
label.grid(row=row, column=0, padx=PADX, pady=PADY)
auto_set = not iface.mac
@ -267,7 +350,7 @@ class NodeConfigDialog(Dialog):
entry = ttk.Entry(tab, textvariable=ip6, state=state)
entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW)
self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6)
self.ifaces[iface.id] = InterfaceData(name, is_auto, mac, ip4, ip6)
def draw_buttons(self) -> None:
frame = ttk.Frame(self.top)
@ -300,8 +383,11 @@ class NodeConfigDialog(Dialog):
if NodeUtils.is_image_node(self.node.type):
self.node.image = self.container_image.get()
server = self.server.get()
if NodeUtils.is_container_node(self.node.type) and server != "localhost":
self.node.server = server
if NodeUtils.is_container_node(self.node.type):
if server == DEFAULT_SERVER:
self.node.server = None
else:
self.node.server = server
# set custom icon
if self.image_file:
@ -313,43 +399,9 @@ class NodeConfigDialog(Dialog):
# update node interface data
for iface in self.canvas_node.ifaces.values():
data = self.ifaces[iface.id]
# validate ip4
ip4_net = data.ip4.get()
if not check_ip4(self, iface.name, ip4_net):
error = True
error = not data.validate(self, iface)
if error:
break
if ip4_net:
ip4, ip4_mask = ip4_net.split("/")
ip4_mask = int(ip4_mask)
else:
ip4, ip4_mask = "", 0
iface.ip4 = ip4
iface.ip4_mask = ip4_mask
# validate ip6
ip6_net = data.ip6.get()
if not check_ip6(self, iface.name, ip6_net):
error = True
break
if ip6_net:
ip6, ip6_mask = ip6_net.split("/")
ip6_mask = int(ip6_mask)
else:
ip6, ip6_mask = "", 0
iface.ip6 = ip6
iface.ip6_mask = ip6_mask
mac = data.mac.get()
auto_mac = data.is_auto.get()
if not auto_mac and not netaddr.valid_mac(mac):
title = f"MAC Error for {iface.name}"
messagebox.showerror(title, "Invalid MAC Address")
error = True
break
elif not auto_mac:
mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded)
iface.mac = str(mac)
# redraw
if not error:

View file

@ -28,8 +28,8 @@ def create_wireless_token(src: int, dst: int, network: int) -> str:
def create_edge_token(link: Link) -> str:
iface1_id = link.iface1.id if link.iface1 else None
iface2_id = link.iface2.id if link.iface2 else None
iface1_id = link.iface1.id if link.iface1 else 0
iface2_id = link.iface2.id if link.iface2 else 0
return f"{link.node1_id}-{iface1_id}-{link.node2_id}-{iface2_id}"
@ -297,6 +297,9 @@ class CanvasEdge(Edge):
self.context: tk.Menu = tk.Menu(self.canvas)
self.create_context()
def is_customized(self) -> bool:
return self.width != EDGE_WIDTH or self.color != EDGE_COLOR
def create_context(self) -> None:
themes.style_menu(self.context)
self.context.add_command(label="Configure", command=self.click_configure)

View file

@ -107,7 +107,7 @@ class NodeNameEntry(ValidationEntry):
if len(s) == 0:
return True
for x in s:
if not x.isalnum() and x != "_":
if not x.isalnum() and x != "-":
return False
return True

View file

@ -31,6 +31,9 @@ from core.nodes.network import WlanNode
if TYPE_CHECKING:
from core.emulator.session import Session
LEARNING_DISABLED: int = 0
LEARNING_ENABLED: int = 30000
def get_mobility_node(session: "Session", node_id: int) -> Union[WlanNode, EmaneNet]:
try:
@ -172,26 +175,6 @@ class MobilityManager(ModelManager):
)
self.session.broadcast_event(event_data)
def update_nets(
self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]
) -> None:
"""
A mobility script has caused nodes in the 'moved' list to move.
Update every mobility network. This saves range calculations if the model
were to recalculate for each individual node movement.
:param moved: moved nodes
:param moved_ifaces: moved network interfaces
:return: nothing
"""
for node_id in self.nodes():
try:
node = get_mobility_node(self.session, node_id)
if node.model:
node.model.update(moved, moved_ifaces)
except CoreError:
logging.exception("error updating mobility node")
class WirelessModel(ConfigurableOptions):
"""
@ -223,11 +206,10 @@ class WirelessModel(ConfigurableOptions):
"""
return []
def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None:
def update(self, moved_ifaces: List[CoreInterface]) -> None:
"""
Update this wireless model.
:param moved: moved nodes
:param moved_ifaces: moved network interfaces
:return: nothing
"""
@ -280,6 +262,12 @@ class BasicRangeModel(WirelessModel):
Configuration(
_id="error", _type=ConfigDataTypes.STRING, default="0", label="loss (%)"
),
Configuration(
_id="promiscuous",
_type=ConfigDataTypes.BOOL,
default="0",
label="promiscuous mode",
),
]
@classmethod
@ -303,6 +291,7 @@ class BasicRangeModel(WirelessModel):
self.delay: Optional[int] = None
self.loss: Optional[float] = None
self.jitter: Optional[int] = None
self.promiscuous: bool = False
def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int:
"""
@ -369,14 +358,13 @@ class BasicRangeModel(WirelessModel):
position_callback = set_position
def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None:
def update(self, moved_ifaces: List[CoreInterface]) -> None:
"""
Node positions have changed without recalc. Update positions from
node.position, then re-calculate links for those that have moved.
Assumes bidirectional links, with one calculation per node pair, where
one of the nodes has moved.
:param moved: moved nodes
:param moved_ifaces: moved network interfaces
:return: nothing
"""
@ -420,7 +408,6 @@ class BasicRangeModel(WirelessModel):
with self.wlan._linked_lock:
linked = self.wlan.linked(a, b)
if d > self.range:
if linked:
logging.debug("was linked, unlinking")
@ -467,6 +454,12 @@ class BasicRangeModel(WirelessModel):
self.delay = self._get_config(self.delay, config, "delay")
self.loss = self._get_config(self.loss, config, "error")
self.jitter = self._get_config(self.jitter, config, "jitter")
promiscuous = config["promiscuous"] == "1"
if self.promiscuous and not promiscuous:
self.wlan.net_client.set_mac_learning(self.wlan.brname, LEARNING_ENABLED)
elif not self.promiscuous and promiscuous:
self.wlan.net_client.set_mac_learning(self.wlan.brname, LEARNING_DISABLED)
self.promiscuous = promiscuous
self.setlinkparams()
def create_link_data(
@ -638,17 +631,14 @@ class WayPointMobility(WirelessModel):
return
return self.run()
# only move interfaces attached to self.wlan, or all nodenum in script?
moved = []
moved_ifaces = []
for iface in self.net.get_ifaces():
node = iface.node
if self.movenode(node, dt):
moved.append(node)
moved_ifaces.append(iface)
# calculate all ranges after moving nodes; this saves calculations
self.session.mobility.update_nets(moved, moved_ifaces)
self.net.model.update(moved_ifaces)
# TODO: check session state
self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround)
@ -659,7 +649,6 @@ class WayPointMobility(WirelessModel):
:return: nothing
"""
logging.info("running mobility scenario")
self.timezero = time.monotonic()
self.lasttime = self.timezero - (0.001 * self.refresh_ms)
self.movenodesinitial()
@ -719,7 +708,6 @@ class WayPointMobility(WirelessModel):
:return: nothing
"""
moved = []
moved_ifaces = []
for iface in self.net.get_ifaces():
node = iface.node
@ -727,9 +715,8 @@ class WayPointMobility(WirelessModel):
continue
x, y, z = self.initial[node.id].coords
self.setnodeposition(node, x, y, z)
moved.append(node)
moved_ifaces.append(iface)
self.session.mobility.update_nets(moved, moved_ifaces)
self.net.model.update(moved_ifaces)
def addwaypoint(
self,
@ -1114,7 +1101,7 @@ class Ns2ScriptedMobility(WayPointMobility):
:return: nothing
"""
logging.info("starting script")
logging.info("starting script: %s", self.file)
laststate = self.state
super().start()
if laststate == self.STATE_PAUSED:
@ -1135,6 +1122,7 @@ class Ns2ScriptedMobility(WayPointMobility):
:return: nothing
"""
logging.info("pausing script: %s", self.file)
super().pause()
self.statescript("pause")
@ -1146,6 +1134,7 @@ class Ns2ScriptedMobility(WayPointMobility):
position
:return: nothing
"""
logging.info("stopping script: %s", self.file)
super().stop(move_initial=move_initial)
self.statescript("stop")

View file

@ -837,7 +837,12 @@ class CoreNode(CoreNodeBase):
if net.has_custom_iface:
return net.custom_iface(self, iface_data)
else:
iface_id = self.newveth(iface_data.id, iface_data.name)
iface_id = iface_data.id
if iface_id is not None and iface_id in self.ifaces:
raise CoreError(
f"node({self.name}) already has interface({iface_id})"
)
iface_id = self.newveth(iface_id, iface_data.name)
self.attachnet(iface_id, net)
if iface_data.mac:
self.set_mac(iface_id, iface_data.mac)

View file

@ -241,12 +241,16 @@ class CoreInterface:
jitter = self.getparam("jitter")
if jitter is not None:
jitter = int(jitter)
buffer = self.getparam("buffer")
if buffer is not None:
buffer = int(buffer)
return LinkOptions(
delay=delay,
bandwidth=bandwidth,
dup=dup,
jitter=jitter,
loss=self.getparam("loss"),
buffer=buffer,
unidirectional=unidirectional,
)

View file

@ -286,14 +286,15 @@ class LinuxNetClient:
return True
return False
def disable_mac_learning(self, name: str) -> None:
def set_mac_learning(self, name: str, value: int) -> None:
"""
Disable mac learning for a Linux bridge.
Set mac learning for a Linux bridge.
:param name: bridge name
:param value: ageing time value
:return: nothing
"""
self.run(f"{IP} link set {name} type bridge ageing_time 0")
self.run(f"{IP} link set {name} type bridge ageing_time {value}")
class OvsNetClient(LinuxNetClient):

View file

@ -32,7 +32,8 @@ if TYPE_CHECKING:
WirelessModelType = Type[WirelessModel]
ebtables_lock = threading.Lock()
LEARNING_DISABLED: int = 0
ebtables_lock: threading.Lock = threading.Lock()
class EbtablesQueue:
@ -946,7 +947,7 @@ class HubNode(CoreNetwork):
:return: nothing
"""
super().startup()
self.net_client.disable_mac_learning(self.brname)
self.net_client.set_mac_learning(self.brname, LEARNING_DISABLED)
class WlanNode(CoreNetwork):
@ -989,7 +990,6 @@ class WlanNode(CoreNetwork):
:return: nothing
"""
super().startup()
self.net_client.disable_mac_learning(self.brname)
ebq.ebchange(self)
def attach(self, iface: CoreInterface) -> None:

View file

@ -266,7 +266,9 @@ class Rj45Node(CoreNodeBase):
will run on, default is None for localhost
"""
super().__init__(session, _id, name, server)
self.iface = CoreInterface(session, self, name, name, mtu, server)
self.iface: CoreInterface = CoreInterface(
session, self, name, name, mtu, server
)
self.iface.transport_type = TransportType.RAW
self.lock: threading.RLock = threading.RLock()
self.iface_id: Optional[int] = None
@ -335,11 +337,12 @@ class Rj45Node(CoreNodeBase):
if iface_id is None:
iface_id = 0
if self.iface.net is not None:
raise CoreError("RJ45 nodes support at most 1 network interface")
raise CoreError(
f"RJ45({self.name}) nodes support at most 1 network interface"
)
self.ifaces[iface_id] = self.iface
self.iface_id = iface_id
if net is not None:
self.iface.attachnet(net)
self.iface.attachnet(net)
for ip in iface_data.get_ips():
self.add_ip(ip)
return self.iface
@ -353,6 +356,12 @@ class Rj45Node(CoreNodeBase):
"""
self.get_iface(iface_id)
self.ifaces.pop(iface_id)
if self.iface.net is None:
raise CoreError(
f"RJ45({self.name}) is not currently connected to a network"
)
self.iface.detachnet()
self.iface.net = None
self.shutdown()
def get_iface(self, iface_id: int) -> CoreInterface:

View file

@ -4,7 +4,6 @@ sdt.py: Scripted Display Tool (SDT3D) helper
import logging
import socket
import threading
from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple
from urllib.parse import urlparse
@ -65,7 +64,6 @@ class Sdt:
:param session: session this manager is tied to
"""
self.session: "Session" = session
self.lock: threading.Lock = threading.Lock()
self.sock: Optional[IO] = None
self.connected: bool = False
self.url: str = self.DEFAULT_SDT_URL

View file

@ -570,6 +570,7 @@ class CoreXmlWriter:
add_attribute(options, "unidirectional", options_data.unidirectional)
add_attribute(options, "network_id", link_data.network_id)
add_attribute(options, "key", options_data.key)
add_attribute(options, "buffer", options_data.buffer)
if options.items():
link_element.append(options)
@ -976,6 +977,7 @@ class CoreXmlReader:
if options.loss is None:
options.loss = get_float(options_element, "per")
options.unidirectional = get_int(options_element, "unidirectional")
options.buffer = get_int(options_element, "buffer")
if options.unidirectional == 1 and node_set in node_sets:
logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id)

View file

@ -74,10 +74,14 @@ if __name__ == "__main__":
parser.add_argument(
"-a",
"--address",
required=True,
help="local address that distributed servers will use for gre tunneling",
)
parser.add_argument(
"-s", "--server", help="distributed server to use for creating nodes"
"-s",
"--server",
required=True,
help="distributed server to use for creating nodes",
)
args = parser.parse_args()
main(args)

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "core"
version = "7.3.0"
version = "7.4.0"
description = "CORE Common Open Research Emulator"
authors = ["Boeing Research and Technology"]
license = "BSD-2-Clause"

View file

@ -309,6 +309,7 @@ class TestXml:
options.jitter = 10
options.delay = 30
options.dup = 5
options.buffer = 100
session.add_link(node1.id, switch.id, iface1_data, options=options)
# instantiate session
@ -352,6 +353,7 @@ class TestXml:
assert options.jitter == link.options.jitter
assert options.delay == link.options.delay
assert options.dup == link.options.dup
assert options.buffer == link.options.buffer
def test_link_options_ptp(
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
@ -376,6 +378,7 @@ class TestXml:
options.jitter = 10
options.delay = 30
options.dup = 5
options.buffer = 100
session.add_link(node1.id, node2.id, iface1_data, iface2_data, options)
# instantiate session
@ -419,6 +422,7 @@ class TestXml:
assert options.jitter == link.options.jitter
assert options.delay == link.options.delay
assert options.dup == link.options.dup
assert options.buffer == link.options.buffer
def test_link_options_bidirectional(
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
@ -444,6 +448,7 @@ class TestXml:
options1.loss = 10.5
options1.dup = 5
options1.jitter = 5
options1.buffer = 50
session.add_link(node1.id, node2.id, iface1_data, iface2_data, options1)
options2 = LinkOptions()
options2.unidirectional = 1
@ -452,6 +457,7 @@ class TestXml:
options2.loss = 10
options2.dup = 10
options2.jitter = 10
options2.buffer = 100
session.update_link(
node2.id, node1.id, iface2_data.id, iface1_data.id, options2
)
@ -499,8 +505,10 @@ class TestXml:
assert options1.loss == link1.options.loss
assert options1.dup == link1.options.dup
assert options1.jitter == link1.options.jitter
assert options1.buffer == link1.options.buffer
assert options2.bandwidth == link2.options.bandwidth
assert options2.delay == link2.options.delay
assert options2.loss == link2.options.loss
assert options2.dup == link2.options.dup
assert options2.jitter == link2.options.jitter
assert options2.buffer == link2.options.buffer

View file

@ -100,6 +100,7 @@ class OsInfo:
if not os_like:
like = " ".join(like)
print(f"unsupported os install type({like})")
print("trying using the -i option to specify an install type")
sys.exit(1)
if version:
try:
@ -141,7 +142,7 @@ def get_os(install_type: Optional[str]) -> OsInfo:
key, value = line.split("=")
d[key] = value.strip("\"")
name_value = d["ID"]
like_value = d["ID_LIKE"]
like_value = d.get("ID_LIKE", "")
version_value = d["VERSION_ID"]
return OsInfo.get(name_value, like_value.split(), version_value)