commit
d98a9a5a91
24 changed files with 247 additions and 122 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
3
tasks.py
3
tasks.py
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue