Merge branch 'coretk' into coretk-color

This commit is contained in:
Huy Pham 2019-12-16 08:03:21 -08:00
commit bd2eaa8966
9 changed files with 405 additions and 247 deletions

View file

@ -242,27 +242,17 @@ class CoreClient:
mapped_config = response.configs[_id] mapped_config = response.configs[_id]
self.wlan_configs[_id] = mapped_config.config self.wlan_configs[_id] = mapped_config.config
# save and retrieve data, needed for session nodes # get service configurations
for node in session.nodes: response = self.client.get_node_service_configs(self.session_id)
# get node service config and file config for config in response.configs:
# retrieve service configurations data for default nodes service_configs = self.service_configs.setdefault(config.node_id, {})
if node.type == core_pb2.NodeType.DEFAULT: service_configs[config.service] = config.data
for service in node.services: logging.info("service file configs: %s", config.files)
response = self.client.get_node_service( for file_name in config.files:
self.session_id, node.id, service file_configs = self.file_configs.setdefault(config.node_id, {})
) files = file_configs.setdefault(config.service, {})
if node.id not in self.service_configs: data = config.files[file_name]
self.service_configs[node.id] = {} files[file_name] = data
self.service_configs[node.id][service] = response.service
for file in response.service.configs:
response = self.client.get_node_service_file(
self.session_id, node.id, service, file
)
if node.id not in self.file_configs:
self.file_configs[node.id] = {}
if service not in self.file_configs[node.id]:
self.file_configs[node.id][service] = {}
self.file_configs[node.id][service][file] = response.data
# draw session # draw session
self.app.canvas.reset_and_redraw(session) self.app.canvas.reset_and_redraw(session)

View file

@ -323,7 +323,7 @@ class ServiceConfiguration(Dialog):
button = ttk.Button(frame, text="Apply", command=self.click_apply) button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX) button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button( button = ttk.Button(
frame, text="Dafults", command=self.click_defaults, state="disabled" frame, text="Defaults", command=self.click_defaults, state="disabled"
) )
button.grid(row=0, column=1, sticky="ew", padx=PADX) button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button( button = ttk.Button(

View file

@ -378,16 +378,17 @@ class CoreGrpcClient:
) )
return self.stub.AddSessionServer(request) return self.stub.AddSessionServer(request)
def events(self, session_id, handler): def events(self, session_id, handler, events=None):
""" """
Listen for session events. Listen for session events.
:param int session_id: id of session :param int session_id: id of session
:param handler: handler for every event :param handler: handler for received events
:param list events: events to listen to, defaults to all
:return: nothing :return: nothing
:raises grpc.RpcError: when session doesn't exist :raises grpc.RpcError: when session doesn't exist
""" """
request = core_pb2.EventsRequest(session_id=session_id) request = core_pb2.EventsRequest(session_id=session_id, events=events)
stream = self.stub.Events(request) stream = self.stub.Events(request)
start_streamer(stream, handler) start_streamer(stream, handler)
@ -730,6 +731,18 @@ class CoreGrpcClient:
) )
return self.stub.SetServiceDefaults(request) return self.stub.SetServiceDefaults(request)
def get_node_service_configs(self, session_id):
"""
Get service data for a node.
:param int session_id: session id
:return: response with all node service configs
:rtype: core_pb2.GetNodeServiceConfigsResponse
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetNodeServiceConfigsRequest(session_id=session_id)
return self.stub.GetNodeServiceConfigs(request)
def get_node_service(self, session_id, node_id, service): def get_node_service(self, session_id, node_id, service):
""" """
Get service data for a node. Get service data for a node.

View file

@ -0,0 +1,268 @@
import logging
from queue import Empty, Queue
from core.api.grpc import core_pb2
from core.api.grpc.grpcutils import convert_value
from core.emulator.data import (
ConfigData,
EventData,
ExceptionData,
FileData,
LinkData,
NodeData,
)
def handle_node_event(event):
"""
Handle node event when there is a node event
:param core.emulator.data.NodeData event: node data
:return: node event that contains node id, name, model, position, and services
:rtype: core.api.grpc.core_pb2.NodeEvent
"""
position = core_pb2.Position(x=event.x_position, y=event.y_position)
services = event.services or ""
services = services.split("|")
node_proto = core_pb2.Node(
id=event.id,
name=event.name,
model=event.model,
position=position,
services=services,
)
return core_pb2.NodeEvent(node=node_proto, source=event.source)
def handle_link_event(event):
"""
Handle link event when there is a link event
:param core.emulator.data.LinkData event: link data
:return: link event that has message type and link information
:rtype: core.api.grpc.core_pb2.LinkEvent
"""
interface_one = None
if event.interface1_id is not None:
interface_one = core_pb2.Interface(
id=event.interface1_id,
name=event.interface1_name,
mac=convert_value(event.interface1_mac),
ip4=convert_value(event.interface1_ip4),
ip4mask=event.interface1_ip4_mask,
ip6=convert_value(event.interface1_ip6),
ip6mask=event.interface1_ip6_mask,
)
interface_two = None
if event.interface2_id is not None:
interface_two = core_pb2.Interface(
id=event.interface2_id,
name=event.interface2_name,
mac=convert_value(event.interface2_mac),
ip4=convert_value(event.interface2_ip4),
ip4mask=event.interface2_ip4_mask,
ip6=convert_value(event.interface2_ip6),
ip6mask=event.interface2_ip6_mask,
)
options = core_pb2.LinkOptions(
opaque=event.opaque,
jitter=event.jitter,
key=event.key,
mburst=event.mburst,
mer=event.mer,
per=event.per,
bandwidth=event.bandwidth,
burst=event.burst,
delay=event.delay,
dup=event.dup,
unidirectional=event.unidirectional,
)
link = core_pb2.Link(
type=event.link_type,
node_one_id=event.node1_id,
node_two_id=event.node2_id,
interface_one=interface_one,
interface_two=interface_two,
options=options,
)
return core_pb2.LinkEvent(message_type=event.message_type, link=link)
def handle_session_event(event):
"""
Handle session event when there is a session event
:param core.emulator.data.EventData event: event data
:return: session event
:rtype: core.api.grpc.core_pb2.SessionEvent
"""
event_time = event.time
if event_time is not None:
event_time = float(event_time)
return core_pb2.SessionEvent(
node_id=event.node,
event=event.event_type,
name=event.name,
data=event.data,
time=event_time,
session_id=event.session,
)
def handle_config_event(event):
"""
Handle configuration event when there is configuration event
:param core.emulator.data.ConfigData event: configuration data
:return: configuration event
:rtype: core.api.grpc.core_pb2.ConfigEvent
"""
session_id = None
if event.session is not None:
session_id = int(event.session)
return core_pb2.ConfigEvent(
message_type=event.message_type,
node_id=event.node,
object=event.object,
type=event.type,
captions=event.captions,
bitmap=event.bitmap,
data_values=event.data_values,
possible_values=event.possible_values,
groups=event.groups,
session_id=session_id,
interface=event.interface_number,
network_id=event.network_id,
opaque=event.opaque,
data_types=event.data_types,
)
def handle_exception_event(event):
"""
Handle exception event when there is exception event
:param core.emulator.data.ExceptionData event: exception data
:return: exception event
:rtype: core.api.grpc.core_pb2.ExceptionEvent
"""
return core_pb2.ExceptionEvent(
node_id=event.node,
session_id=int(event.session),
level=event.level,
source=event.source,
date=event.date,
text=event.text,
opaque=event.opaque,
)
def handle_file_event(event):
"""
Handle file event
:param core.emulator.data.FileData event: file data
:return: file event
:rtype: core.api.grpc.core_pb2.FileEvent
"""
return core_pb2.FileEvent(
message_type=event.message_type,
node_id=event.node,
name=event.name,
mode=event.mode,
number=event.number,
type=event.type,
source=event.source,
session_id=event.session,
data=event.data,
compressed_data=event.compressed_data,
)
class EventStreamer:
"""
Processes session events to generate grpc events.
"""
def __init__(self, session, event_types):
"""
Create a EventStreamer instance.
:param core.emulator.session.Session session: session to process events for
:param set event_types: types of events to process
"""
self.session = session
self.event_types = event_types
self.queue = Queue()
self.add_handlers()
def add_handlers(self):
"""
Add a session event handler for desired event types.
:return: nothing
"""
if core_pb2.EventType.NODE in self.event_types:
self.session.node_handlers.append(self.queue.put)
if core_pb2.EventType.LINK in self.event_types:
self.session.link_handlers.append(self.queue.put)
if core_pb2.EventType.CONFIG in self.event_types:
self.session.config_handlers.append(self.queue.put)
if core_pb2.EventType.FILE in self.event_types:
self.session.file_handlers.append(self.queue.put)
if core_pb2.EventType.EXCEPTION in self.event_types:
self.session.exception_handlers.append(self.queue.put)
if core_pb2.EventType.SESSION in self.event_types:
self.session.event_handlers.append(self.queue.put)
def process(self):
"""
Process the next event in the queue.
:return: grpc event, or None when invalid event or queue timeout
:rtype: core.api.grpc.core_pb2.Event
"""
event = core_pb2.Event()
try:
data = self.queue.get(timeout=1)
if isinstance(data, NodeData):
event.node_event.CopyFrom(handle_node_event(data))
elif isinstance(data, LinkData):
event.link_event.CopyFrom(handle_link_event(data))
elif isinstance(data, EventData):
event.session_event.CopyFrom(handle_session_event(data))
elif isinstance(data, ConfigData):
event.config_event.CopyFrom(handle_config_event(data))
# TODO: remove when config events are fixed
event.config_event.session_id = self.session.id
elif isinstance(data, ExceptionData):
event.exception_event.CopyFrom(handle_exception_event(data))
elif isinstance(data, FileData):
event.file_event.CopyFrom(handle_file_event(data))
else:
logging.error("unknown event: %s", data)
event = None
except Empty:
event = None
return event
def remove_handlers(self):
"""
Remove session event handlers for events being watched.
:return: nothing
"""
if core_pb2.EventType.NODE in self.event_types:
self.session.node_handlers.remove(self.queue.put)
if core_pb2.EventType.LINK in self.event_types:
self.session.link_handlers.remove(self.queue.put)
if core_pb2.EventType.CONFIG in self.event_types:
self.session.config_handlers.remove(self.queue.put)
if core_pb2.EventType.FILE in self.event_types:
self.session.file_handlers.remove(self.queue.put)
if core_pb2.EventType.EXCEPTION in self.event_types:
self.session.exception_handlers.remove(self.queue.put)
if core_pb2.EventType.SESSION in self.event_types:
self.session.event_handlers.remove(self.queue.put)

View file

@ -352,3 +352,25 @@ def service_configuration(session, config):
service.startup = tuple(config.startup) service.startup = tuple(config.startup)
service.validate = tuple(config.validate) service.validate = tuple(config.validate)
service.shutdown = tuple(config.shutdown) service.shutdown = tuple(config.shutdown)
def get_service_configuration(service):
"""
Convenience for converting a service to service data proto.
:param service: service to get proto data for
:return: service proto data
:rtype: core.api.grpc.core_pb2.NodeServiceData
"""
return core_pb2.NodeServiceData(
executables=service.executables,
dependencies=service.dependencies,
dirs=service.dirs,
configs=service.configs,
startup=service.startup,
validate=service.validate,
validation_mode=service.validation_mode.value,
validation_timer=service.validation_timer,
shutdown=service.shutdown,
meta=service.meta,
)

View file

@ -5,27 +5,19 @@ import re
import tempfile import tempfile
import time import time
from concurrent import futures from concurrent import futures
from queue import Empty, Queue
import grpc import grpc
from core.api.grpc import core_pb2, core_pb2_grpc, grpcutils from core.api.grpc import core_pb2, core_pb2_grpc, grpcutils
from core.api.grpc.events import EventStreamer
from core.api.grpc.grpcutils import ( from core.api.grpc.grpcutils import (
convert_value,
get_config_options, get_config_options,
get_emane_model_id, get_emane_model_id,
get_links, get_links,
get_net_stats, get_net_stats,
) )
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.data import ( from core.emulator.data import LinkData
ConfigData,
EventData,
ExceptionData,
FileData,
LinkData,
NodeData,
)
from core.emulator.emudata import LinkOptions, NodeOptions from core.emulator.emudata import LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
@ -439,210 +431,19 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
def Events(self, request, context): def Events(self, request, context):
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
queue = Queue() event_types = set(request.events)
session.node_handlers.append(queue.put) if not event_types:
session.link_handlers.append(queue.put) event_types = set(core_pb2.EventType.Enum.values())
session.config_handlers.append(queue.put)
session.file_handlers.append(queue.put)
session.exception_handlers.append(queue.put)
session.event_handlers.append(queue.put)
streamer = EventStreamer(session, event_types)
while self._is_running(context): while self._is_running(context):
event = core_pb2.Event() event = streamer.process()
try: if event:
data = queue.get(timeout=1)
if isinstance(data, NodeData):
event.node_event.CopyFrom(self._handle_node_event(data))
elif isinstance(data, LinkData):
event.link_event.CopyFrom(self._handle_link_event(data))
elif isinstance(data, EventData):
event.session_event.CopyFrom(self._handle_session_event(data))
elif isinstance(data, ConfigData):
event.config_event.CopyFrom(self._handle_config_event(data))
# TODO: remove when config events are fixed
event.config_event.session_id = session.id
elif isinstance(data, ExceptionData):
event.exception_event.CopyFrom(self._handle_exception_event(data))
elif isinstance(data, FileData):
event.file_event.CopyFrom(self._handle_file_event(data))
else:
logging.error("unknown event: %s", data)
continue
yield event yield event
except Empty:
continue
session.node_handlers.remove(queue.put) streamer.remove_handlers()
session.link_handlers.remove(queue.put)
session.config_handlers.remove(queue.put)
session.file_handlers.remove(queue.put)
session.exception_handlers.remove(queue.put)
session.event_handlers.remove(queue.put)
self._cancel_stream(context) self._cancel_stream(context)
def _handle_node_event(self, event):
"""
Handle node event when there is a node event
:param core.emulator.data.NodeData event: node data
:return: node event that contains node id, name, model, position, and services
:rtype: core.api.grpc.core_pb2.NodeEvent
"""
position = core_pb2.Position(x=event.x_position, y=event.y_position)
services = event.services or ""
services = services.split("|")
node_proto = core_pb2.Node(
id=event.id,
name=event.name,
model=event.model,
position=position,
services=services,
)
return core_pb2.NodeEvent(node=node_proto, source=event.source)
def _handle_link_event(self, event):
"""
Handle link event when there is a link event
:param core.emulator.data.LinkData event: link data
:return: link event that has message type and link information
:rtype: core.api.grpc.core_pb2.LinkEvent
"""
interface_one = None
if event.interface1_id is not None:
interface_one = core_pb2.Interface(
id=event.interface1_id,
name=event.interface1_name,
mac=convert_value(event.interface1_mac),
ip4=convert_value(event.interface1_ip4),
ip4mask=event.interface1_ip4_mask,
ip6=convert_value(event.interface1_ip6),
ip6mask=event.interface1_ip6_mask,
)
interface_two = None
if event.interface2_id is not None:
interface_two = core_pb2.Interface(
id=event.interface2_id,
name=event.interface2_name,
mac=convert_value(event.interface2_mac),
ip4=convert_value(event.interface2_ip4),
ip4mask=event.interface2_ip4_mask,
ip6=convert_value(event.interface2_ip6),
ip6mask=event.interface2_ip6_mask,
)
options = core_pb2.LinkOptions(
opaque=event.opaque,
jitter=event.jitter,
key=event.key,
mburst=event.mburst,
mer=event.mer,
per=event.per,
bandwidth=event.bandwidth,
burst=event.burst,
delay=event.delay,
dup=event.dup,
unidirectional=event.unidirectional,
)
link = core_pb2.Link(
type=event.link_type,
node_one_id=event.node1_id,
node_two_id=event.node2_id,
interface_one=interface_one,
interface_two=interface_two,
options=options,
)
return core_pb2.LinkEvent(message_type=event.message_type, link=link)
def _handle_session_event(self, event):
"""
Handle session event when there is a session event
:param core.emulator.data.EventData event: event data
:return: session event
:rtype: core.api.grpc.core_pb2.SessionEvent
"""
event_time = event.time
if event_time is not None:
event_time = float(event_time)
return core_pb2.SessionEvent(
node_id=event.node,
event=event.event_type,
name=event.name,
data=event.data,
time=event_time,
session_id=event.session,
)
def _handle_config_event(self, event):
"""
Handle configuration event when there is configuration event
:param core.emulator.data.ConfigData event: configuration data
:return: configuration event
:rtype: core.api.grpc.core_pb2.ConfigEvent
"""
session_id = None
if event.session is not None:
session_id = int(event.session)
return core_pb2.ConfigEvent(
message_type=event.message_type,
node_id=event.node,
object=event.object,
type=event.type,
captions=event.captions,
bitmap=event.bitmap,
data_values=event.data_values,
possible_values=event.possible_values,
groups=event.groups,
session_id=session_id,
interface=event.interface_number,
network_id=event.network_id,
opaque=event.opaque,
data_types=event.data_types,
)
def _handle_exception_event(self, event):
"""
Handle exception event when there is exception event
:param core.emulator.data.ExceptionData event: exception data
:return: exception event
:rtype: core.api.grpc.core_pb2.ExceptionEvent
"""
return core_pb2.ExceptionEvent(
node_id=event.node,
session_id=int(event.session),
level=event.level,
source=event.source,
date=event.date,
text=event.text,
opaque=event.opaque,
)
def _handle_file_event(self, event):
"""
Handle file event
:param core.emulator.data.FileData event: file data
:return: file event
:rtype: core.api.grpc.core_pb2.FileEvent
"""
return core_pb2.FileEvent(
message_type=event.message_type,
node_id=event.node,
name=event.name,
mode=event.mode,
number=event.number,
type=event.type,
source=event.source,
session_id=event.session,
data=event.data,
compressed_data=event.compressed_data,
)
def Throughputs(self, request, context): def Throughputs(self, request, context):
""" """
Calculate average throughput after every certain amount of delay time Calculate average throughput after every certain amount of delay time
@ -1116,6 +917,32 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
] = service_defaults.services ] = service_defaults.services
return core_pb2.SetServiceDefaultsResponse(result=True) return core_pb2.SetServiceDefaultsResponse(result=True)
def GetNodeServiceConfigs(self, request, context):
"""
Retrieve all node service configurations.
:param core.api.grpc.core_pb2.GetNodeServiceConfigsRequest request:
get-node-service request
:param grpc.ServicerContext context: context object
:return: all node service configs response
:rtype: core.api.grpc.core_pb2.GetNodeServiceConfigsResponse
"""
logging.debug("get node service configs: %s", request)
session = self.get_session(request.session_id, context)
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 = grpcutils.get_service_configuration(service)
config = core_pb2.GetNodeServiceConfigsResponse.ServiceConfig(
node_id=node_id,
service=name,
data=service_proto,
files=service.config_data,
)
configs.append(config)
return core_pb2.GetNodeServiceConfigsResponse(configs=configs)
def GetNodeService(self, request, context): def GetNodeService(self, request, context):
""" """
Retrieve a requested service from a node Retrieve a requested service from a node
@ -1131,18 +958,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
service = session.services.get_service( service = session.services.get_service(
request.node_id, request.service, default_service=True request.node_id, request.service, default_service=True
) )
service_proto = core_pb2.NodeServiceData( service_proto = grpcutils.get_service_configuration(service)
executables=service.executables,
dependencies=service.dependencies,
dirs=service.dirs,
configs=service.configs,
startup=service.startup,
validate=service.validate,
validation_mode=service.validation_mode.value,
validation_timer=service.validation_timer,
shutdown=service.shutdown,
meta=service.meta,
)
return core_pb2.GetNodeServiceResponse(service=service_proto) return core_pb2.GetNodeServiceResponse(service=service_proto)
def GetNodeServiceFile(self, request, context): def GetNodeServiceFile(self, request, context):

View file

@ -461,8 +461,8 @@ class CoreServices:
:param core.netns.vnode.LxcNode node: node to start services on :param core.netns.vnode.LxcNode node: node to start services on
:return: nothing :return: nothing
""" """
funcs = []
boot_paths = ServiceDependencies(node.services).boot_paths() boot_paths = ServiceDependencies(node.services).boot_paths()
funcs = []
for boot_path in boot_paths: for boot_path in boot_paths:
args = (node, boot_path) args = (node, boot_path)
funcs.append((self._start_boot_paths, args, {})) funcs.append((self._start_boot_paths, args, {}))
@ -484,6 +484,7 @@ class CoreServices:
" -> ".join([x.name for x in boot_path]), " -> ".join([x.name for x in boot_path]),
) )
for service in boot_path: for service in boot_path:
service = self.get_service(node.id, service.name, default_service=True)
try: try:
self.boot_service(node, service) self.boot_service(node, service)
except Exception: except Exception:
@ -744,7 +745,9 @@ class CoreServices:
config_files = service.get_configs(node) config_files = service.get_configs(node)
for file_name in config_files: for file_name in config_files:
logging.debug("generating service config: %s", file_name) logging.debug(
"generating service config custom(%s): %s", service.custom, file_name
)
if service.custom: if service.custom:
cfg = service.config_data.get(file_name) cfg = service.config_data.get(file_name)
if cfg is None: if cfg is None:

View file

@ -89,6 +89,8 @@ service CoreApi {
} }
rpc SetServiceDefaults (SetServiceDefaultsRequest) returns (SetServiceDefaultsResponse) { rpc SetServiceDefaults (SetServiceDefaultsRequest) returns (SetServiceDefaultsResponse) {
} }
rpc GetNodeServiceConfigs (GetNodeServiceConfigsRequest) returns (GetNodeServiceConfigsResponse) {
}
rpc GetNodeService (GetNodeServiceRequest) returns (GetNodeServiceResponse) { rpc GetNodeService (GetNodeServiceRequest) returns (GetNodeServiceResponse) {
} }
rpc GetNodeServiceFile (GetNodeServiceFileRequest) returns (GetNodeServiceFileResponse) { rpc GetNodeServiceFile (GetNodeServiceFileRequest) returns (GetNodeServiceFileResponse) {
@ -266,6 +268,7 @@ message AddSessionServerResponse {
message EventsRequest { message EventsRequest {
int32 session_id = 1; int32 session_id = 1;
repeated EventType.Enum events = 2;
} }
message ThroughputsRequest { message ThroughputsRequest {
@ -537,6 +540,20 @@ message SetServiceDefaultsResponse {
bool result = 1; bool result = 1;
} }
message GetNodeServiceConfigsRequest {
int32 session_id = 1;
}
message GetNodeServiceConfigsResponse {
message ServiceConfig {
int32 node_id = 1;
string service = 2;
NodeServiceData data = 3;
map<string, string> files = 4;
}
repeated ServiceConfig configs = 1;
}
message GetNodeServiceRequest { message GetNodeServiceRequest {
int32 session_id = 1; int32 session_id = 1;
int32 node_id = 2; int32 node_id = 2;
@ -742,6 +759,17 @@ message ServiceFileConfig {
string data = 4; string data = 4;
} }
message EventType {
enum Enum {
SESSION = 0;
NODE = 1;
LINK = 2;
CONFIG = 3;
EXCEPTION = 4;
FILE = 5;
}
}
message MessageType { message MessageType {
enum Enum { enum Enum {
NONE = 0; NONE = 0;

View file

@ -878,6 +878,24 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert session.services.default_services[node_type] == services assert session.services.default_services[node_type] == services
def test_get_node_service_configs(self, grpc_server):
# given
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
node = session.add_node()
service_name = "DefaultRoute"
session.services.set_service(node.id, service_name)
# then
with client.context_connect():
response = client.get_node_service_configs(session.id)
# then
assert len(response.configs) == 1
service_config = response.configs[0]
assert service_config.node_id == node.id
assert service_config.service == service_name
def test_get_node_service(self, grpc_server): def test_get_node_service(self, grpc_server):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()