Merge branch 'coretk' into coretk-color

This commit is contained in:
Huy Pham 2019-12-13 15:53:51 -08:00
commit ea2bfad591
13 changed files with 160 additions and 78 deletions

View file

@ -183,8 +183,7 @@ class CoreClient:
) )
def handle_exception_event(self, event): def handle_exception_event(self, event):
print(event) logging.info("exception event: %s", event)
print(event.node_id)
self.app.statusbar.core_alarms.append(event) self.app.statusbar.core_alarms.append(event)
def join_session(self, session_id, query_location=True): def join_session(self, session_id, query_location=True):
@ -229,26 +228,25 @@ class CoreClient:
# get emane model config # get emane model config
response = self.client.get_emane_model_configs(self.session_id) response = self.client.get_emane_model_configs(self.session_id)
for _id in response.configs: for config in response.configs:
config = response.configs[_id]
interface = None interface = None
node_id = _id if config.interface != -1:
if _id >= 1000: interface = config.interface
interface = _id % 1000
node_id = int(_id / 1000)
self.set_emane_model_config( self.set_emane_model_config(
node_id, config.model, config.config, interface config.node_id, config.model, config.config, interface
) )
# get wlan configurations
response = self.client.get_wlan_configs(self.session_id)
for _id in response.configs:
mapped_config = response.configs[_id]
self.wlan_configs[_id] = mapped_config.config
# save and retrieve data, needed for session nodes # save and retrieve data, needed for session nodes
for node in session.nodes: for node in session.nodes:
# get node service config and file config # get node service config and file config
# get wlan configs for wlan nodes
if node.type == core_pb2.NodeType.WIRELESS_LAN:
response = self.client.get_wlan_config(self.session_id, node.id)
self.wlan_configs[node.id] = response.config
# retrieve service configurations data for default nodes # retrieve service configurations data for default nodes
elif node.type == core_pb2.NodeType.DEFAULT: if node.type == core_pb2.NodeType.DEFAULT:
for service in node.services: for service in node.services:
response = self.client.get_node_service( response = self.client.get_node_service(
self.session_id, node.id, service self.session_id, node.id, service

View file

@ -46,6 +46,7 @@ class NodeConfigDialog(Dialog):
self.canvas_node = canvas_node self.canvas_node = canvas_node
self.node = canvas_node.core_node self.node = canvas_node.core_node
self.image = canvas_node.image self.image = canvas_node.image
self.image_file = None
self.image_button = None self.image_button = None
self.name = tk.StringVar(value=self.node.name) self.name = tk.StringVar(value=self.node.name)
self.type = tk.StringVar(value=self.node.model) self.type = tk.StringVar(value=self.node.model)
@ -201,6 +202,7 @@ class NodeConfigDialog(Dialog):
if file_path: if file_path:
self.image = Images.create(file_path, nodeutils.ICON_SIZE) self.image = Images.create(file_path, nodeutils.ICON_SIZE)
self.image_button.config(image=self.image) self.image_button.config(image=self.image)
self.image_file = file_path
def config_apply(self): def config_apply(self):
# update core node # update core node
@ -211,6 +213,10 @@ class NodeConfigDialog(Dialog):
if NodeUtils.is_container_node(self.node.type) and server != "localhost": if NodeUtils.is_container_node(self.node.type) and server != "localhost":
self.node.server = server self.node.server = server
# set custom icon
if self.image_file:
self.node.icon = self.image_file
# update canvas node # update canvas node
self.canvas_node.image = self.image self.canvas_node.image = self.image

View file

@ -4,6 +4,7 @@ import tkinter as tk
from PIL import Image, ImageTk from PIL import Image, ImageTk
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from coretk import nodeutils
from coretk.dialogs.shapemod import ShapeDialog from coretk.dialogs.shapemod import ShapeDialog
from coretk.graph import tags from coretk.graph import tags
from coretk.graph.edges import CanvasEdge, CanvasWirelessEdge from coretk.graph.edges import CanvasEdge, CanvasWirelessEdge
@ -12,6 +13,7 @@ from coretk.graph.linkinfo import LinkInfo, Throughput
from coretk.graph.node import CanvasNode from coretk.graph.node import CanvasNode
from coretk.graph.shape import Shape from coretk.graph.shape import Shape
from coretk.graph.shapeutils import ShapeType, is_draw_shape from coretk.graph.shapeutils import ShapeType, is_draw_shape
from coretk.images import Images
from coretk.nodeutils import NodeUtils from coretk.nodeutils import NodeUtils
ZOOM_IN = 1.1 ZOOM_IN = 1.1
@ -136,6 +138,11 @@ class CanvasGraph(tk.Canvas):
valid_y = y1 <= y <= y2 valid_y = y1 <= y <= y2
return valid_x and valid_y return valid_x and valid_y
def valid_position(self, x1, y1, x2, y2):
valid_topleft = self.inside_canvas(x1, y1)
valid_bottomright = self.inside_canvas(x2, y2)
return valid_topleft and valid_bottomright
def draw_grid(self): def draw_grid(self):
""" """
Create grid. Create grid.
@ -186,12 +193,19 @@ class CanvasGraph(tk.Canvas):
""" """
# draw existing nodes # draw existing nodes
for core_node in session.nodes: for core_node in session.nodes:
logging.info("drawing core node: %s", core_node)
# peer to peer node is not drawn on the GUI # peer to peer node is not drawn on the GUI
if NodeUtils.is_ignore_node(core_node.type): if NodeUtils.is_ignore_node(core_node.type):
continue continue
# draw nodes on the canvas # draw nodes on the canvas
image = NodeUtils.node_icon(core_node.type, core_node.model) image = NodeUtils.node_icon(core_node.type, core_node.model)
if core_node.icon:
try:
image = Images.create(core_node.icon, nodeutils.ICON_SIZE)
except OSError:
logging.error("invalid icon: %s", core_node.icon)
x = core_node.position.x x = core_node.position.x
y = core_node.position.y y = core_node.position.y
node = CanvasNode(self.master, x, y, core_node, image) node = CanvasNode(self.master, x, y, core_node, image)
@ -530,6 +544,13 @@ class CanvasGraph(tk.Canvas):
""" """
x, y = self.canvas_xy(event) x, y = self.canvas_xy(event)
if not self.inside_canvas(x, y): if not self.inside_canvas(x, y):
if self.select_box:
self.select_box.delete()
self.select_box = None
if is_draw_shape(self.annotation_type) and self.shape_drawing:
shape = self.shapes.pop(self.selected)
shape.delete()
self.shape_drawing = False
return return
x_offset = x - self.cursor[0] x_offset = x - self.cursor[0]
@ -595,10 +616,7 @@ class CanvasGraph(tk.Canvas):
if self.selected is None or self.selected in self.shapes: if self.selected is None or self.selected in self.shapes:
actual_x, actual_y = self.get_actual_coords(x, y) actual_x, actual_y = self.get_actual_coords(x, y)
core_node = self.core.create_node( core_node = self.core.create_node(
int(actual_x), actual_x, actual_y, self.node_draw.node_type, self.node_draw.model
int(actual_y),
self.node_draw.node_type,
self.node_draw.model,
) )
node = CanvasNode(self.master, x, y, core_node, self.node_draw.image) node = CanvasNode(self.master, x, y, core_node, self.node_draw.image)
self.core.canvas_nodes[core_node.id] = node self.core.canvas_nodes[core_node.id] = node

View file

@ -103,10 +103,19 @@ class CanvasNode:
self.motion(x_offset, y_offset, update=False) self.motion(x_offset, y_offset, update=False)
def motion(self, x_offset, y_offset, update=True): def motion(self, x_offset, y_offset, update=True):
original_position = self.canvas.coords(self.id)
self.canvas.move(self.id, x_offset, y_offset) self.canvas.move(self.id, x_offset, y_offset)
x, y = self.canvas.coords(self.id)
# check new position
bbox = self.canvas.bbox(self.id)
if not self.canvas.valid_position(*bbox):
self.canvas.coords(self.id, original_position)
return
# move test and selection box
self.canvas.move(self.text_id, x_offset, y_offset) self.canvas.move(self.text_id, x_offset, y_offset)
self.canvas.move_selection(self.id, x_offset, y_offset) self.canvas.move_selection(self.id, x_offset, y_offset)
x, y = self.canvas.coords(self.id)
# move antennae # move antennae
for antenna_id in self.antennae: for antenna_id in self.antennae:
@ -131,8 +140,8 @@ class CanvasNode:
# set actual coords for node and update core is running # set actual coords for node and update core is running
real_x, real_y = self.canvas.get_actual_coords(x, y) real_x, real_y = self.canvas.get_actual_coords(x, y)
self.core_node.position.x = int(real_x) self.core_node.position.x = real_x
self.core_node.position.y = int(real_y) self.core_node.position.y = real_y
if self.app.core.is_runtime() and update: if self.app.core.is_runtime() and update:
self.app.core.edit_node(self.core_node) self.app.core.edit_node(self.core_node)
@ -155,11 +164,6 @@ class CanvasNode:
else: else:
self.show_config() self.show_config()
def update_coords(self):
x, y = self.canvas.coords(self.id)
self.core_node.position.x = int(x)
self.core_node.position.y = int(y)
def create_context(self): def create_context(self):
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
is_emane = self.core_node.type == NodeType.EMANE is_emane = self.core_node.type == NodeType.EMANE
@ -169,6 +173,8 @@ class CanvasNode:
context.add_command(label="Configure", command=self.show_config) context.add_command(label="Configure", command=self.show_config)
if NodeUtils.is_container_node(self.core_node.type): if NodeUtils.is_container_node(self.core_node.type):
context.add_command(label="Services", state=tk.DISABLED) context.add_command(label="Services", state=tk.DISABLED)
if is_wlan:
context.add_command(label="WLAN Config", command=self.show_wlan_config)
if is_wlan and self.core_node.id in self.app.core.mobility_players: if is_wlan and self.core_node.id in self.app.core.mobility_players:
context.add_command( context.add_command(
label="Mobility Player", command=self.show_mobility_player label="Mobility Player", command=self.show_mobility_player

View file

@ -136,7 +136,13 @@ class Shape:
self.canvas.delete(self.id) self.canvas.delete(self.id)
def motion(self, x_offset, y_offset): def motion(self, x_offset, y_offset):
original_position = self.canvas.coords(self.id)
self.canvas.move(self.id, x_offset, y_offset) self.canvas.move(self.id, x_offset, y_offset)
coords = self.canvas.coords(self.id)
if not self.canvas.valid_position(*coords):
self.canvas.coords(self.id, original_position)
return
self.canvas.move_selection(self.id, x_offset, y_offset) self.canvas.move_selection(self.id, x_offset, y_offset)
if self.text_id is not None: if self.text_id is not None:
self.canvas.move(self.text_id, x_offset, y_offset) self.canvas.move(self.text_id, x_offset, y_offset)

View file

@ -827,6 +827,18 @@ class CoreGrpcClient:
) )
return self.stub.ServiceAction(request) return self.stub.ServiceAction(request)
def get_wlan_configs(self, session_id):
"""
Get all wlan configurations.
:param int session_id: session id
:return: response with a dict of node ids to wlan configurations
:rtype: core_pb2.GetWlanConfigsResponse
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetWlanConfigsRequest(session_id=session_id)
return self.stub.GetWlanConfigs(request)
def get_wlan_config(self, session_id, node_id): def get_wlan_config(self, session_id, node_id):
""" """
Get wlan configuration for a node. Get wlan configuration for a node.

View file

@ -221,6 +221,22 @@ def get_emane_model_id(node_id, interface_id):
return node_id return node_id
def parse_emane_model_id(_id):
"""
Parses EMANE model id to get true node id and interface id.
:param _id: id to parse
:return: node id and interface id
:rtype: tuple
"""
interface = -1
node_id = _id
if _id >= 1000:
interface = _id % 1000
node_id = int(_id / 1000)
return node_id, interface
def convert_link(session, link_data): def convert_link(session, link_data):
""" """
Convert link_data into core protobuf Link Convert link_data into core protobuf Link

View file

@ -393,15 +393,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
position = core_pb2.Position( position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z x=node.position.x, y=node.position.y, z=node.position.z
) )
services = getattr(node, "services", []) services = getattr(node, "services", [])
if services is None: if services is None:
services = [] services = []
services = [x.name for x in services] services = [x.name for x in services]
emane_model = None emane_model = None
if isinstance(node, EmaneNet): if isinstance(node, EmaneNet):
emane_model = node.model.name emane_model = node.model.name
image = getattr(node, "image", None)
node_proto = core_pb2.Node( node_proto = core_pb2.Node(
id=node.id, id=node.id,
@ -411,6 +410,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
type=node_type.value, type=node_type.value,
position=position, position=position,
services=services, services=services,
icon=node.icon,
image=image,
) )
if isinstance(node, (DockerNode, LxcNode)): if isinstance(node, (DockerNode, LxcNode)):
node_proto.image = node.image node_proto.image = node.image
@ -488,13 +489,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: node event that contains node id, name, model, position, and services :return: node event that contains node id, name, model, position, and services
:rtype: core.api.grpc.core_pb2.NodeEvent :rtype: core.api.grpc.core_pb2.NodeEvent
""" """
x = None position = core_pb2.Position(x=event.x_position, y=event.y_position)
if event.x_position is not None:
x = int(event.x_position)
y = None
if event.y_position is not None:
y = int(event.y_position)
position = core_pb2.Position(x=x, y=y)
services = event.services or "" services = event.services or ""
services = services.split("|") services = services.split("|")
node_proto = core_pb2.Node( node_proto = core_pb2.Node(
@ -1163,13 +1158,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("get node service file: %s", request) logging.debug("get node service file: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context)
service = None
for current_service in node.services:
if current_service.name == request.service:
service = current_service
break
if not service:
context.abort(grpc.StatusCode.NOT_FOUND, "service not found")
file_data = session.services.get_service_file( file_data = session.services.get_service_file(
node, request.service, request.file node, request.service, request.file
) )
@ -1248,6 +1236,31 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.ServiceActionResponse(result=result) return core_pb2.ServiceActionResponse(result=result)
def GetWlanConfigs(self, request, context):
"""
Retrieve all wireless-lan configurations.
:param core.api.grpc.core_pb2.GetWlanConfigsRequest request: request
:param context: core.api.grpc.core_pb2.GetWlanConfigResponse
:return: all wlan configurations
:rtype: core.api.grpc.core_pb2.GetWlanConfigsResponse
"""
logging.debug("get wlan configs: %s", request)
session = self.get_session(request.session_id, context)
response = core_pb2.GetWlanConfigsResponse()
for node_id in session.mobility.node_configurations:
model_config = session.mobility.node_configurations[node_id]
if node_id == -1:
continue
for model_name in model_config:
if model_name != BasicRangeModel.name:
continue
current_config = session.mobility.get_model_config(node_id, model_name)
config = get_config_options(current_config, BasicRangeModel)
mapped_config = core_pb2.MappedConfig(config=config)
response.configs[node_id].CopyFrom(mapped_config)
return response
def GetWlanConfig(self, request, context): def GetWlanConfig(self, request, context):
""" """
Retrieve wireless-lan configuration of a node Retrieve wireless-lan configuration of a node
@ -1381,21 +1394,26 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("get emane model configs: %s", request) logging.debug("get emane model configs: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
response = core_pb2.GetEmaneModelConfigsResponse()
for node_id in session.emane.node_configurations: configs = []
model_config = session.emane.node_configurations[node_id] for _id in session.emane.node_configurations:
if node_id == -1: if _id == -1:
continue continue
for model_name in model_config: model_configs = session.emane.node_configurations[_id]
for model_name in model_configs:
model = session.emane.models[model_name] model = session.emane.models[model_name]
current_config = session.emane.get_model_config(node_id, model_name) current_config = session.emane.get_model_config(_id, model_name)
config = get_config_options(current_config, model) config = get_config_options(current_config, model)
node_id, interface = grpcutils.parse_emane_model_id(_id)
model_config = core_pb2.GetEmaneModelConfigsResponse.ModelConfig( model_config = core_pb2.GetEmaneModelConfigsResponse.ModelConfig(
model=model_name, config=config node_id=node_id,
model=model_name,
interface=interface,
config=config,
) )
response.configs[node_id].CopyFrom(model_config) configs.append(model_config)
return response return core_pb2.GetEmaneModelConfigsResponse(configs=configs)
def SaveXml(self, request, context): def SaveXml(self, request, context):
""" """

View file

@ -5,7 +5,6 @@ import signal
import sys import sys
import core.services import core.services
from core.emulator.emudata import IdGen
from core.emulator.session import Session from core.emulator.session import Session
from core.services.coreservices import ServiceManager from core.services.coreservices import ServiceManager
@ -49,7 +48,6 @@ class CoreEmu:
self.config = config self.config = config
# session management # session management
self.session_id_gen = IdGen()
self.sessions = {} self.sessions = {}
# load services # load services
@ -79,7 +77,6 @@ class CoreEmu:
:return: nothing :return: nothing
""" """
logging.info("shutting down all sessions") logging.info("shutting down all sessions")
self.session_id_gen.id = 0
sessions = self.sessions.copy() sessions = self.sessions.copy()
self.sessions.clear() self.sessions.clear()
for _id in sessions: for _id in sessions:
@ -96,11 +93,9 @@ class CoreEmu:
:rtype: EmuSession :rtype: EmuSession
""" """
if not _id: if not _id:
while True: _id = 1
_id = self.session_id_gen.next() while _id in self.sessions:
if _id not in self.sessions: _id += 1
break
session = _cls(_id, config=self.config) session = _cls(_id, config=self.config)
logging.info("created session: %s", _id) logging.info("created session: %s", _id)
self.sessions[_id] = session self.sessions[_id] = session

View file

@ -396,7 +396,7 @@ class CoreServices:
""" """
Add services to a node. Add services to a node.
:param core.coreobj.PyCoreNode node: node to add services to :param core.nodes.base.CoreNode node: node to add services to
:param str node_type: node type to add services to :param str node_type: node type to add services to
:param list[str] services: names of services to add to node :param list[str] services: names of services to add to node
:return: nothing :return: nothing

View file

@ -117,14 +117,8 @@ class NodeElement:
def add_position(self): def add_position(self):
x = self.node.position.x x = self.node.position.x
if x is not None:
x = int(x)
y = self.node.position.y y = self.node.position.y
if y is not None:
y = int(y)
z = self.node.position.z z = self.node.position.z
if z is not None:
z = int(z)
lat, lon, alt = None, None, None lat, lon, alt = None, None, None
if x is not None and y is not None: if x is not None and y is not None:
lat, lon, alt = self.session.location.getgeo(x, y, z) lat, lon, alt = self.session.location.getgeo(x, y, z)
@ -751,8 +745,8 @@ class CoreXmlReader:
position_element = device_element.find("position") position_element = device_element.find("position")
if position_element is not None: if position_element is not None:
x = get_int(position_element, "x") x = get_float(position_element, "x")
y = get_int(position_element, "y") y = get_float(position_element, "y")
if all([x, y]): if all([x, y]):
options.set_position(x, y) options.set_position(x, y)
@ -773,8 +767,8 @@ class CoreXmlReader:
position_element = network_element.find("position") position_element = network_element.find("position")
if position_element is not None: if position_element is not None:
x = get_int(position_element, "x") x = get_float(position_element, "x")
y = get_int(position_element, "y") y = get_float(position_element, "y")
if all([x, y]): if all([x, y]):
options.set_position(x, y) options.set_position(x, y)

View file

@ -101,6 +101,8 @@ service CoreApi {
} }
// wlan rpc // wlan rpc
rpc GetWlanConfigs (GetWlanConfigsRequest) returns (GetWlanConfigsResponse) {
}
rpc GetWlanConfig (GetWlanConfigRequest) returns (GetWlanConfigResponse) { rpc GetWlanConfig (GetWlanConfigRequest) returns (GetWlanConfigResponse) {
} }
rpc SetWlanConfig (SetWlanConfigRequest) returns (SetWlanConfigResponse) { rpc SetWlanConfig (SetWlanConfigRequest) returns (SetWlanConfigResponse) {
@ -585,6 +587,14 @@ message ServiceActionResponse {
bool result = 1; bool result = 1;
} }
message GetWlanConfigsRequest {
int32 session_id = 1;
}
message GetWlanConfigsResponse {
map<int32, MappedConfig> configs = 1;
}
message GetWlanConfigRequest { message GetWlanConfigRequest {
int32 session_id = 1; int32 session_id = 1;
int32 node_id = 2; int32 node_id = 2;
@ -654,10 +664,12 @@ message GetEmaneModelConfigsRequest {
message GetEmaneModelConfigsResponse { message GetEmaneModelConfigsResponse {
message ModelConfig { message ModelConfig {
string model = 1; int32 node_id = 1;
map<string, ConfigOption> config = 2; string model = 2;
int32 interface = 3;
map<string, ConfigOption> config = 4;
} }
map<int32, ModelConfig> configs = 1; repeated ModelConfig configs = 1;
} }
message SaveXmlRequest { message SaveXmlRequest {
@ -949,9 +961,9 @@ message SessionLocation {
} }
message Position { message Position {
int32 x = 1; float x = 1;
int32 y = 2; float y = 2;
int32 z = 3; float z = 3;
float lat = 4; float lat = 4;
float lon = 5; float lon = 5;
float alt = 6; float alt = 6;

View file

@ -708,10 +708,11 @@ class TestGrpc:
# then # then
assert len(response.configs) == 1 assert len(response.configs) == 1
assert emane_network.id in response.configs model_config = response.configs[0]
model_config = response.configs[emane_network.id] assert emane_network.id == model_config.node_id
assert model_config.model == EmaneIeee80211abgModel.name assert model_config.model == EmaneIeee80211abgModel.name
assert len(model_config.config) > 0 assert len(model_config.config) > 0
assert model_config.interface == -1
def test_set_emane_model_config(self, grpc_server): def test_set_emane_model_config(self, grpc_server):
# given # given