added files to config services, added default logic for creating files from templates, added new method to provide extra data to templates, updated gui to display templates raw
This commit is contained in:
parent
9447ddb94f
commit
7b5df11dc7
11 changed files with 120 additions and 52 deletions
|
@ -15,6 +15,8 @@ from core.api.grpc import core_pb2, core_pb2_grpc
|
||||||
from core.api.grpc.configservices_pb2 import (
|
from core.api.grpc.configservices_pb2 import (
|
||||||
GetConfigServicesRequest,
|
GetConfigServicesRequest,
|
||||||
GetConfigServicesResponse,
|
GetConfigServicesResponse,
|
||||||
|
GetConfigServiceTemplatesRequest,
|
||||||
|
GetConfigServiceTemplatesResponse,
|
||||||
GetNodeConfigServiceRequest,
|
GetNodeConfigServiceRequest,
|
||||||
GetNodeConfigServiceResponse,
|
GetNodeConfigServiceResponse,
|
||||||
GetNodeConfigServicesRequest,
|
GetNodeConfigServicesRequest,
|
||||||
|
@ -1092,6 +1094,12 @@ class CoreGrpcClient:
|
||||||
request = GetConfigServicesRequest()
|
request = GetConfigServicesRequest()
|
||||||
return self.stub.GetConfigServices(request)
|
return self.stub.GetConfigServices(request)
|
||||||
|
|
||||||
|
def get_config_service_templates(
|
||||||
|
self, name: str
|
||||||
|
) -> GetConfigServiceTemplatesResponse:
|
||||||
|
request = GetConfigServiceTemplatesRequest(name=name)
|
||||||
|
return self.stub.GetConfigServiceTemplates(request)
|
||||||
|
|
||||||
def get_node_config_service(
|
def get_node_config_service(
|
||||||
self, session_id: int, node_id: int, name: str
|
self, session_id: int, node_id: int, name: str
|
||||||
) -> GetNodeConfigServiceResponse:
|
) -> GetNodeConfigServiceResponse:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import re
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from grpc import ServicerContext
|
from grpc import ServicerContext
|
||||||
|
@ -14,6 +15,8 @@ from core.api.grpc.configservices_pb2 import (
|
||||||
ConfigService,
|
ConfigService,
|
||||||
GetConfigServicesRequest,
|
GetConfigServicesRequest,
|
||||||
GetConfigServicesResponse,
|
GetConfigServicesResponse,
|
||||||
|
GetConfigServiceTemplatesRequest,
|
||||||
|
GetConfigServiceTemplatesResponse,
|
||||||
GetNodeConfigServiceRequest,
|
GetNodeConfigServiceRequest,
|
||||||
GetNodeConfigServiceResponse,
|
GetNodeConfigServiceResponse,
|
||||||
GetNodeConfigServicesRequest,
|
GetNodeConfigServicesRequest,
|
||||||
|
@ -112,9 +115,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
except CoreError:
|
except CoreError:
|
||||||
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
|
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
|
||||||
|
|
||||||
def validate_service(self, name: str, context: ServicerContext) -> None:
|
def validate_service(
|
||||||
if name not in self.coreemu.service_manager.services:
|
self, name: str, context: ServicerContext
|
||||||
|
) -> Type[ConfigService]:
|
||||||
|
service = self.coreemu.service_manager.services.get(name)
|
||||||
|
if not service:
|
||||||
context.abort(grpc.StatusCode.NOT_FOUND, f"unknown service {name}")
|
context.abort(grpc.StatusCode.NOT_FOUND, f"unknown service {name}")
|
||||||
|
return service
|
||||||
|
|
||||||
def StartSession(
|
def StartSession(
|
||||||
self, request: core_pb2.StartSessionRequest, context: ServicerContext
|
self, request: core_pb2.StartSessionRequest, context: ServicerContext
|
||||||
|
@ -1456,6 +1463,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
executables=service.executables,
|
executables=service.executables,
|
||||||
dependencies=service.dependencies,
|
dependencies=service.dependencies,
|
||||||
directories=service.directories,
|
directories=service.directories,
|
||||||
|
files=service.files,
|
||||||
startup=service.startup,
|
startup=service.startup,
|
||||||
validate=service.validate,
|
validate=service.validate,
|
||||||
shutdown=service.shutdown,
|
shutdown=service.shutdown,
|
||||||
|
@ -1481,6 +1489,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config = {x.id: x.default for x in service.default_configs}
|
config = {x.id: x.default for x in service.default_configs}
|
||||||
return GetNodeConfigServiceResponse(config=config)
|
return GetNodeConfigServiceResponse(config=config)
|
||||||
|
|
||||||
|
def GetConfigServiceTemplates(
|
||||||
|
self, request: GetConfigServiceTemplatesRequest, context: ServicerContext
|
||||||
|
) -> GetConfigServiceTemplatesResponse:
|
||||||
|
service_class = self.validate_service(request.name, context)
|
||||||
|
service = service_class(None)
|
||||||
|
templates = service.get_templates()
|
||||||
|
return GetConfigServiceTemplatesResponse(templates=templates)
|
||||||
|
|
||||||
def GetNodeConfigServices(
|
def GetNodeConfigServices(
|
||||||
self, request: GetNodeConfigServicesRequest, context: ServicerContext
|
self, request: GetNodeConfigServicesRequest, context: ServicerContext
|
||||||
) -> GetNodeConfigServicesResponse:
|
) -> GetNodeConfigServicesResponse:
|
||||||
|
|
|
@ -55,6 +55,11 @@ class ConfigService(abc.ABC):
|
||||||
def directories(self) -> List[str]:
|
def directories(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def files(self) -> List[str]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def default_configs(self) -> List[Configuration]:
|
def default_configs(self) -> List[Configuration]:
|
||||||
|
@ -120,8 +125,34 @@ class ConfigService(abc.ABC):
|
||||||
f"failure to create service directory: {directory}"
|
f"failure to create service directory: {directory}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def data(self) -> Dict[str, Any]:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_text(self, name: str) -> str:
|
||||||
|
raise CoreError(
|
||||||
|
f"node({self.node.name} service({self.name}) unknown template({name})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_templates(self) -> Dict[str, str]:
|
||||||
|
templates = {}
|
||||||
|
for name in self.files:
|
||||||
|
if self.templates.has_template(name):
|
||||||
|
template = self.templates.get_template(name).source
|
||||||
|
else:
|
||||||
|
template = self.get_text(name)
|
||||||
|
template = inspect.cleandoc(template)
|
||||||
|
templates[name] = template
|
||||||
|
return templates
|
||||||
|
|
||||||
def create_files(self) -> None:
|
def create_files(self) -> None:
|
||||||
raise NotImplementedError
|
data = self.data()
|
||||||
|
for name in self.files:
|
||||||
|
if self.templates.has_template(name):
|
||||||
|
self.render_template(name, data)
|
||||||
|
else:
|
||||||
|
text = self.get_text(name)
|
||||||
|
text = inspect.cleandoc(text)
|
||||||
|
self.render_text(name, text, data)
|
||||||
|
|
||||||
def run_startup(self) -> None:
|
def run_startup(self) -> None:
|
||||||
for cmd in self.startup:
|
for cmd in self.startup:
|
||||||
|
@ -176,7 +207,6 @@ class ConfigService(abc.ABC):
|
||||||
|
|
||||||
def render_text(self, name: str, text: str, data: Dict[str, Any] = None) -> None:
|
def render_text(self, name: str, text: str, data: Dict[str, Any] = None) -> None:
|
||||||
try:
|
try:
|
||||||
text = inspect.cleandoc(text)
|
|
||||||
template = Template(text)
|
template = Template(text)
|
||||||
self._render(name, template, data)
|
self._render(name, template, data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -7,6 +7,7 @@ class VpnClient(ConfigService):
|
||||||
name = "VPNClient"
|
name = "VPNClient"
|
||||||
group = GROUP_NAME
|
group = GROUP_NAME
|
||||||
directories = []
|
directories = []
|
||||||
|
files = ["vpnclient.sh"]
|
||||||
executables = ["openvpn", "ip", "killall"]
|
executables = ["openvpn", "ip", "killall"]
|
||||||
dependencies = []
|
dependencies = []
|
||||||
startup = ["sh vpnclient.sh"]
|
startup = ["sh vpnclient.sh"]
|
||||||
|
@ -14,6 +15,3 @@ class VpnClient(ConfigService):
|
||||||
shutdown = ["killall openvpn"]
|
shutdown = ["killall openvpn"]
|
||||||
validation_mode = ConfigServiceMode.BLOCKING
|
validation_mode = ConfigServiceMode.BLOCKING
|
||||||
default_configs = []
|
default_configs = []
|
||||||
|
|
||||||
def create_files(self):
|
|
||||||
self.render_template("vpnclient.sh")
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
@ -12,6 +14,7 @@ class DefaultRoute(ConfigService):
|
||||||
name = "DefaultRoute"
|
name = "DefaultRoute"
|
||||||
group = GROUP_NAME
|
group = GROUP_NAME
|
||||||
directories = []
|
directories = []
|
||||||
|
files = ["defaultroute.sh"]
|
||||||
executables = ["ip"]
|
executables = ["ip"]
|
||||||
dependencies = []
|
dependencies = []
|
||||||
startup = ["sh defaultroute.sh"]
|
startup = ["sh defaultroute.sh"]
|
||||||
|
@ -24,7 +27,7 @@ class DefaultRoute(ConfigService):
|
||||||
Configuration(_id="value3", _type=ConfigDataTypes.STRING, label="Value 3"),
|
Configuration(_id="value3", _type=ConfigDataTypes.STRING, label="Value 3"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def create_files(self):
|
def data(self) -> Dict[str, Any]:
|
||||||
addresses = []
|
addresses = []
|
||||||
for netif in self.node.netifs():
|
for netif in self.node.netifs():
|
||||||
if getattr(netif, "control", False):
|
if getattr(netif, "control", False):
|
||||||
|
@ -33,14 +36,14 @@ class DefaultRoute(ConfigService):
|
||||||
net = netaddr.IPNetwork(addr)
|
net = netaddr.IPNetwork(addr)
|
||||||
if net[1] != net[-2]:
|
if net[1] != net[-2]:
|
||||||
addresses.append(net[1])
|
addresses.append(net[1])
|
||||||
data = dict(addresses=addresses)
|
return dict(addresses=addresses)
|
||||||
self.render_template("defaultroute.sh", data)
|
|
||||||
|
|
||||||
|
|
||||||
class IpForwardService(ConfigService):
|
class IpForwardService(ConfigService):
|
||||||
name = "IPForward"
|
name = "IPForward"
|
||||||
group = GROUP_NAME
|
group = GROUP_NAME
|
||||||
directories = []
|
directories = []
|
||||||
|
files = ["ipforward.sh"]
|
||||||
executables = ["sysctl"]
|
executables = ["sysctl"]
|
||||||
dependencies = []
|
dependencies = []
|
||||||
startup = ["sh ipforward.sh"]
|
startup = ["sh ipforward.sh"]
|
||||||
|
@ -49,10 +52,9 @@ class IpForwardService(ConfigService):
|
||||||
validation_mode = ConfigServiceMode.BLOCKING
|
validation_mode = ConfigServiceMode.BLOCKING
|
||||||
default_configs = []
|
default_configs = []
|
||||||
|
|
||||||
def create_files(self) -> None:
|
def data(self) -> Dict[str, Any]:
|
||||||
devnames = []
|
devnames = []
|
||||||
for ifc in self.node.netifs():
|
for ifc in self.node.netifs():
|
||||||
devname = utils.sysctl_devname(ifc.name)
|
devname = utils.sysctl_devname(ifc.name)
|
||||||
devnames.append(devname)
|
devnames.append(devname)
|
||||||
data = dict(devnames=devnames)
|
return dict(devnames=devnames)
|
||||||
self.render_template("ipforward.sh", data)
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ class SimpleService(ConfigService):
|
||||||
name = "Simple"
|
name = "Simple"
|
||||||
group = "SimpleGroup"
|
group = "SimpleGroup"
|
||||||
directories = []
|
directories = []
|
||||||
|
files = ["test1.sh"]
|
||||||
executables = []
|
executables = []
|
||||||
dependencies = []
|
dependencies = []
|
||||||
startup = []
|
startup = []
|
||||||
|
@ -13,10 +14,10 @@ class SimpleService(ConfigService):
|
||||||
validation_mode = ConfigServiceMode.BLOCKING
|
validation_mode = ConfigServiceMode.BLOCKING
|
||||||
default_configs = []
|
default_configs = []
|
||||||
|
|
||||||
def create_files(self):
|
def get_text(self, name: str) -> str:
|
||||||
text = """
|
if name == "test1.sh":
|
||||||
# sample script
|
return """
|
||||||
# node id(${node.id}) name(${node.name})
|
# sample script
|
||||||
echo hello
|
# node id(${node.id}) name(${node.name})
|
||||||
"""
|
echo hello
|
||||||
self.render_text("test1.sh", text)
|
"""
|
||||||
|
|
|
@ -63,6 +63,7 @@ class CoreClient:
|
||||||
self.app = app
|
self.app = app
|
||||||
self.master = app.master
|
self.master = app.master
|
||||||
self.services = {}
|
self.services = {}
|
||||||
|
self.config_services_groups = {}
|
||||||
self.config_services = {}
|
self.config_services = {}
|
||||||
self.default_services = {}
|
self.default_services = {}
|
||||||
self.emane_models = []
|
self.emane_models = []
|
||||||
|
@ -417,7 +418,10 @@ class CoreClient:
|
||||||
# get config service informations
|
# get config service informations
|
||||||
response = self.client.get_config_services()
|
response = self.client.get_config_services()
|
||||||
for service in response.services:
|
for service in response.services:
|
||||||
group_services = self.config_services.setdefault(service.group, set())
|
self.config_services[service.name] = service
|
||||||
|
group_services = self.config_services_groups.setdefault(
|
||||||
|
service.group, set()
|
||||||
|
)
|
||||||
group_services.add(service.name)
|
group_services.add(service.name)
|
||||||
|
|
||||||
# if there are no sessions, create a new session, else join a session
|
# if there are no sessions, create a new session, else join a session
|
||||||
|
|
|
@ -46,7 +46,7 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.default_shutdown = []
|
self.default_shutdown = []
|
||||||
self.validation_mode = None
|
self.validation_mode = None
|
||||||
self.validation_time = None
|
self.validation_time = None
|
||||||
self.validation_period = None
|
self.validation_period = tk.StringVar()
|
||||||
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16)
|
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16)
|
||||||
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16)
|
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16)
|
||||||
|
|
||||||
|
@ -68,35 +68,33 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
def load(self):
|
def load(self):
|
||||||
try:
|
try:
|
||||||
self.app.core.create_nodes_and_links()
|
self.app.core.create_nodes_and_links()
|
||||||
default_config = self.app.core.get_node_service(
|
# default_config = self.app.core.get_node_service(
|
||||||
self.node_id, self.service_name
|
# self.node_id, self.service_name
|
||||||
)
|
# )
|
||||||
self.default_startup = default_config.startup[:]
|
# self.default_startup = default_config.startup[:]
|
||||||
self.default_validate = default_config.validate[:]
|
# self.default_validate = default_config.validate[:]
|
||||||
self.default_shutdown = default_config.shutdown[:]
|
# self.default_shutdown = default_config.shutdown[:]
|
||||||
custom_configs = self.service_configs
|
# custom_configs = self.service_configs
|
||||||
if (
|
# if (
|
||||||
self.node_id in custom_configs
|
# self.node_id in custom_configs
|
||||||
and self.service_name in custom_configs[self.node_id]
|
# and self.service_name in custom_configs[self.node_id]
|
||||||
):
|
# ):
|
||||||
service_config = custom_configs[self.node_id][self.service_name]
|
# service_config = custom_configs[self.node_id][self.service_name]
|
||||||
else:
|
# else:
|
||||||
service_config = default_config
|
# service_config = default_config
|
||||||
|
|
||||||
|
service_config = self.core.config_services[self.service_name]
|
||||||
self.dependencies = service_config.dependencies[:]
|
self.dependencies = service_config.dependencies[:]
|
||||||
self.executables = service_config.executables[:]
|
self.executables = service_config.executables[:]
|
||||||
self.filenames = service_config.configs[:]
|
self.filenames = service_config.files[:]
|
||||||
self.startup_commands = service_config.startup[:]
|
self.startup_commands = service_config.startup[:]
|
||||||
self.validation_commands = service_config.validate[:]
|
self.validation_commands = service_config.validate[:]
|
||||||
self.shutdown_commands = service_config.shutdown[:]
|
self.shutdown_commands = service_config.shutdown[:]
|
||||||
self.validation_mode = service_config.validation_mode
|
self.validation_mode = service_config.validation_mode
|
||||||
self.validation_time = service_config.validation_timer
|
self.validation_time = service_config.validation_timer
|
||||||
self.original_service_files = {
|
self.validation_period.set(service_config.validation_period)
|
||||||
x: self.app.core.get_node_service_file(
|
response = self.core.client.get_config_service_templates(self.service_name)
|
||||||
self.node_id, self.service_name, x
|
self.original_service_files = response.templates
|
||||||
)
|
|
||||||
for x in self.filenames
|
|
||||||
}
|
|
||||||
self.temp_service_files = dict(self.original_service_files)
|
self.temp_service_files = dict(self.original_service_files)
|
||||||
file_configs = self.file_configs
|
file_configs = self.file_configs
|
||||||
if (
|
if (
|
||||||
|
@ -303,7 +301,7 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
label = ttk.Label(frame, text="Validation Period")
|
label = ttk.Label(frame, text="Validation Period")
|
||||||
label.grid(row=2, column=0, sticky="w", padx=PADX)
|
label.grid(row=2, column=0, sticky="w", padx=PADX)
|
||||||
self.validation_period_entry = ttk.Entry(
|
self.validation_period_entry = ttk.Entry(
|
||||||
frame, state=tk.DISABLED, textvariable=tk.StringVar()
|
frame, state=tk.DISABLED, textvariable=self.validation_period
|
||||||
)
|
)
|
||||||
self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY)
|
self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class NodeConfigServiceDialog(Dialog):
|
||||||
label_frame.columnconfigure(0, weight=1)
|
label_frame.columnconfigure(0, weight=1)
|
||||||
self.groups = ListboxScroll(label_frame)
|
self.groups = ListboxScroll(label_frame)
|
||||||
self.groups.grid(sticky="nsew")
|
self.groups.grid(sticky="nsew")
|
||||||
for group in sorted(self.app.core.config_services):
|
for group in sorted(self.app.core.config_services_groups):
|
||||||
self.groups.listbox.insert(tk.END, group)
|
self.groups.listbox.insert(tk.END, group)
|
||||||
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
|
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
|
||||||
self.groups.listbox.selection_set(0)
|
self.groups.listbox.selection_set(0)
|
||||||
|
@ -98,7 +98,7 @@ class NodeConfigServiceDialog(Dialog):
|
||||||
index = selection[0]
|
index = selection[0]
|
||||||
group = self.groups.listbox.get(index)
|
group = self.groups.listbox.get(index)
|
||||||
self.services.clear()
|
self.services.clear()
|
||||||
for name in sorted(self.app.core.config_services[group]):
|
for name in sorted(self.app.core.config_services_groups[group]):
|
||||||
checked = name in self.current_services
|
checked = name in self.current_services
|
||||||
self.services.add(name, checked)
|
self.services.add(name, checked)
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,13 @@ message ConfigService {
|
||||||
repeated string executables = 3;
|
repeated string executables = 3;
|
||||||
repeated string dependencies = 4;
|
repeated string dependencies = 4;
|
||||||
repeated string directories = 5;
|
repeated string directories = 5;
|
||||||
repeated string startup = 6;
|
repeated string files = 6;
|
||||||
repeated string validate = 7;
|
repeated string startup = 7;
|
||||||
repeated string shutdown = 8;
|
repeated string validate = 8;
|
||||||
ConfigServiceValidationMode.Enum validation_mode = 9;
|
repeated string shutdown = 9;
|
||||||
int32 validation_timer = 10;
|
ConfigServiceValidationMode.Enum validation_mode = 10;
|
||||||
float validation_period = 11;
|
int32 validation_timer = 11;
|
||||||
|
float validation_period = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetConfigServicesRequest {
|
message GetConfigServicesRequest {
|
||||||
|
@ -32,6 +33,14 @@ message GetConfigServicesResponse {
|
||||||
repeated ConfigService services = 1;
|
repeated ConfigService services = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetConfigServiceTemplatesRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetConfigServiceTemplatesResponse {
|
||||||
|
map<string, string> templates = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message GetNodeConfigServiceRequest {
|
message GetNodeConfigServiceRequest {
|
||||||
int32 session_id = 1;
|
int32 session_id = 1;
|
||||||
int32 node_id = 2;
|
int32 node_id = 2;
|
||||||
|
|
|
@ -107,6 +107,8 @@ service CoreApi {
|
||||||
// config services
|
// config services
|
||||||
rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) {
|
rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) {
|
||||||
}
|
}
|
||||||
|
rpc GetConfigServiceTemplates (configservices.GetConfigServiceTemplatesRequest) returns (configservices.GetConfigServiceTemplatesResponse) {
|
||||||
|
}
|
||||||
rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) {
|
rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) {
|
||||||
}
|
}
|
||||||
rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) {
|
rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue