Skip to content

Commit b2726b6

Browse files
authored
Merge pull request #562 from coreemu/develop
7.5.0 merge
2 parents d98a9a5 + d0a55dd commit b2726b6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2448
-1689
lines changed

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## 2021-03-11 CORE 7.5.0
2+
3+
* core-daemon
4+
* fixed issue setting mobility loop value properly
5+
* fixed issue that some states would not properly remove session directories
6+
* \#560 - fixed issues with sdt integration for mobility movement and layer creation
7+
* core-pygui
8+
* added multiple canvas support
9+
* added support to hide nodes and restore them visually
10+
* update to assign full netmasks to wireless connected nodes by default
11+
* update to display services and action controls for nodes during runtime
12+
* fixed issues with custom nodes
13+
* fixed issue auto assigning macs, avoiding duplication
14+
* fixed issue joining session with different netmasks
15+
* fixed issues when deleting a session from the sessions dialog
16+
* \#550 - fixed issue not sending all service customization data
17+
* core-cli
18+
* added delete session command
19+
120
## 2021-01-11 CORE 7.4.0
221

322
* Installation

configure.ac

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Process this file with autoconf to produce a configure script.
33

44
# this defines the CORE version number, must be static for AC_INIT
5-
AC_INIT(core, 7.4.0)
5+
AC_INIT(core, 7.5.0)
66

77
# autoconf and automake initialization
88
AC_CONFIG_SRCDIR([netns/version.h.in])

daemon/core/api/grpc/grpcutils.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
from core.errors import CoreError
2626
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
2727
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
28+
from core.nodes.docker import DockerNode
2829
from core.nodes.interface import CoreInterface
30+
from core.nodes.lxd import LxcNode
2931
from core.nodes.network import WlanNode
3032
from core.services.coreservices import CoreService
3133

@@ -67,6 +69,7 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption
6769
image=node_proto.image,
6870
services=node_proto.services,
6971
config_services=node_proto.config_services,
72+
canvas=node_proto.canvas,
7073
)
7174
if node_proto.emane:
7275
options.emane = node_proto.emane
@@ -263,19 +266,22 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
263266
geo = core_pb2.Geo(
264267
lat=node.position.lat, lon=node.position.lon, alt=node.position.alt
265268
)
266-
services = getattr(node, "services", [])
267-
if services is None:
268-
services = []
269-
services = [x.name for x in services]
270-
config_services = getattr(node, "config_services", {})
271-
config_services = [x for x in config_services]
269+
services = [x.name for x in node.services]
270+
model = node.type
271+
node_dir = None
272+
config_services = []
273+
if isinstance(node, CoreNodeBase):
274+
node_dir = node.nodedir
275+
config_services = [x for x in node.config_services]
276+
channel = None
277+
if isinstance(node, CoreNode):
278+
channel = node.ctrlchnlname
272279
emane_model = None
273280
if isinstance(node, EmaneNet):
274281
emane_model = node.model.name
275-
model = getattr(node, "type", None)
276-
node_dir = getattr(node, "nodedir", None)
277-
channel = getattr(node, "ctrlchnlname", None)
278-
image = getattr(node, "image", None)
282+
image = None
283+
if isinstance(node, (DockerNode, LxcNode)):
284+
image = node.image
279285
return core_pb2.Node(
280286
id=node.id,
281287
name=node.name,
@@ -290,6 +296,7 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
290296
config_services=config_services,
291297
dir=node_dir,
292298
channel=channel,
299+
canvas=node.canvas,
293300
)
294301

295302

daemon/core/api/grpc/wrappers.py

+3
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ class Node:
649649
geo: Geo = None
650650
dir: str = None
651651
channel: str = None
652+
canvas: int = None
652653

653654
# configurations
654655
emane_model_configs: Dict[
@@ -683,6 +684,7 @@ def from_proto(cls, proto: core_pb2.Node) -> "Node":
683684
geo=Geo.from_proto(proto.geo),
684685
dir=proto.dir,
685686
channel=proto.channel,
687+
canvas=proto.canvas,
686688
)
687689

688690
def to_proto(self) -> core_pb2.Node:
@@ -700,6 +702,7 @@ def to_proto(self) -> core_pb2.Node:
700702
server=self.server,
701703
dir=self.dir,
702704
channel=self.channel,
705+
canvas=self.canvas,
703706
)
704707

705708

daemon/core/configservices/frrservices/services.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ def data(self) -> Dict[str, Any]:
103103
ip4s.append(str(ip4.ip))
104104
for ip6 in iface.ip6s:
105105
ip6s.append(str(ip6.ip))
106-
is_control = getattr(iface, "control", False)
107-
ifaces.append((iface, ip4s, ip6s, is_control))
106+
ifaces.append((iface, ip4s, ip6s, iface.control))
108107

109108
return dict(
110109
frr_conf=frr_conf,

daemon/core/configservices/quaggaservices/services.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ def data(self) -> Dict[str, Any]:
104104
ip4s.append(str(ip4.ip))
105105
for ip6 in iface.ip6s:
106106
ip6s.append(str(ip6.ip))
107-
is_control = getattr(iface, "control", False)
108-
ifaces.append((iface, ip4s, ip6s, is_control))
107+
ifaces.append((iface, ip4s, ip6s, iface.control))
109108

110109
return dict(
111110
quagga_bin_search=quagga_bin_search,

daemon/core/emulator/session.py

+7-12
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ def __init__(
134134
self.link_handlers: List[Callable[[LinkData], None]] = []
135135
self.file_handlers: List[Callable[[FileData], None]] = []
136136
self.config_handlers: List[Callable[[ConfigData], None]] = []
137-
self.shutdown_handlers: List[Callable[[Session], None]] = []
138137

139138
# session options/metadata
140139
self.options: SessionConfig = SessionConfig()
@@ -591,7 +590,6 @@ def edit_node(self, node_id: int, options: NodeOptions) -> None:
591590
:raises core.CoreError: when node to update does not exist
592591
"""
593592
node = self.get_node(node_id, NodeBase)
594-
node.canvas = options.canvas
595593
node.icon = options.icon
596594
self.set_node_position(node, options)
597595
self.sdt.edit_node(node, options.lon, options.lat, options.alt)
@@ -772,20 +770,17 @@ def shutdown(self) -> None:
772770
"""
773771
if self.state == EventTypes.SHUTDOWN_STATE:
774772
logging.info("session(%s) state(%s) already shutdown", self.id, self.state)
775-
return
776-
logging.info("session(%s) state(%s) shutting down", self.id, self.state)
777-
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
778-
# clear out current core session
779-
self.clear()
780-
# shutdown sdt
781-
self.sdt.shutdown()
773+
else:
774+
logging.info("session(%s) state(%s) shutting down", self.id, self.state)
775+
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
776+
# clear out current core session
777+
self.clear()
778+
# shutdown sdt
779+
self.sdt.shutdown()
782780
# remove this sessions working directory
783781
preserve = self.options.get_config("preservedir") == "1"
784782
if not preserve:
785783
shutil.rmtree(self.session_dir, ignore_errors=True)
786-
# call session shutdown handlers
787-
for handler in self.shutdown_handlers:
788-
handler(self)
789784

790785
def broadcast_event(self, event_data: EventData) -> None:
791786
"""

daemon/core/gui/app.py

+37-33
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import logging
22
import math
33
import tkinter as tk
4-
from tkinter import PhotoImage, font, ttk
4+
from tkinter import PhotoImage, font, messagebox, ttk
55
from tkinter.ttk import Progressbar
66
from typing import Any, Dict, Optional, Type
77

88
import grpc
99

10-
from core.gui import appconfig, themes
10+
from core.gui import appconfig, images
11+
from core.gui import nodeutils as nutils
12+
from core.gui import themes
1113
from core.gui.appconfig import GuiConfig
1214
from core.gui.coreclient import CoreClient
1315
from core.gui.dialogs.error import ErrorDialog
1416
from core.gui.frames.base import InfoFrameBase
1517
from core.gui.frames.default import DefaultInfoFrame
16-
from core.gui.graph.graph import CanvasGraph
17-
from core.gui.images import ImageEnum, Images
18+
from core.gui.graph.manager import CanvasManager
19+
from core.gui.images import ImageEnum
1820
from core.gui.menubar import Menubar
19-
from core.gui.nodeutils import NodeUtils
2021
from core.gui.statusbar import StatusBar
2122
from core.gui.themes import PADY
2223
from core.gui.toolbar import Toolbar
@@ -29,13 +30,13 @@ class Application(ttk.Frame):
2930
def __init__(self, proxy: bool, session_id: int = None) -> None:
3031
super().__init__()
3132
# load node icons
32-
NodeUtils.setup()
33+
nutils.setup()
3334

3435
# widgets
3536
self.menubar: Optional[Menubar] = None
3637
self.toolbar: Optional[Toolbar] = None
3738
self.right_frame: Optional[ttk.Frame] = None
38-
self.canvas: Optional[CanvasGraph] = None
39+
self.manager: Optional[CanvasManager] = None
3940
self.statusbar: Optional[StatusBar] = None
4041
self.progress: Optional[Progressbar] = None
4142
self.infobar: Optional[ttk.Frame] = None
@@ -77,7 +78,7 @@ def setup_app(self) -> None:
7778
self.master.title("CORE")
7879
self.center()
7980
self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
80-
image = Images.get(ImageEnum.CORE, 16)
81+
image = images.from_enum(ImageEnum.CORE, width=images.DIALOG_SIZE)
8182
self.master.tk.call("wm", "iconphoto", self.master._w, image)
8283
self.master.option_add("*tearOff", tk.FALSE)
8384
self.setup_file_dialogs()
@@ -136,20 +137,8 @@ def draw_infobar(self) -> None:
136137
label.grid(sticky=tk.EW, pady=PADY)
137138

138139
def draw_canvas(self) -> None:
139-
canvas_frame = ttk.Frame(self.right_frame)
140-
canvas_frame.rowconfigure(0, weight=1)
141-
canvas_frame.columnconfigure(0, weight=1)
142-
canvas_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=1)
143-
self.canvas = CanvasGraph(canvas_frame, self, self.core)
144-
self.canvas.grid(sticky=tk.NSEW)
145-
scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
146-
scroll_y.grid(row=0, column=1, sticky=tk.NS)
147-
scroll_x = ttk.Scrollbar(
148-
canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview
149-
)
150-
scroll_x.grid(row=1, column=0, sticky=tk.EW)
151-
self.canvas.configure(xscrollcommand=scroll_x.set)
152-
self.canvas.configure(yscrollcommand=scroll_y.set)
140+
self.manager = CanvasManager(self.right_frame, self, self.core)
141+
self.manager.notebook.grid(sticky=tk.NSEW)
153142

154143
def draw_status(self) -> None:
155144
self.statusbar = StatusBar(self.right_frame, self)
@@ -179,17 +168,30 @@ def show_info(self) -> None:
179168
def hide_info(self) -> None:
180169
self.infobar.grid_forget()
181170

182-
def show_grpc_exception(self, title: str, e: grpc.RpcError) -> None:
171+
def show_grpc_exception(
172+
self, message: str, e: grpc.RpcError, blocking: bool = False
173+
) -> None:
183174
logging.exception("app grpc exception", exc_info=e)
184-
message = e.details()
185-
self.show_error(title, message)
175+
dialog = ErrorDialog(self, "GRPC Exception", message, e.details())
176+
if blocking:
177+
dialog.show()
178+
else:
179+
self.after(0, lambda: dialog.show())
186180

187-
def show_exception(self, title: str, e: Exception) -> None:
181+
def show_exception(self, message: str, e: Exception) -> None:
188182
logging.exception("app exception", exc_info=e)
189-
self.show_error(title, str(e))
183+
self.after(
184+
0, lambda: ErrorDialog(self, "App Exception", message, str(e)).show()
185+
)
190186

191-
def show_error(self, title: str, message: str) -> None:
192-
self.after(0, lambda: ErrorDialog(self, title, message).show())
187+
def show_exception_data(self, title: str, message: str, details: str) -> None:
188+
self.after(0, lambda: ErrorDialog(self, title, message, details).show())
189+
190+
def show_error(self, title: str, message: str, blocking: bool = False) -> None:
191+
if blocking:
192+
messagebox.showerror(title, message, parent=self)
193+
else:
194+
self.after(0, lambda: messagebox.showerror(title, message, parent=self))
193195

194196
def on_closing(self) -> None:
195197
if self.toolbar.picker:
@@ -201,15 +203,17 @@ def save_config(self) -> None:
201203

202204
def joined_session_update(self) -> None:
203205
if self.core.is_runtime():
206+
self.menubar.set_state(is_runtime=True)
204207
self.toolbar.set_runtime()
205208
else:
209+
self.menubar.set_state(is_runtime=False)
206210
self.toolbar.set_design()
207211

208-
def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
209-
return Images.get(image_enum, int(width * self.app_scale))
212+
def get_enum_icon(self, image_enum: ImageEnum, *, width: int) -> PhotoImage:
213+
return images.from_enum(image_enum, width=width, scale=self.app_scale)
210214

211-
def get_custom_icon(self, image_file: str, width: int) -> PhotoImage:
212-
return Images.get_custom(image_file, int(width * self.app_scale))
215+
def get_file_icon(self, file_path: str, *, width: int) -> PhotoImage:
216+
return images.from_file(file_path, width=width, scale=self.app_scale)
213217

214218
def close(self) -> None:
215219
self.master.destroy()

daemon/core/gui/appconfig.py

+19-21
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ def __init__(
185185
def copy_files(current_path: Path, new_path: Path) -> None:
186186
for current_file in current_path.glob("*"):
187187
new_file = new_path.joinpath(current_file.name)
188-
shutil.copy(current_file, new_file)
188+
if not new_file.exists():
189+
shutil.copy(current_file, new_file)
189190

190191

191192
def find_terminal() -> Optional[str]:
@@ -197,30 +198,27 @@ def find_terminal() -> Optional[str]:
197198

198199

199200
def check_directory() -> None:
200-
if HOME_PATH.exists():
201-
return
202-
HOME_PATH.mkdir()
203-
BACKGROUNDS_PATH.mkdir()
204-
CUSTOM_EMANE_PATH.mkdir()
205-
CUSTOM_SERVICE_PATH.mkdir()
206-
ICONS_PATH.mkdir()
207-
MOBILITY_PATH.mkdir()
208-
XMLS_PATH.mkdir()
209-
SCRIPT_PATH.mkdir()
210-
201+
HOME_PATH.mkdir(exist_ok=True)
202+
BACKGROUNDS_PATH.mkdir(exist_ok=True)
203+
CUSTOM_EMANE_PATH.mkdir(exist_ok=True)
204+
CUSTOM_SERVICE_PATH.mkdir(exist_ok=True)
205+
ICONS_PATH.mkdir(exist_ok=True)
206+
MOBILITY_PATH.mkdir(exist_ok=True)
207+
XMLS_PATH.mkdir(exist_ok=True)
208+
SCRIPT_PATH.mkdir(exist_ok=True)
211209
copy_files(LOCAL_ICONS_PATH, ICONS_PATH)
212210
copy_files(LOCAL_BACKGROUND_PATH, BACKGROUNDS_PATH)
213211
copy_files(LOCAL_XMLS_PATH, XMLS_PATH)
214212
copy_files(LOCAL_MOBILITY_PATH, MOBILITY_PATH)
215-
216-
terminal = find_terminal()
217-
if "EDITOR" in os.environ:
218-
editor = EDITORS[0]
219-
else:
220-
editor = EDITORS[1]
221-
preferences = PreferencesConfig(editor, terminal)
222-
config = GuiConfig(preferences=preferences)
223-
save(config)
213+
if not CONFIG_PATH.exists():
214+
terminal = find_terminal()
215+
if "EDITOR" in os.environ:
216+
editor = EDITORS[0]
217+
else:
218+
editor = EDITORS[1]
219+
preferences = PreferencesConfig(editor, terminal)
220+
config = GuiConfig(preferences=preferences)
221+
save(config)
224222

225223

226224
def read() -> GuiConfig:

0 commit comments

Comments
 (0)