grpc: changes to support nodes containing configuration data, allowing for node creation with configs and querying nodes with their configs

This commit is contained in:
Blake Harnden 2021-07-12 10:29:53 -07:00
parent 8678922c92
commit 54ac807a4f
11 changed files with 290 additions and 291 deletions

View file

@ -11,16 +11,7 @@ from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tup
import grpc import grpc
from core.api.grpc import ( from core.api.grpc import core_pb2, core_pb2_grpc, emane_pb2, wrappers
configservices_pb2,
core_pb2,
core_pb2_grpc,
emane_pb2,
mobility_pb2,
services_pb2,
wlan_pb2,
wrappers,
)
from core.api.grpc.configservices_pb2 import ( from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsRequest, GetConfigServiceDefaultsRequest,
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
@ -233,95 +224,17 @@ class CoreGrpcClient:
self.proxy: bool = proxy self.proxy: bool = proxy
def start_session( def start_session(
self, self, session: wrappers.Session, definition: bool = False
session: wrappers.Session,
asymmetric_links: List[wrappers.Link] = None,
definition: bool = False,
) -> Tuple[bool, List[str]]: ) -> Tuple[bool, List[str]]:
""" """
Start a session. Start a session.
:param session: session to start :param session: session to start
:param asymmetric_links: link configuration for asymmetric links
:param definition: True to only define session data, False to start session :param definition: True to only define session data, False to start session
:return: tuple of result and exception strings :return: tuple of result and exception strings
""" """
nodes = [x.to_proto() for x in session.nodes.values()]
links = [x.to_proto() for x in session.links]
if asymmetric_links:
asymmetric_links = [x.to_proto() for x in asymmetric_links]
hooks = [x.to_proto() for x in session.hooks.values()]
emane_model_configs = []
mobility_configs = []
wlan_configs = []
service_configs = []
service_file_configs = []
config_service_configs = []
for node in session.nodes.values():
for key, config in node.emane_model_configs.items():
model, iface_id = key
config = wrappers.ConfigOption.to_dict(config)
if iface_id is None:
iface_id = -1
emane_model_config = emane_pb2.EmaneModelConfig(
node_id=node.id, iface_id=iface_id, model=model, config=config
)
emane_model_configs.append(emane_model_config)
if node.wlan_config:
config = wrappers.ConfigOption.to_dict(node.wlan_config)
wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config)
wlan_configs.append(wlan_config)
if node.mobility_config:
config = wrappers.ConfigOption.to_dict(node.mobility_config)
mobility_config = mobility_pb2.MobilityConfig(
node_id=node.id, config=config
)
mobility_configs.append(mobility_config)
for name, config in node.service_configs.items():
service_config = services_pb2.ServiceConfig(
node_id=node.id,
service=name,
directories=config.dirs,
files=config.configs,
startup=config.startup,
validate=config.validate,
shutdown=config.shutdown,
)
service_configs.append(service_config)
for service, file_configs in node.service_file_configs.items():
for file, data in file_configs.items():
service_file_config = services_pb2.ServiceFileConfig(
node_id=node.id, service=service, file=file, data=data
)
service_file_configs.append(service_file_config)
for name, service_config in node.config_service_configs.items():
config_service_config = configservices_pb2.ConfigServiceConfig(
node_id=node.id,
name=name,
templates=service_config.templates,
config=service_config.config,
)
config_service_configs.append(config_service_config)
options = {k: v.value for k, v in session.options.items()}
servers = [x.to_proto() for x in session.servers]
request = core_pb2.StartSessionRequest( request = core_pb2.StartSessionRequest(
session_id=session.id, session=session.to_proto(), definition=definition
nodes=nodes,
links=links,
location=session.location.to_proto(),
hooks=hooks,
emane_model_configs=emane_model_configs,
wlan_configs=wlan_configs,
mobility_configs=mobility_configs,
service_configs=service_configs,
service_file_configs=service_file_configs,
asymmetric_links=asymmetric_links,
config_service_configs=config_service_configs,
options=options,
user=session.user,
definition=definition,
metadata=session.metadata,
servers=servers,
) )
response = self.stub.StartSession(request) response = self.stub.StartSession(request)
return response.result, list(response.exceptions) return response.result, list(response.exceptions)

View file

@ -8,9 +8,8 @@ from grpc import ServicerContext
from core import utils from core import utils
from core.api.grpc import common_pb2, core_pb2, wrappers from core.api.grpc import common_pb2, core_pb2, wrappers
from core.api.grpc.common_pb2 import MappedConfig
from core.api.grpc.configservices_pb2 import ConfigServiceConfig from core.api.grpc.configservices_pb2 import ConfigServiceConfig
from core.api.grpc.emane_pb2 import GetEmaneModelConfig from core.api.grpc.emane_pb2 import NodeEmaneConfig
from core.api.grpc.services_pb2 import ( from core.api.grpc.services_pb2 import (
NodeServiceConfig, NodeServiceConfig,
NodeServiceData, NodeServiceData,
@ -252,12 +251,15 @@ def get_config_options(
return results return results
def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: def get_node_proto(
session: Session, node: NodeBase, emane_configs: List[NodeEmaneConfig]
) -> core_pb2.Node:
""" """
Convert CORE node to protobuf representation. Convert CORE node to protobuf representation.
:param session: session containing node :param session: session containing node
:param node: node to convert :param node: node to convert
:param emane_configs: emane configs related to node
:return: node proto :return: node proto
""" """
node_type = session.get_node_type(node.__class__) node_type = session.get_node_type(node.__class__)
@ -283,6 +285,42 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
image = None image = None
if isinstance(node, (DockerNode, LxcNode)): if isinstance(node, (DockerNode, LxcNode)):
image = node.image image = node.image
# check for wlan config
wlan_config = session.mobility.get_configs(
node.id, config_type=BasicRangeModel.name
)
if wlan_config:
wlan_config = get_config_options(wlan_config, BasicRangeModel)
# check for mobility config
mobility_config = session.mobility.get_configs(
node.id, config_type=Ns2ScriptedMobility.name
)
if mobility_config:
mobility_config = get_config_options(mobility_config, Ns2ScriptedMobility)
# check for service configs
custom_services = session.services.custom_services.get(node.id)
service_configs = {}
if custom_services:
for service in custom_services.values():
service_proto = get_service_configuration(service)
service_configs[service.name] = NodeServiceConfig(
node_id=node.id,
service=service.name,
data=service_proto,
files=service.config_data,
)
# check for config service configs
config_service_configs = {}
if isinstance(node, CoreNode):
for service in node.config_services.values():
if not service.custom_templates and not service.custom_config:
continue
config_service_configs[service.name] = ConfigServiceConfig(
node_id=node.id,
name=service.name,
templates=service.custom_templates,
config=service.custom_config,
)
return core_pb2.Node( return core_pb2.Node(
id=node.id, id=node.id,
name=node.name, name=node.name,
@ -298,6 +336,11 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
dir=node_dir, dir=node_dir,
channel=channel, channel=channel,
canvas=node.canvas, canvas=node.canvas,
wlan_config=wlan_config,
mobility_config=mobility_config,
service_configs=service_configs,
config_service_configs=config_service_configs,
emane_configs=emane_configs,
) )
@ -530,8 +573,8 @@ def get_nem_id(
return nem_id return nem_id
def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneConfig]]:
configs = [] configs = {}
for _id, model_configs in session.emane.node_configs.items(): for _id, model_configs in session.emane.node_configs.items():
for model_name in model_configs: for model_name in model_configs:
model_class = session.emane.get_model(model_name) model_class = session.emane.get_model(model_name)
@ -539,42 +582,11 @@ def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]:
config = get_config_options(current_config, model_class) config = get_config_options(current_config, model_class)
node_id, iface_id = utils.parse_iface_config_id(_id) node_id, iface_id = utils.parse_iface_config_id(_id)
iface_id = iface_id if iface_id is not None else -1 iface_id = iface_id if iface_id is not None else -1
model_config = GetEmaneModelConfig( node_config = NodeEmaneConfig(
node_id=node_id, model=model_name, iface_id=iface_id, config=config model=model_name, iface_id=iface_id, config=config
) )
configs.append(model_config) node_configs = configs.setdefault(node_id, [])
return configs node_configs.append(node_config)
def get_wlan_configs(session: Session) -> Dict[int, MappedConfig]:
configs = {}
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 = MappedConfig(config=config)
configs[node_id] = mapped_config
return configs
def get_mobility_configs(session: Session) -> Dict[int, MappedConfig]:
configs = {}
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 != Ns2ScriptedMobility.name:
continue
current_config = session.mobility.get_model_config(node_id, model_name)
config = get_config_options(current_config, Ns2ScriptedMobility)
mapped_config = MappedConfig(config=config)
configs[node_id] = mapped_config
return configs return configs
@ -596,40 +608,6 @@ def get_default_services(session: Session) -> List[ServiceDefaults]:
return default_services return default_services
def get_node_service_configs(session: Session) -> List[NodeServiceConfig]:
configs = []
for node_id, service_configs in session.services.custom_services.items():
for name in service_configs:
service = session.services.get_service(node_id, name)
service_proto = get_service_configuration(service)
config = NodeServiceConfig(
node_id=node_id,
service=name,
data=service_proto,
files=service.config_data,
)
configs.append(config)
return configs
def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfig]:
configs = []
for node in session.nodes.values():
if not isinstance(node, CoreNodeBase):
continue
for name, service in node.config_services.items():
if not service.custom_templates and not service.custom_config:
continue
config_proto = ConfigServiceConfig(
node_id=node.id,
name=name,
templates=service.custom_templates,
config=service.custom_config,
)
configs.append(config_proto)
return configs
def get_mobility_node( def get_mobility_node(
session: Session, node_id: int, context: ServicerContext session: Session, node_id: int, context: ServicerContext
) -> Union[WlanNode, EmaneNet]: ) -> Union[WlanNode, EmaneNet]:
@ -645,10 +623,12 @@ def get_mobility_node(
def convert_session(session: Session) -> wrappers.Session: def convert_session(session: Session) -> wrappers.Session:
links = [] links = []
nodes = [] nodes = []
emane_configs = get_emane_model_configs_dict(session)
for _id in session.nodes: for _id in session.nodes:
node = session.nodes[_id] node = session.nodes[_id]
if not isinstance(node, (PtpNet, CtrlNet)): if not isinstance(node, (PtpNet, CtrlNet)):
node_proto = get_node_proto(session, node) node_emane_configs = emane_configs.get(node.id, [])
node_proto = get_node_proto(session, node, node_emane_configs)
nodes.append(node_proto) nodes.append(node_proto)
node_links = get_links(node) node_links = get_links(node)
links.extend(node_links) links.extend(node_links)
@ -659,11 +639,6 @@ def convert_session(session: Session) -> wrappers.Session:
x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale
) )
hooks = get_hooks(session) hooks = get_hooks(session)
emane_model_configs = get_emane_model_configs(session)
wlan_configs = get_wlan_configs(session)
mobility_configs = get_mobility_configs(session)
service_configs = get_node_service_configs(session)
config_service_configs = get_node_config_service_configs(session)
session_file = str(session.file_path) if session.file_path else None session_file = str(session.file_path) if session.file_path else None
options = get_config_options(session.options.get_configs(), session.options) options = get_config_options(session.options.get_configs(), session.options)
servers = [ servers = [
@ -680,13 +655,51 @@ def convert_session(session: Session) -> wrappers.Session:
default_services=default_services, default_services=default_services,
location=location, location=location,
hooks=hooks, hooks=hooks,
emane_model_configs=emane_model_configs,
wlan_configs=wlan_configs,
service_configs=service_configs,
config_service_configs=config_service_configs,
mobility_configs=mobility_configs,
metadata=session.metadata, metadata=session.metadata,
file=session_file, file=session_file,
options=options, options=options,
servers=servers, servers=servers,
) )
def configure_node(
session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext
) -> None:
for emane_config in node.emane_configs:
_id = utils.iface_config_id(node.id, emane_config.iface_id)
config = {k: v.value for k, v in emane_config.config.items()}
session.emane.set_config(_id, emane_config.model, config)
if node.wlan_config:
config = {k: v.value for k, v in node.wlan_config.items()}
session.mobility.set_model_config(node.id, BasicRangeModel.name, config)
if node.mobility_config:
config = {k: v.value for k, v in node.mobility_config.items()}
session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config)
for service_name, service_config in node.service_configs.items():
data = service_config.data
config = ServiceConfig(
node_id=node.id,
service=service_name,
startup=data.startup,
validate=data.validate,
shutdown=data.shutdown,
files=data.configs,
directories=data.dirs,
)
service_configuration(session, config)
for file_name, file_data in service_config.files.items():
session.services.set_service_file(
node.id, service_name, file_name, file_data
)
if node.config_service_configs:
if not isinstance(core_node, CoreNode):
context.abort(
grpc.StatusCode.INVALID_ARGUMENT,
"invalid node type with config service configs",
)
for service_name, service_config in node.config_service_configs.items():
service = core_node.config_services[service_name]
if service_config.config:
service.set_config(service_config.config)
for name, template in service_config.templates.items():
service.set_template(name, template)

View file

@ -224,7 +224,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: start session response :return: start session response
""" """
logger.debug("start session: %s", request) logger.debug("start session: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session.id, context)
# clear previous state and setup for creation # clear previous state and setup for creation
session.clear() session.clear()
@ -234,77 +234,51 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
state = EventTypes.CONFIGURATION_STATE state = EventTypes.CONFIGURATION_STATE
session.directory.mkdir(exist_ok=True) session.directory.mkdir(exist_ok=True)
session.set_state(state) session.set_state(state)
session.user = request.user session.user = request.session.user
# session options # session options
session.options.config_reset() session.options.config_reset()
for key, value in request.options.items(): for option in request.session.options.values():
session.options.set_config(key, value) session.options.set_config(option.name, option.value)
session.metadata = dict(request.metadata) session.metadata = dict(request.session.metadata)
# add servers # add servers
for server in request.servers: for server in request.session.servers:
session.distributed.add_server(server.name, server.host) session.distributed.add_server(server.name, server.host)
# location # location
if request.HasField("location"): if request.session.HasField("location"):
grpcutils.session_location(session, request.location) grpcutils.session_location(session, request.session.location)
# add all hooks # add all hooks
for hook in request.hooks: for hook in request.session.hooks:
state = EventTypes(hook.state) state = EventTypes(hook.state)
session.add_hook(state, hook.file, hook.data) session.add_hook(state, hook.file, hook.data)
# create nodes # create nodes
_, exceptions = grpcutils.create_nodes(session, request.nodes) _, exceptions = grpcutils.create_nodes(session, request.session.nodes)
if exceptions: if exceptions:
exceptions = [str(x) for x in exceptions] exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
# emane configs # check for configurations
for config in request.emane_model_configs: for node in request.session.nodes:
_id = utils.iface_config_id(config.node_id, config.iface_id) core_node = self.get_node(session, node.id, context, NodeBase)
session.emane.set_config(_id, config.model, config.config) grpcutils.configure_node(session, node, core_node, context)
# wlan configs
for config in request.wlan_configs:
session.mobility.set_model_config(
config.node_id, BasicRangeModel.name, config.config
)
# mobility configs
for config in request.mobility_configs:
session.mobility.set_model_config(
config.node_id, Ns2ScriptedMobility.name, config.config
)
# service configs
for config in request.service_configs:
grpcutils.service_configuration(session, config)
# service file configs
for config in request.service_file_configs:
session.services.set_service_file(
config.node_id, config.service, config.file, config.data
)
# config service configs
for config in request.config_service_configs:
node = self.get_node(session, config.node_id, context, CoreNode)
service = node.config_services[config.name]
if config.config:
service.set_config(config.config)
for name, template in config.templates.items():
service.set_template(name, template)
# create links # create links
_, exceptions = grpcutils.create_links(session, request.links) links = []
asym_links = []
for link in request.session.links:
if link.options.unidirectional:
asym_links.append(link)
else:
links.append(link)
_, exceptions = grpcutils.create_links(session, links)
if exceptions: if exceptions:
exceptions = [str(x) for x in exceptions] exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
_, exceptions = grpcutils.edit_links(session, asym_links)
# asymmetric links
_, exceptions = grpcutils.edit_links(session, request.asymmetric_links)
if exceptions: if exceptions:
exceptions = [str(x) for x in exceptions] exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
@ -541,6 +515,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
_type, _id, options = grpcutils.add_node_data(request.node) _type, _id, options = grpcutils.add_node_data(request.node)
_class = session.get_node_class(_type) _class = session.get_node_class(_type)
node = session.add_node(_class, _id, options) node = session.add_node(_class, _id, options)
grpcutils.configure_node(session, request.node, node, context)
source = request.source if request.source else None source = request.source if request.source else None
session.broadcast_node(node, MessageFlags.ADD, source) session.broadcast_node(node, MessageFlags.ADD, source)
return core_pb2.AddNodeResponse(node_id=node.id) return core_pb2.AddNodeResponse(node_id=node.id)
@ -563,7 +538,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
iface = node.ifaces[iface_id] iface = node.ifaces[iface_id]
iface_proto = grpcutils.iface_to_proto(request.node_id, iface) iface_proto = grpcutils.iface_to_proto(request.node_id, iface)
ifaces.append(iface_proto) ifaces.append(iface_proto)
node_proto = grpcutils.get_node_proto(session, node) emane_configs = grpcutils.get_emane_model_configs_dict(session)
node_emane_configs = emane_configs.get(node.id, [])
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
links = get_links(node) links = get_links(node)
return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links) return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links)

View file

@ -239,12 +239,26 @@ class NodeServiceData:
configs=proto.configs, configs=proto.configs,
startup=proto.startup, startup=proto.startup,
validate=proto.validate, validate=proto.validate,
validation_mode=proto.validation_mode, validation_mode=ServiceValidationMode(proto.validation_mode),
validation_timer=proto.validation_timer, validation_timer=proto.validation_timer,
shutdown=proto.shutdown, shutdown=proto.shutdown,
meta=proto.meta, meta=proto.meta,
) )
def to_proto(self) -> services_pb2.NodeServiceData:
return services_pb2.NodeServiceData(
executables=self.executables,
dependencies=self.dependencies,
dirs=self.dirs,
configs=self.configs,
startup=self.startup,
validate=self.validate,
validation_mode=self.validation_mode.value,
validation_timer=self.validation_timer,
shutdown=self.shutdown,
meta=self.meta,
)
@dataclass @dataclass
class NodeServiceConfig: class NodeServiceConfig:
@ -430,15 +444,27 @@ class ConfigOption:
@classmethod @classmethod
def from_proto(cls, proto: common_pb2.ConfigOption) -> "ConfigOption": def from_proto(cls, proto: common_pb2.ConfigOption) -> "ConfigOption":
config_type = ConfigOptionType(proto.type) if proto.type is not None else None
return ConfigOption( return ConfigOption(
label=proto.label, label=proto.label,
name=proto.name, name=proto.name,
value=proto.value, value=proto.value,
type=ConfigOptionType(proto.type), type=config_type,
group=proto.group, group=proto.group,
select=proto.select, select=proto.select,
) )
def to_proto(self) -> common_pb2.ConfigOption:
config_type = self.type.value if self.type is not None else None
return common_pb2.ConfigOption(
label=self.label,
name=self.name,
value=self.value,
type=config_type,
select=self.select,
group=self.group,
)
@dataclass @dataclass
class Interface: class Interface:
@ -714,6 +740,23 @@ class Node:
@classmethod @classmethod
def from_proto(cls, proto: core_pb2.Node) -> "Node": def from_proto(cls, proto: core_pb2.Node) -> "Node":
service_configs = {}
service_file_configs = {}
for service, node_config in proto.service_configs.items():
service_configs[service] = NodeServiceData.from_proto(node_config.data)
service_file_configs[service] = dict(node_config.files)
emane_configs = {}
for emane_config in proto.emane_configs:
iface_id = None if emane_config.iface_id == -1 else emane_config.iface_id
model = emane_config.model
key = (model, iface_id)
emane_configs[key] = ConfigOption.from_dict(emane_config.config)
config_service_configs = {}
for service, service_config in proto.config_service_configs.items():
config_service_configs[service] = ConfigServiceData(
templates=dict(service_config.templates),
config=dict(service_config.config),
)
return Node( return Node(
id=proto.id, id=proto.id,
name=proto.name, name=proto.name,
@ -730,9 +773,43 @@ class Node:
dir=proto.dir, dir=proto.dir,
channel=proto.channel, channel=proto.channel,
canvas=proto.canvas, canvas=proto.canvas,
wlan_config=ConfigOption.from_dict(proto.wlan_config),
mobility_config=ConfigOption.from_dict(proto.mobility_config),
service_configs=service_configs,
service_file_configs=service_file_configs,
config_service_configs=config_service_configs,
emane_model_configs=emane_configs,
) )
def to_proto(self) -> core_pb2.Node: def to_proto(self) -> core_pb2.Node:
emane_configs = []
for key, config in self.emane_model_configs.items():
model, iface_id = key
if iface_id is None:
iface_id = -1
config = {k: v.to_proto() for k, v in config.items()}
emane_config = emane_pb2.NodeEmaneConfig(
iface_id=iface_id, model=model, config=config
)
emane_configs.append(emane_config)
service_configs = {}
for service, service_data in self.service_configs.items():
service_configs[service] = services_pb2.NodeServiceConfig(
service=service, data=service_data.to_proto()
)
for service, file_configs in self.service_file_configs.items():
service_config = service_configs.get(service)
if service_config:
service_config.files.update(file_configs)
else:
service_configs[service] = services_pb2.NodeServiceConfig(
service=service, files=file_configs
)
config_service_configs = {}
for service, service_config in self.config_service_configs.items():
config_service_configs[service] = configservices_pb2.ConfigServiceConfig(
templates=service_config.templates, config=service_config.config
)
return core_pb2.Node( return core_pb2.Node(
id=self.id, id=self.id,
name=self.name, name=self.name,
@ -748,6 +825,11 @@ class Node:
dir=self.dir, dir=self.dir,
channel=self.channel, channel=self.channel,
canvas=self.canvas, canvas=self.canvas,
wlan_config={k: v.to_proto() for k, v in self.wlan_config.items()},
mobility_config={k: v.to_proto() for k, v in self.mobility_config.items()},
service_configs=service_configs,
config_service_configs=config_service_configs,
emane_configs=emane_configs,
) )
def set_wlan(self, config: Dict[str, str]) -> None: def set_wlan(self, config: Dict[str, str]) -> None:
@ -796,32 +878,6 @@ class Session:
x.node_type: set(x.services) for x in proto.default_services x.node_type: set(x.services) for x in proto.default_services
} }
hooks = {x.file: Hook.from_proto(x) for x in proto.hooks} hooks = {x.file: Hook.from_proto(x) for x in proto.hooks}
# update nodes with their current configurations
for model in proto.emane_model_configs:
iface_id = None
if model.iface_id != -1:
iface_id = model.iface_id
node = nodes[model.node_id]
key = (model.model, iface_id)
node.emane_model_configs[key] = ConfigOption.from_dict(model.config)
for node_id, mapped_config in proto.wlan_configs.items():
node = nodes[node_id]
node.wlan_config = ConfigOption.from_dict(mapped_config.config)
for config in proto.service_configs:
service = config.service
node = nodes[config.node_id]
node.service_configs[service] = NodeServiceData.from_proto(config.data)
for file, data in config.files.items():
files = node.service_file_configs.setdefault(service, {})
files[file] = data
for config in proto.config_service_configs:
node = nodes[config.node_id]
node.config_service_configs[config.name] = ConfigServiceData(
templates=dict(config.templates), config=dict(config.config)
)
for node_id, mapped_config in proto.mobility_configs.items():
node = nodes[node_id]
node.mobility_config = ConfigOption.from_dict(mapped_config.config)
file_path = Path(proto.file) if proto.file else None file_path = Path(proto.file) if proto.file else None
options = ConfigOption.from_dict(proto.options) options = ConfigOption.from_dict(proto.options)
servers = [Server.from_proto(x) for x in proto.servers] servers = [Server.from_proto(x) for x in proto.servers]
@ -841,6 +897,35 @@ class Session:
servers=servers, servers=servers,
) )
def to_proto(self) -> core_pb2.Session:
nodes = [x.to_proto() for x in self.nodes.values()]
links = [x.to_proto() for x in self.links]
hooks = [x.to_proto() for x in self.hooks.values()]
options = {k: v.to_proto() for k, v in self.options.items()}
servers = [x.to_proto() for x in self.servers]
default_services = []
for node_type, services in self.default_services.items():
default_service = services_pb2.ServiceDefaults(
node_type=node_type, services=services
)
default_services.append(default_service)
file = str(self.file) if self.file else None
return core_pb2.Session(
id=self.id,
state=self.state.value,
nodes=nodes,
links=links,
dir=self.dir,
user=self.user,
default_services=default_services,
location=self.location.to_proto(),
hooks=hooks,
metadata=self.metadata,
file=file,
options=options,
servers=servers,
)
def add_node( def add_node(
self, self,
_id: int, _id: int,

View file

@ -439,7 +439,7 @@ class CoreClient:
def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]: def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]:
if not definition: if not definition:
self.ifaces_manager.set_macs([x.link for x in self.links.values()]) self.ifaces_manager.set_macs([x.link for x in self.links.values()])
links, asym_links = [], [] links = []
for edge in self.links.values(): for edge in self.links.values():
link = edge.link link = edge.link
if not definition: if not definition:
@ -449,12 +449,11 @@ class CoreClient:
link.iface2.mac = self.ifaces_manager.next_mac() link.iface2.mac = self.ifaces_manager.next_mac()
links.append(link) links.append(link)
if edge.asymmetric_link: if edge.asymmetric_link:
asym_links.append(edge.asymmetric_link) links.append(edge.asymmetric_link)
return links, asym_links return links
def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]: def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]:
links, asym_links = self.get_links(definition) self.session.links = self.get_links(definition)
self.session.links = links
self.session.metadata = self.get_metadata() self.session.metadata = self.get_metadata()
self.session.servers.clear() self.session.servers.clear()
for server in self.servers.values(): for server in self.servers.values():
@ -462,9 +461,7 @@ class CoreClient:
result = False result = False
exceptions = [] exceptions = []
try: try:
result, exceptions = self.client.start_session( result, exceptions = self.client.start_session(self.session, definition)
self.session, asym_links, definition
)
logger.info( logger.info(
"start session(%s) definition(%s), result: %s", "start session(%s) definition(%s), result: %s",
self.session.id, self.session.id,

View file

@ -310,9 +310,9 @@ class ConfigServiceConfigDialog(Dialog):
current_listbox.itemconfig(current_listbox.curselection()[0], bg="") current_listbox.itemconfig(current_listbox.curselection()[0], bg="")
self.destroy() self.destroy()
return return
service_config = self.node.config_service_configs.get(self.service_name) service_config = self.node.config_service_configs.setdefault(
if not service_config: self.service_name, ConfigServiceData()
service_config = ConfigServiceData() )
if self.config_frame: if self.config_frame:
self.config_frame.parse_config() self.config_frame.parse_config()
service_config.config = {x.name: x.value for x in self.config.values()} service_config.config = {x.name: x.value for x in self.config.values()}

View file

@ -31,6 +31,7 @@ class NodeConfigServiceDialog(Dialog):
if services is None: if services is None:
services = set(node.config_services) services = set(node.config_services)
self.current_services: Set[str] = services self.current_services: Set[str] = services
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
self.draw() self.draw()
def draw(self) -> None: def draw(self) -> None:
@ -131,13 +132,20 @@ class NodeConfigServiceDialog(Dialog):
if self.is_custom_service(name): if self.is_custom_service(name):
self.current.listbox.itemconfig(tk.END, bg="green") self.current.listbox.itemconfig(tk.END, bg="green")
def cleanup_custom_services(self) -> None:
for service in list(self.node.config_service_configs):
if service not in self.node.config_services:
self.node.config_service_configs.pop(service)
def click_save(self) -> None: def click_save(self) -> None:
self.node.config_services = self.current_services.copy() self.node.config_services = self.current_services.copy()
self.cleanup_custom_services()
logger.info("saved node config services: %s", self.node.config_services) logger.info("saved node config services: %s", self.node.config_services)
self.destroy() self.destroy()
def click_cancel(self) -> None: def click_cancel(self) -> None:
self.current_services = None self.current_services = None
self.cleanup_custom_services()
self.destroy() self.destroy()
def click_remove(self) -> None: def click_remove(self) -> None:

View file

@ -25,6 +25,7 @@ class NodeServiceDialog(Dialog):
self.current: Optional[ListboxScroll] = None self.current: Optional[ListboxScroll] = None
services = set(node.services) services = set(node.services)
self.current_services: Set[str] = services self.current_services: Set[str] = services
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
self.draw() self.draw()
def draw(self) -> None: def draw(self) -> None:
@ -77,7 +78,7 @@ class NodeServiceDialog(Dialog):
button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button.grid(row=0, column=1, sticky=tk.EW, padx=PADX)
button = ttk.Button(frame, text="Remove", command=self.click_remove) button = ttk.Button(frame, text="Remove", command=self.click_remove)
button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) button.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy) button = ttk.Button(frame, text="Cancel", command=self.click_cancel)
button.grid(row=0, column=3, sticky=tk.EW) button.grid(row=0, column=3, sticky=tk.EW)
# trigger group change # trigger group change
@ -125,8 +126,21 @@ class NodeServiceDialog(Dialog):
"Service Configuration", "Select a service to configure", parent=self "Service Configuration", "Select a service to configure", parent=self
) )
def cleanup_custom_services(self) -> None:
for service in list(self.node.service_configs):
if service not in self.node.services:
self.node.service_configs.pop(service)
for service in list(self.node.service_file_configs):
if service not in self.node.services:
self.node.service_file_configs.pop(service)
def click_cancel(self) -> None:
self.cleanup_custom_services()
self.destroy()
def click_save(self) -> None: def click_save(self) -> None:
self.node.services = self.current_services.copy() self.node.services = self.current_services.copy()
self.cleanup_custom_services()
self.destroy() self.destroy()
def click_remove(self) -> None: def click_remove(self) -> None:

View file

@ -655,6 +655,7 @@ class CoreNode(CoreNodeBase):
:param dir_path: path to create :param dir_path: path to create
:return: nothing :return: nothing
""" """
logger.info("creating private directory: %s", dir_path)
if not str(dir_path).startswith("/"): if not str(dir_path).startswith("/"):
raise CoreError(f"private directory path not fully qualified: {dir_path}") raise CoreError(f"private directory path not fully qualified: {dir_path}")
host_path = self.host_path(dir_path, is_dir=True) host_path = self.host_path(dir_path, is_dir=True)

View file

@ -135,23 +135,8 @@ message GetConfigResponse {
message StartSessionRequest { message StartSessionRequest {
int32 session_id = 1; Session session = 1;
repeated Node nodes = 2; bool definition = 2;
repeated Link links = 3;
repeated Hook hooks = 4;
SessionLocation location = 5;
repeated wlan.WlanConfig wlan_configs = 6;
repeated emane.EmaneModelConfig emane_model_configs = 7;
repeated mobility.MobilityConfig mobility_configs = 8;
repeated services.ServiceConfig service_configs = 9;
repeated services.ServiceFileConfig service_file_configs = 10;
repeated Link asymmetric_links = 11;
repeated configservices.ConfigServiceConfig config_service_configs = 12;
map<string, string> options = 13;
string user = 14;
bool definition = 15;
map<string, string> metadata = 16;
repeated Server servers = 17;
} }
message StartSessionResponse { message StartSessionResponse {
@ -577,15 +562,10 @@ message Session {
repeated services.ServiceDefaults default_services = 7; repeated services.ServiceDefaults default_services = 7;
SessionLocation location = 8; SessionLocation location = 8;
repeated Hook hooks = 9; repeated Hook hooks = 9;
repeated emane.GetEmaneModelConfig emane_model_configs = 10; map<string, string> metadata = 10;
map<int32, common.MappedConfig> wlan_configs = 11; string file = 11;
repeated services.NodeServiceConfig service_configs = 12; map<string, common.ConfigOption> options = 12;
repeated configservices.ConfigServiceConfig config_service_configs = 13; repeated Server servers = 13;
map<int32, common.MappedConfig> mobility_configs = 14;
map<string, string> metadata = 15;
string file = 16;
map<string, common.ConfigOption> options = 17;
repeated Server servers = 18;
} }
message SessionSummary { message SessionSummary {
@ -612,6 +592,11 @@ message Node {
string dir = 13; string dir = 13;
string channel = 14; string channel = 14;
int32 canvas = 15; int32 canvas = 15;
map<string, common.ConfigOption> wlan_config = 16;
map<string, common.ConfigOption> mobility_config = 17;
map<string, services.NodeServiceConfig> service_configs = 18;
map<string, configservices.ConfigServiceConfig> config_service_configs= 19;
repeated emane.NodeEmaneConfig emane_configs = 20;
} }
message Link { message Link {

View file

@ -31,6 +31,12 @@ message GetEmaneModelConfig {
map<string, common.ConfigOption> config = 4; map<string, common.ConfigOption> config = 4;
} }
message NodeEmaneConfig {
int32 iface_id = 1;
string model = 2;
map<string, common.ConfigOption> config = 3;
}
message GetEmaneEventChannelRequest { message GetEmaneEventChannelRequest {
int32 session_id = 1; int32 session_id = 1;
int32 nem_id = 2; int32 nem_id = 2;