Command line user interface for the node component
Attributes
cli module-attribute
cli = NodeCLI()
Classes
DatasetArgumentParser
DatasetArgumentParser(subparser, parser=None)
Bases: CLIArgumentParser
Initializes CLI options for dataset actions
Source code in fedbiomed/common/cli.py
def __init__(self, subparser: argparse.ArgumentParser, parser = None):
self._subparser = subparser
# Parser that is going to be add using subparser
self._parser = None
self._main_parser = parser
Functions
add
add(args)
Adds datasets
Source code in fedbiomed/node/cli.py
def add(self, args):
"""Adds datasets"""
if args.mnist != "":
if args.mnist is None:
mnist_path = os.path.join(self._node.config.root, NODE_DATA_FOLDER)
else:
mnist_path = args.mnist
return add_database(
self._node.dataset_manager,
interactive=False,
path=mnist_path
)
if args.file:
return self._add_dataset_from_file(path=args.file)
# All operation is handled by CLI utils add_database
return add_database(self._node.dataset_manager)
delete
delete(args)
Deletes datasets
Source code in fedbiomed/node/cli.py
def delete(self, args):
"""Deletes datasets"""
if args.all:
return delete_all_database(self._node.dataset_manager)
if args.mnist:
return delete_database(self._node.dataset_manager, interactive=False)
return delete_database(self._node.dataset_manager)
initialize
initialize()
Initializes dataset options for the node CLI
Source code in fedbiomed/node/cli.py
def initialize(self):
"""Initializes dataset options for the node CLI"""
self._parser = self._subparser.add_parser(
"dataset",
help="Dataset operations"
)
self._parser.set_defaults(func=self.default)
# Creates subparser of dataset option
dataset_subparsers = self._parser.add_subparsers()
# Add option
add = dataset_subparsers.add_parser(
"add",
help="Adds dataset"
)
add.set_defaults(func=self.add)
# List option
list_ = dataset_subparsers.add_parser(
"list",
help="List datasets that are deployed in the node.")
# Delete option
delete = dataset_subparsers.add_parser(
"delete",
help="Deletes dataset that are deployed in the node.")
add.add_argument(
"--mnist",
"-m",
metavar="MNIST_DATA_PATH",
help="Deploys MNIST dataset by downloading form default source to given path.",
nargs='?',
type=str,
required=False,
default=""
)
self._mnist_path = add
add.add_argument(
"--file",
"-fl",
required=False,
metavar="File that describes the dataset",
help="File path the dataset file description. This option adds dataset by given file which has"
"custom format that describes the dataset.")
delete.add_argument(
"--all",
'-a',
required=False,
action="store_true",
help="Removes entire dataset database.")
delete.add_argument(
"--mnist",
'-m',
required=False,
action="store_true",
help="Removes MNIST dataset.")
list_.set_defaults(func=self.list)
delete.set_defaults(func=self.delete)
list
list(unused_args)
List datasets
Parameters:
Name | Type | Description | Default |
---|---|---|---|
unused_args | Empty arguments since | required |
Source code in fedbiomed/node/cli.py
def list(self, unused_args):
"""List datasets
Args:
unused_args: Empty arguments since `list` command no positional args.
"""
print('Listing your data available')
data = self._node.dataset_manager.list_my_data(verbose=True)
if len(data) == 0:
print('No data has been set up.')
GUIControl
GUIControl(subparser, parser=None)
Bases: CLIArgumentParser
Source code in fedbiomed/common/cli.py
def __init__(self, subparser: argparse.ArgumentParser, parser = None):
self._subparser = subparser
# Parser that is going to be add using subparser
self._parser = None
self._main_parser = parser
Functions
forward
forward(args, extra_args)
Launches Fed-BioMed Node GUI
Parameters:
Name | Type | Description | Default |
---|---|---|---|
args | Namespace | parser argument's namespace | required |
Source code in fedbiomed/node/cli.py
def forward(self, args: argparse.Namespace, extra_args):
"""Launches Fed-BioMed Node GUI
Args:
args: parser argument's namespace
"""
fedbiomed_root = os.path.abspath(args.path)
if args.data_folder == '':
data_folder = os.path.join(self._node.config.root, NODE_DATA_FOLDER)
else:
data_folder = os.path.abspath(args.data_folder)
if not os.path.isdir(data_folder):
raise FedbiomedError(f"path {data_folder} is not a folder. Aborting")
os.environ.update({
"DATA_PATH": data_folder,
"FBM_NODE_COMPONENT_ROOT": fedbiomed_root,
})
current_env = os.environ.copy()
if args.key_file and args.cert_file:
certificate = ["--keyfile", args.key_file, "--certfile", args.cert_file ]
else:
certificate = []
fedbiomed_gui = importlib.import_module("fedbiomed_gui")
server_app = Path(fedbiomed_gui.__file__).parent
print("path to server", server_app)
host_port = ["--host", args.host, "--port", args.port]
if args.development:
command = [
"FLASK_ENV=development",
f"FLASK_APP="
f"{os.path.join(server_app, 'server', 'wsgi.py')}",
"flask",
"run",
*host_port,
*certificate
]
else:
command = [
"gunicorn",
"--workers",
"1",
# str(os.cpu_count()),
*certificate,
"-b",
f"{args.host}:{args.port}",
"--access-logfile",
"-",
"fedbiomed_gui.server.wsgi:app"
]
try:
with subprocess.Popen(" ".join(command), env=current_env, shell=True) as proc:
proc.wait()
except Exception as e:
print(e)
initialize
initialize()
Initializes GUI commands
Source code in fedbiomed/node/cli.py
def initialize(self):
"""Initializes GUI commands"""
self._parser = self._subparser.add_parser(
"gui", #add_help=False,
help="Action to manage Node user interface"
)
gui_subparsers = self._parser.add_subparsers(title='start GUI')
start = gui_subparsers.add_parser(
'start',
help='Launch the server (defaults on localhost:8484)')
start.set_defaults(func=self.forward)
start.add_argument(
"--data-folder",
"-df",
type=str,
nargs="?",
default="", # data folder in root directory
required=False)
start.add_argument(
"--cert-file",
"-cf",
type=str,
nargs="?",
required=False,
help="Name of the certificate to use in order to enable HTTPS. "
"If cert file doesn't exist script will raise an error.")
start.add_argument(
"--key-file",
"-kf",
type=str,
nargs="?",
required=False,
help="Name of the private key for the SSL certificate. "
"If the key file doesn't exist, the script will raise an error.")
start.add_argument(
"--port",
"-p",
type=str,
nargs="?",
default="8484",
required=False,
help="HTTP port that GUI will be served. Default is `8484`")
start.add_argument(
"--host",
"-ho",
type=str,
default="localhost",
nargs="?",
required=False,
help="HTTP port that GUI will be served. Default is `127.0.0.1` (localhost)")
start.add_argument(
"--debug",
"-dbg",
action="store_true",
required=False,
help="HTTP port that GUI will be served. Default is `8484`")
start.add_argument(
"--recreate",
"-rc",
action="store_true",
required=False,
help="Re-creates gui build")
start.add_argument(
"--development",
"-dev",
action="store_true",
required=False,
help="If it is set, GUI will start in development mode."
)
NodeCLI
NodeCLI()
Bases: CommonCLI
Source code in fedbiomed/node/cli.py
def __init__(self):
super().__init__()
self._parser.prog = "fedbiomed node"
self.description = f"{__intro__} \nA CLI app for fedbiomed node component."
# Parent parser for parameters that are common for Node CLI actions
self.initialize()
Attributes
description instance-attribute
description = f'{__intro__}
A CLI app for fedbiomed node component.'
Functions
initialize
initialize()
Initializes node module
Source code in fedbiomed/node/cli.py
def initialize(self):
"""Initializes node module"""
class ComponentDirectoryActionNode(ComponentDirectoryAction):
_this = self
_component = ComponentType.NODE
def set_component(self, component_dir: str | None = None) -> None:
"""Create node instance"""
if component_dir:
component_dir = os.path.abspath(component_dir)
os.environ["FBM_NODE_COMPONENT_ROOT"] = component_dir
else:
print("Component is not specified: Using 'fbm-researcher' in current working directory...")
component_dir = os.path.join(os.getcwd(), 'fbm-node')
os.environ["FBM_NODE_COMPONENT_ROOT"] = component_dir
config = node_component.initiate(component_dir)
self._this.config = config
node = Node(config)
# Set node object to make it accessible
setattr(ComponentDirectoryActionNode._this, '_node', node)
os.environ[f"FEDBIOMED_ACTIVE_{self._component.name}_ID"] = \
config.get("default", "id")
# Set node in all subparsers
for _, parser in ComponentDirectoryActionNode._this._arg_parsers.items():
setattr(parser, '_node', node)
super().initialize()
self._parser.add_argument(
"--path",
"-p",
nargs="?",
action=ComponentDirectoryActionNode,
default="fbm-node",
help="The path were component is located. It can be absolute or "
"realtive to the path where CLI is executed."
)
NodeControl
NodeControl(subparser, parser=None)
Bases: CLIArgumentParser
CLI argument parser for starting the node
Source code in fedbiomed/common/cli.py
def __init__(self, subparser: argparse.ArgumentParser, parser = None):
self._subparser = subparser
# Parser that is going to be add using subparser
self._parser = None
self._main_parser = parser
Functions
initialize
initialize()
Initializes missinon control argument parser
Source code in fedbiomed/node/cli.py
def initialize(self):
"""Initializes missinon control argument parser"""
start = self._subparser.add_parser("start", help="Starts the node")
start.set_defaults(func=self.start)
start.add_argument(
"--gpu",
action="store_true",
help="Activate GPU usage if the flag is present")
start.add_argument(
"--gpu-num",
"-gn",
type=int,
nargs="?",
required=False,
default=1,
help="Number of GPU that is going to be used")
start.add_argument(
"--gpu-only",
"-go",
action="store_true",
help="Node performs training only using GPU resources."
"This flag automatically activate GPU.")
start
start(args)
Starts the node
Source code in fedbiomed/node/cli.py
def start(self, args):
"""Starts the node"""
intro()
# Define arguments
node_args = {
"gpu": (args.gpu is True) or (args.gpu_only is True),
"gpu_num": args.gpu_num,
"gpu_only": True if args.gpu_only else False}
# Node instance has to be re-instantiated in start_node
# It is because Process can only pickle pure python objects
p = Process(
target=start_node,
name=f'node-{self._node.config.get("default", "id")}',
args=(self._node.config, node_args)
)
p.deamon = True
p.start()
logger.info("Node started as process with pid = " + str(p.pid))
try:
print('To stop press Ctrl + C.')
p.join()
except KeyboardInterrupt:
p.terminate()
time.sleep(1)
while p.is_alive():
logger.info("Terminating process id =" + str(p.pid))
time.sleep(1)
logger.info('Exited with code ' + str(p.exitcode))
sys.exit(0)
TrainingPlanArgumentParser
TrainingPlanArgumentParser(subparser, parser=None)
Bases: CLIArgumentParser
Argument parser for training-plan operations
Source code in fedbiomed/common/cli.py
def __init__(self, subparser: argparse.ArgumentParser, parser = None):
self._subparser = subparser
# Parser that is going to be add using subparser
self._parser = None
self._main_parser = parser
Functions
approve
approve(args)
Approves training plan
Source code in fedbiomed/node/cli.py
def approve(self, args):
"""Approves training plan"""
approve_training_plan(self._node.tp_security_manager, id=args.id)
delete
delete(args)
Deletes training plan
Source code in fedbiomed/node/cli.py
def delete(self, args):
"""Deletes training plan"""
delete_training_plan(self._node.tp_security_manager, id=args.id)
initialize
initialize()
Source code in fedbiomed/node/cli.py
def initialize(self):
self._parser = self._subparser.add_parser(
"training-plan",
help="CLI operations for TrainingPlans register/list/delete/approve/reject etc."
)
training_plan_suparsers = self._parser.add_subparsers()
self._parser.set_defaults(func=self.default)
common_reject_approve = argparse.ArgumentParser(add_help=False)
common_reject_approve.add_argument(
'--id',
type=str,
nargs='?',
required=False,
help='ID of the training plan that will be processed.'
)
update = training_plan_suparsers.add_parser(
"update", help="Updates training plan"
)
update.set_defaults(func=self.update)
register = training_plan_suparsers.add_parser(
"register", help="Registers training plans manually by selected file thorugh interactive browser."
)
register.set_defaults(func=self.register)
list = training_plan_suparsers.add_parser(
"list", help="Lists all saved/registered training plans with their status.")
list.set_defaults(func=self.list)
delete = training_plan_suparsers.add_parser(
"delete",
parents=[common_reject_approve],
help="Deletes interactively selected training plan from the database.")
delete.set_defaults(func=self.delete)
approve = training_plan_suparsers.add_parser(
"approve",
parents=[common_reject_approve],
help="Approves interactively selected training plans.")
approve.set_defaults(func=self.approve)
reject = training_plan_suparsers.add_parser(
"reject",
parents=[common_reject_approve],
help="Rejects interactively selected training plans.")
reject.add_argument(
"--notes",
type=str,
nargs="?",
required=False,
default="No notes provided.",
help="Note to explain why training plan is rejected."
)
reject.set_defaults(func=self.reject)
view = training_plan_suparsers.add_parser(
"view", help="View interactively selected training plans.")
view.set_defaults(func=self.view)
list
list()
Lists training plans
Source code in fedbiomed/node/cli.py
def list(self):
"""Lists training plans"""
self._node.tp_security_manager.list_training_plans(verbose=True)
register
register()
Registers training plan
Source code in fedbiomed/node/cli.py
def register(self):
"""Registers training plan"""
register_training_plan(self._node.tp_security_manager)
reject
reject(args)
Approves training plan
Source code in fedbiomed/node/cli.py
def reject(self, args):
"""Approves training plan"""
reject_training_plan(self._node.tp_security_manager, id=args.id, notes=args.notes)
update
update()
Updates training plan
Source code in fedbiomed/node/cli.py
def update(self):
"""Updates training plan"""
update_training_plan(self._node.tp_security_manager)
view
view()
Views training plan
Source code in fedbiomed/node/cli.py
def view(self):
"""Views training plan"""
view_training_plan(self._node.tp_security_manager)
Functions
intro
intro()
Prints intro for the CLI
Source code in fedbiomed/node/cli.py
def intro():
"""Prints intro for the CLI"""
print(__intro__)
print('\t- 🆔 Your node ID:', os.environ['FEDBIOMED_ACTIVE_NODE_ID'], '\n')
start_node
start_node(config, node_args)
Starts the node
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name | Config name for the node | required | |
node_args | Arguments for the node | required |
Source code in fedbiomed/node/cli.py
def start_node(config, node_args):
"""Starts the node
Args:
name: Config name for the node
node_args: Arguments for the node
"""
_node = Node(config, node_args)
print(_node)
def _node_signal_handler(signum: int, frame: Union[FrameType, None]):
"""Signal handler that terminates the process.
Args:
signum: Signal number received.
frame: Frame object received. Currently unused
Raises:
SystemExit: Always raised.
"""
# get the (running) Node object
try:
if _node and _node.is_connected():
_node.send_error(ErrorNumbers.FB312,
extra_msg = "Node is stopped",
broadcast=True)
time.sleep(2)
logger.critical("Node stopped in signal_handler, probably node exit on error or user decision (Ctrl C)")
else:
# take care of logger level used because message cannot be sent to node
logger.info("Cannot send error message to researcher (node not initialized yet)")
logger.info("Node stopped in signal_handler, probably node exit on error or user decision (Ctrl C)")
finally:
# give some time to send messages to the researcher
time.sleep(0.5)
sys.exit(signum)
logger.setLevel("DEBUG")
try:
signal.signal(signal.SIGTERM, _node_signal_handler)
logger.info('Launching node...')
# Register default training plans and update hashes
if _node.config.getbool('security', 'training_plan_approval'):
# This methods updates hashes if hashing algorithm has changed
_node.tp_security_manager.check_hashes_for_registered_training_plans()
if _node.config.getbool('security', 'allow_default_training_plans'):
logger.info('Loading default training plans')
_node.tp_security_manager.register_update_default_training_plans()
else:
logger.warning('Training plan approval for train request is not activated. ' +
'This might cause security problems. Please, consider to enable training plan approval.')
logger.info('Starting communication channel with network')
_node.start_messaging(_node_signal_trigger_term)
logger.info('Starting node to node router')
_node.start_protocol()
logger.info('Starting task manager')
_node.task_manager() # handling training tasks in queue
except FedbiomedError as exp:
logger.critical(f"Node stopped. {exp}")
# we may add extra information for the user depending on the error
except Exception as exp:
# must send info to the researcher (no mqqt should be handled
# by the previous FedbiomedError)
_node.send_error(ErrorNumbers.FB300, extra_msg="Error = " + str(exp))
logger.critical(f"Node stopped. {exp}")