flwr 1.20.0__py3-none-any.whl → 1.21.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. flwr/__init__.py +4 -1
  2. flwr/app/__init__.py +28 -0
  3. flwr/app/exception.py +31 -0
  4. flwr/cli/auth_plugin/oidc_cli_plugin.py +4 -4
  5. flwr/cli/cli_user_auth_interceptor.py +1 -1
  6. flwr/cli/config_utils.py +3 -3
  7. flwr/cli/constant.py +25 -8
  8. flwr/cli/log.py +9 -9
  9. flwr/cli/login/login.py +3 -3
  10. flwr/cli/ls.py +5 -5
  11. flwr/cli/new/new.py +11 -0
  12. flwr/cli/new/templates/app/code/__init__.pytorch_msg_api.py.tpl +1 -0
  13. flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +80 -0
  14. flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +41 -0
  15. flwr/cli/new/templates/app/code/task.pytorch_msg_api.py.tpl +98 -0
  16. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  17. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  18. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  19. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  20. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  21. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  22. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  23. flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
  24. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  26. flwr/cli/run/run.py +9 -13
  27. flwr/cli/stop.py +7 -4
  28. flwr/cli/utils.py +19 -8
  29. flwr/client/grpc_rere_client/connection.py +1 -12
  30. flwr/client/rest_client/connection.py +3 -0
  31. flwr/clientapp/__init__.py +10 -0
  32. flwr/clientapp/mod/__init__.py +26 -0
  33. flwr/clientapp/mod/centraldp_mods.py +132 -0
  34. flwr/common/args.py +20 -6
  35. flwr/common/auth_plugin/__init__.py +4 -4
  36. flwr/common/auth_plugin/auth_plugin.py +7 -7
  37. flwr/common/constant.py +23 -4
  38. flwr/common/event_log_plugin/event_log_plugin.py +1 -1
  39. flwr/common/exit/__init__.py +4 -0
  40. flwr/common/exit/exit.py +8 -1
  41. flwr/common/exit/exit_code.py +26 -7
  42. flwr/common/exit/exit_handler.py +62 -0
  43. flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
  44. flwr/common/grpc.py +0 -11
  45. flwr/common/inflatable_utils.py +1 -1
  46. flwr/common/logger.py +1 -1
  47. flwr/common/retry_invoker.py +30 -11
  48. flwr/common/telemetry.py +4 -0
  49. flwr/compat/server/app.py +2 -2
  50. flwr/proto/appio_pb2.py +25 -17
  51. flwr/proto/appio_pb2.pyi +46 -2
  52. flwr/proto/clientappio_pb2.py +3 -11
  53. flwr/proto/clientappio_pb2.pyi +0 -47
  54. flwr/proto/clientappio_pb2_grpc.py +19 -20
  55. flwr/proto/clientappio_pb2_grpc.pyi +10 -11
  56. flwr/proto/control_pb2.py +62 -0
  57. flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +54 -54
  58. flwr/proto/{exec_pb2_grpc.pyi → control_pb2_grpc.pyi} +28 -28
  59. flwr/proto/serverappio_pb2.py +2 -2
  60. flwr/proto/serverappio_pb2_grpc.py +68 -0
  61. flwr/proto/serverappio_pb2_grpc.pyi +26 -0
  62. flwr/proto/simulationio_pb2.py +4 -11
  63. flwr/proto/simulationio_pb2.pyi +0 -58
  64. flwr/proto/simulationio_pb2_grpc.py +129 -27
  65. flwr/proto/simulationio_pb2_grpc.pyi +52 -13
  66. flwr/server/app.py +129 -152
  67. flwr/server/grid/grpc_grid.py +3 -0
  68. flwr/server/grid/inmemory_grid.py +1 -0
  69. flwr/server/serverapp/app.py +157 -146
  70. flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -1
  71. flwr/server/superlink/fleet/vce/vce_api.py +6 -6
  72. flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -0
  73. flwr/server/superlink/linkstate/linkstate.py +2 -1
  74. flwr/server/superlink/linkstate/sqlite_linkstate.py +45 -0
  75. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -1
  76. flwr/server/superlink/serverappio/serverappio_servicer.py +61 -6
  77. flwr/server/superlink/simulation/simulationio_servicer.py +97 -21
  78. flwr/serverapp/__init__.py +12 -0
  79. flwr/serverapp/dp_fixed_clipping.py +352 -0
  80. flwr/serverapp/exception.py +38 -0
  81. flwr/serverapp/strategy/__init__.py +38 -0
  82. flwr/serverapp/strategy/dp_fixed_clipping.py +352 -0
  83. flwr/serverapp/strategy/fedadagrad.py +162 -0
  84. flwr/serverapp/strategy/fedadam.py +181 -0
  85. flwr/serverapp/strategy/fedavg.py +295 -0
  86. flwr/serverapp/strategy/fedopt.py +218 -0
  87. flwr/serverapp/strategy/fedyogi.py +173 -0
  88. flwr/serverapp/strategy/result.py +105 -0
  89. flwr/serverapp/strategy/strategy.py +285 -0
  90. flwr/serverapp/strategy/strategy_utils.py +251 -0
  91. flwr/serverapp/strategy/strategy_utils_tests.py +304 -0
  92. flwr/simulation/app.py +161 -164
  93. flwr/supercore/app_utils.py +58 -0
  94. flwr/{supernode/scheduler → supercore/cli}/__init__.py +3 -3
  95. flwr/supercore/cli/flower_superexec.py +141 -0
  96. flwr/supercore/{scheduler → corestate}/__init__.py +3 -3
  97. flwr/supercore/corestate/corestate.py +81 -0
  98. flwr/supercore/grpc_health/__init__.py +3 -0
  99. flwr/supercore/grpc_health/health_server.py +53 -0
  100. flwr/supercore/grpc_health/simple_health_servicer.py +2 -2
  101. flwr/{superexec → supercore/superexec}/__init__.py +1 -1
  102. flwr/supercore/superexec/plugin/__init__.py +28 -0
  103. flwr/{supernode/scheduler/simple_clientapp_scheduler_plugin.py → supercore/superexec/plugin/base_exec_plugin.py} +10 -6
  104. flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
  105. flwr/supercore/{scheduler/plugin.py → superexec/plugin/exec_plugin.py} +4 -4
  106. flwr/supercore/superexec/plugin/serverapp_exec_plugin.py +28 -0
  107. flwr/supercore/superexec/plugin/simulation_exec_plugin.py +28 -0
  108. flwr/supercore/superexec/run_superexec.py +185 -0
  109. flwr/superlink/servicer/__init__.py +15 -0
  110. flwr/superlink/servicer/control/__init__.py +22 -0
  111. flwr/{superexec/exec_event_log_interceptor.py → superlink/servicer/control/control_event_log_interceptor.py} +7 -7
  112. flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +24 -29
  113. flwr/{superexec/exec_license_interceptor.py → superlink/servicer/control/control_license_interceptor.py} +6 -6
  114. flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +69 -30
  115. flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +10 -10
  116. flwr/supernode/cli/flower_supernode.py +3 -0
  117. flwr/supernode/cli/flwr_clientapp.py +18 -21
  118. flwr/supernode/nodestate/in_memory_nodestate.py +2 -2
  119. flwr/supernode/nodestate/nodestate.py +3 -59
  120. flwr/supernode/runtime/run_clientapp.py +39 -102
  121. flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -17
  122. flwr/supernode/start_client_internal.py +35 -76
  123. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/METADATA +4 -3
  124. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/RECORD +127 -98
  125. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
  126. flwr/proto/exec_pb2.py +0 -62
  127. flwr/superexec/app.py +0 -45
  128. flwr/superexec/deployment.py +0 -191
  129. flwr/superexec/executor.py +0 -100
  130. flwr/superexec/simulation.py +0 -129
  131. /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
  132. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """SuperExec gRPC API."""
15
+ """Control API server."""
16
16
 
17
17
 
18
18
  from logging import INFO
@@ -21,23 +21,21 @@ from typing import Optional
21
21
  import grpc
22
22
 
23
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
- from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
24
+ from flwr.common.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
25
25
  from flwr.common.event_log_plugin import EventLogWriterPlugin
26
26
  from flwr.common.exit import ExitCode, flwr_exit
27
27
  from flwr.common.grpc import generic_create_grpc_server
28
28
  from flwr.common.logger import log
29
- from flwr.common.typing import UserConfig
30
- from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
29
+ from flwr.proto.control_pb2_grpc import add_ControlServicer_to_server
31
30
  from flwr.server.superlink.linkstate import LinkStateFactory
32
31
  from flwr.supercore.ffs import FfsFactory
33
32
  from flwr.supercore.license_plugin import LicensePlugin
34
33
  from flwr.supercore.object_store import ObjectStoreFactory
35
- from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
36
- from flwr.superexec.exec_license_interceptor import ExecLicenseInterceptor
37
- from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
38
34
 
39
- from .exec_servicer import ExecServicer
40
- from .executor import Executor
35
+ from .control_event_log_interceptor import ControlEventLogInterceptor
36
+ from .control_license_interceptor import ControlLicenseInterceptor
37
+ from .control_servicer import ControlServicer
38
+ from .control_user_auth_interceptor import ControlUserAuthInterceptor
41
39
 
42
40
  try:
43
41
  from flwr.ee import get_license_plugin
@@ -48,44 +46,41 @@ except ImportError:
48
46
 
49
47
 
50
48
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
51
- def run_exec_api_grpc(
49
+ def run_control_api_grpc(
52
50
  address: str,
53
- executor: Executor,
54
51
  state_factory: LinkStateFactory,
55
52
  ffs_factory: FfsFactory,
56
53
  objectstore_factory: ObjectStoreFactory,
57
54
  certificates: Optional[tuple[bytes, bytes, bytes]],
58
- config: UserConfig,
59
- auth_plugin: Optional[ExecAuthPlugin] = None,
60
- authz_plugin: Optional[ExecAuthzPlugin] = None,
55
+ is_simulation: bool,
56
+ auth_plugin: Optional[ControlAuthPlugin] = None,
57
+ authz_plugin: Optional[ControlAuthzPlugin] = None,
61
58
  event_log_plugin: Optional[EventLogWriterPlugin] = None,
62
59
  ) -> grpc.Server:
63
- """Run Exec API (gRPC, request-response)."""
64
- executor.set_config(config)
65
-
60
+ """Run Control API (gRPC, request-response)."""
66
61
  license_plugin: Optional[LicensePlugin] = get_license_plugin()
67
62
  if license_plugin and not license_plugin.check_license():
68
63
  flwr_exit(ExitCode.SUPERLINK_LICENSE_INVALID)
69
64
 
70
- exec_servicer: grpc.Server = ExecServicer(
65
+ control_servicer: grpc.Server = ControlServicer(
71
66
  linkstate_factory=state_factory,
72
67
  ffs_factory=ffs_factory,
73
68
  objectstore_factory=objectstore_factory,
74
- executor=executor,
69
+ is_simulation=is_simulation,
75
70
  auth_plugin=auth_plugin,
76
71
  )
77
72
  interceptors: list[grpc.ServerInterceptor] = []
78
73
  if license_plugin is not None:
79
- interceptors.append(ExecLicenseInterceptor(license_plugin))
74
+ interceptors.append(ControlLicenseInterceptor(license_plugin))
80
75
  if auth_plugin is not None and authz_plugin is not None:
81
- interceptors.append(ExecUserAuthInterceptor(auth_plugin, authz_plugin))
76
+ interceptors.append(ControlUserAuthInterceptor(auth_plugin, authz_plugin))
82
77
  # Event log interceptor must be added after user auth interceptor
83
78
  if event_log_plugin is not None:
84
- interceptors.append(ExecEventLogInterceptor(event_log_plugin))
79
+ interceptors.append(ControlEventLogInterceptor(event_log_plugin))
85
80
  log(INFO, "Flower event logging enabled")
86
- exec_add_servicer_to_server_fn = add_ExecServicer_to_server
87
- exec_grpc_server = generic_create_grpc_server(
88
- servicer_and_add_fn=(exec_servicer, exec_add_servicer_to_server_fn),
81
+ control_add_servicer_to_server_fn = add_ControlServicer_to_server
82
+ control_grpc_server = generic_create_grpc_server(
83
+ servicer_and_add_fn=(control_servicer, control_add_servicer_to_server_fn),
89
84
  server_address=address,
90
85
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
91
86
  certificates=certificates,
@@ -93,14 +88,14 @@ def run_exec_api_grpc(
93
88
  )
94
89
 
95
90
  if auth_plugin is None:
96
- log(INFO, "Flower Deployment Engine: Starting Exec API on %s", address)
91
+ log(INFO, "Flower Deployment Runtime: Starting Control API on %s", address)
97
92
  else:
98
93
  log(
99
94
  INFO,
100
- "Flower Deployment Engine: Starting Exec API with user "
95
+ "Flower Deployment Runtime: Starting Control API with user "
101
96
  "authentication on %s",
102
97
  address,
103
98
  )
104
- exec_grpc_server.start()
99
+ control_grpc_server.start()
105
100
 
106
- return exec_grpc_server
101
+ return control_grpc_server
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower Exec API license interceptor."""
15
+ """Flower Control API license interceptor."""
16
16
 
17
17
 
18
18
  from collections.abc import Iterator
@@ -24,8 +24,8 @@ from google.protobuf.message import Message as GrpcMessage
24
24
  from flwr.supercore.license_plugin import LicensePlugin
25
25
 
26
26
 
27
- class ExecLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
28
- """Exec API interceptor for license checking."""
27
+ class ControlLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
28
+ """Control API interceptor for license checking."""
29
29
 
30
30
  def __init__(self, license_plugin: LicensePlugin) -> None:
31
31
  """Initialize the interceptor with a license plugin."""
@@ -42,12 +42,12 @@ class ExecLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
42
42
  Continue RPC call if license check is enabled and passes, else, terminate RPC
43
43
  call by setting context to abort.
44
44
  """
45
- # Only apply to Exec service
46
- if not handler_call_details.method.startswith("/flwr.proto.Exec/"):
45
+ # Only apply to Control service
46
+ if not handler_call_details.method.startswith("/flwr.proto.Control/"):
47
47
  return continuation(handler_call_details)
48
48
 
49
49
  # One of the method handlers in
50
- # `flwr.superexec.exec_servicer.ExecServicer`
50
+ # `flwr.superlink.servicer.control.ControlServicer`
51
51
  method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
52
52
  return self._generic_license_unary_method_handler(method_handler)
53
53
 
@@ -12,9 +12,10 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """SuperExec API servicer."""
15
+ """Control API servicer."""
16
16
 
17
17
 
18
+ import hashlib
18
19
  import time
19
20
  from collections.abc import Generator
20
21
  from logging import ERROR, INFO
@@ -22,11 +23,13 @@ from typing import Any, Optional, cast
22
23
 
23
24
  import grpc
24
25
 
25
- from flwr.common import now
26
- from flwr.common.auth_plugin import ExecAuthPlugin
26
+ from flwr.cli.config_utils import get_fab_metadata
27
+ from flwr.common import Context, RecordDict, now
28
+ from flwr.common.auth_plugin import ControlAuthPlugin
27
29
  from flwr.common.constant import (
28
30
  FAB_MAX_SIZE,
29
31
  LOG_STREAM_INTERVAL,
32
+ NO_USER_AUTH_MESSAGE,
30
33
  RUN_ID_NOT_FOUND_MESSAGE,
31
34
  Status,
32
35
  SubStatus,
@@ -37,9 +40,9 @@ from flwr.common.serde import (
37
40
  run_to_proto,
38
41
  user_config_from_proto,
39
42
  )
40
- from flwr.common.typing import Run, RunStatus
41
- from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
42
- from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
43
+ from flwr.common.typing import Fab, Run, RunStatus
44
+ from flwr.proto import control_pb2_grpc # pylint: disable=E0611
45
+ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
43
46
  GetAuthTokensRequest,
44
47
  GetAuthTokensResponse,
45
48
  GetLoginDetailsRequest,
@@ -57,33 +60,33 @@ from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
57
60
  from flwr.supercore.ffs import FfsFactory
58
61
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
59
62
 
60
- from .exec_user_auth_interceptor import shared_account_info
61
- from .executor import Executor
63
+ from .control_user_auth_interceptor import shared_account_info
62
64
 
63
65
 
64
- class ExecServicer(exec_pb2_grpc.ExecServicer):
65
- """SuperExec API servicer."""
66
+ class ControlServicer(control_pb2_grpc.ControlServicer):
67
+ """Control API servicer."""
66
68
 
67
69
  def __init__( # pylint: disable=R0913, R0917
68
70
  self,
69
71
  linkstate_factory: LinkStateFactory,
70
72
  ffs_factory: FfsFactory,
71
73
  objectstore_factory: ObjectStoreFactory,
72
- executor: Executor,
73
- auth_plugin: Optional[ExecAuthPlugin] = None,
74
+ is_simulation: bool,
75
+ auth_plugin: Optional[ControlAuthPlugin] = None,
74
76
  ) -> None:
75
77
  self.linkstate_factory = linkstate_factory
76
78
  self.ffs_factory = ffs_factory
77
79
  self.objectstore_factory = objectstore_factory
78
- self.executor = executor
79
- self.executor.initialize(linkstate_factory, ffs_factory)
80
+ self.is_simulation = is_simulation
80
81
  self.auth_plugin = auth_plugin
81
82
 
82
83
  def StartRun(
83
84
  self, request: StartRunRequest, context: grpc.ServicerContext
84
85
  ) -> StartRunResponse:
85
86
  """Create run ID."""
86
- log(INFO, "ExecServicer.StartRun")
87
+ log(INFO, "ControlServicer.StartRun")
88
+ state = self.linkstate_factory.state()
89
+ ffs = self.ffs_factory.ffs()
87
90
 
88
91
  if len(request.fab.content) > FAB_MAX_SIZE:
89
92
  log(
@@ -94,24 +97,60 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
94
97
  return StartRunResponse()
95
98
 
96
99
  flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
97
- run_id = self.executor.start_run(
98
- request.fab.content,
99
- user_config_from_proto(request.override_config),
100
- config_record_from_proto(request.federation_options),
101
- flwr_aid,
102
- )
100
+ override_config = user_config_from_proto(request.override_config)
101
+ federation_options = config_record_from_proto(request.federation_options)
102
+ fab_file = request.fab.content
103
+
104
+ try:
105
+ # Check that num-supernodes is set
106
+ if self.is_simulation and "num-supernodes" not in federation_options:
107
+ raise ValueError(
108
+ "Federation options doesn't contain key `num-supernodes`."
109
+ )
110
+
111
+ # Create run
112
+ fab = Fab(hashlib.sha256(fab_file).hexdigest(), fab_file)
113
+ fab_hash = ffs.put(fab.content, {})
114
+ if fab_hash != fab.hash_str:
115
+ raise RuntimeError(
116
+ f"FAB ({fab.hash_str}) hash from request doesn't match contents"
117
+ )
118
+ fab_id, fab_version = get_fab_metadata(fab.content)
119
+
120
+ run_id = state.create_run(
121
+ fab_id,
122
+ fab_version,
123
+ fab_hash,
124
+ override_config,
125
+ federation_options,
126
+ flwr_aid,
127
+ )
128
+
129
+ # Create an empty context for the Run
130
+ context = Context(
131
+ run_id=run_id,
132
+ node_id=0,
133
+ node_config={},
134
+ state=RecordDict(),
135
+ run_config={},
136
+ )
137
+
138
+ # Register the context at the LinkState
139
+ state.set_serverapp_context(run_id=run_id, context=context)
103
140
 
104
- if run_id is None:
105
- log(ERROR, "Executor failed to start run")
141
+ # pylint: disable-next=broad-except
142
+ except Exception as e:
143
+ log(ERROR, "Could not start run: %s", str(e))
106
144
  return StartRunResponse()
107
145
 
146
+ log(INFO, "Created run %s", str(run_id))
108
147
  return StartRunResponse(run_id=run_id)
109
148
 
110
149
  def StreamLogs( # pylint: disable=C0103
111
150
  self, request: StreamLogsRequest, context: grpc.ServicerContext
112
151
  ) -> Generator[StreamLogsResponse, Any, None]:
113
152
  """Get logs."""
114
- log(INFO, "ExecServicer.StreamLogs")
153
+ log(INFO, "ControlServicer.StreamLogs")
115
154
  state = self.linkstate_factory.state()
116
155
 
117
156
  # Retrieve run ID and run
@@ -158,7 +197,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
158
197
  self, request: ListRunsRequest, context: grpc.ServicerContext
159
198
  ) -> ListRunsResponse:
160
199
  """Handle `flwr ls` command."""
161
- log(INFO, "ExecServicer.List")
200
+ log(INFO, "ControlServicer.List")
162
201
  state = self.linkstate_factory.state()
163
202
 
164
203
  # Build a set of run IDs for `flwr ls --runs`
@@ -204,7 +243,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
204
243
  self, request: StopRunRequest, context: grpc.ServicerContext
205
244
  ) -> StopRunResponse:
206
245
  """Stop a given run ID."""
207
- log(INFO, "ExecServicer.StopRun")
246
+ log(INFO, "ControlServicer.StopRun")
208
247
  state = self.linkstate_factory.state()
209
248
 
210
249
  # Retrieve run ID and run
@@ -249,11 +288,11 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
249
288
  self, request: GetLoginDetailsRequest, context: grpc.ServicerContext
250
289
  ) -> GetLoginDetailsResponse:
251
290
  """Start login."""
252
- log(INFO, "ExecServicer.GetLoginDetails")
291
+ log(INFO, "ControlServicer.GetLoginDetails")
253
292
  if self.auth_plugin is None:
254
293
  context.abort(
255
294
  grpc.StatusCode.UNIMPLEMENTED,
256
- "ExecServicer initialized without user authentication",
295
+ NO_USER_AUTH_MESSAGE,
257
296
  )
258
297
  raise grpc.RpcError() # This line is unreachable
259
298
 
@@ -276,11 +315,11 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
276
315
  self, request: GetAuthTokensRequest, context: grpc.ServicerContext
277
316
  ) -> GetAuthTokensResponse:
278
317
  """Get auth token."""
279
- log(INFO, "ExecServicer.GetAuthTokens")
318
+ log(INFO, "ControlServicer.GetAuthTokens")
280
319
  if self.auth_plugin is None:
281
320
  context.abort(
282
321
  grpc.StatusCode.UNIMPLEMENTED,
283
- "ExecServicer initialized without user authentication",
322
+ NO_USER_AUTH_MESSAGE,
284
323
  )
285
324
  raise grpc.RpcError() # This line is unreachable
286
325
 
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower Exec API interceptor."""
15
+ """Flower Control API interceptor."""
16
16
 
17
17
 
18
18
  import contextvars
@@ -20,9 +20,9 @@ from typing import Any, Callable, Union
20
20
 
21
21
  import grpc
22
22
 
23
- from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
23
+ from flwr.common.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
24
24
  from flwr.common.typing import AccountInfo
25
- from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
25
+ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
26
26
  GetAuthTokensRequest,
27
27
  GetAuthTokensResponse,
28
28
  GetLoginDetailsRequest,
@@ -50,13 +50,13 @@ shared_account_info: contextvars.ContextVar[AccountInfo] = contextvars.ContextVa
50
50
  )
51
51
 
52
52
 
53
- class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
54
- """Exec API interceptor for user authentication."""
53
+ class ControlUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
54
+ """Control API interceptor for user authentication."""
55
55
 
56
56
  def __init__(
57
57
  self,
58
- auth_plugin: ExecAuthPlugin,
59
- authz_plugin: ExecAuthzPlugin,
58
+ auth_plugin: ControlAuthPlugin,
59
+ authz_plugin: ControlAuthzPlugin,
60
60
  ):
61
61
  self.auth_plugin = auth_plugin
62
62
  self.authz_plugin = authz_plugin
@@ -72,12 +72,12 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
72
72
  by validating auth metadata sent by the user. Continue RPC call if user is
73
73
  authenticated, else, terminate RPC call by setting context to abort.
74
74
  """
75
- # Only apply to Exec service
76
- if not handler_call_details.method.startswith("/flwr.proto.Exec/"):
75
+ # Only apply to Control service
76
+ if not handler_call_details.method.startswith("/flwr.proto.Control/"):
77
77
  return continuation(handler_call_details)
78
78
 
79
79
  # One of the method handlers in
80
- # `flwr.superexec.exec_servicer.ExecServicer`
80
+ # `flwr.superlink.servicer.control.ControlServicer`
81
81
  method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
82
82
  return self._generic_auth_unary_method_handler(method_handler)
83
83
 
@@ -41,6 +41,7 @@ from flwr.common.constant import (
41
41
  )
42
42
  from flwr.common.exit import ExitCode, flwr_exit
43
43
  from flwr.common.logger import log
44
+ from flwr.supercore.grpc_health import add_args_health
44
45
  from flwr.supernode.start_client_internal import start_client_internal
45
46
 
46
47
 
@@ -79,6 +80,7 @@ def flower_supernode() -> None:
79
80
  flwr_path=args.flwr_dir,
80
81
  isolation=args.isolation,
81
82
  clientappio_api_address=args.clientappio_api_address,
83
+ health_server_address=args.health_server_address,
82
84
  )
83
85
 
84
86
 
@@ -118,6 +120,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
118
120
  help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
119
121
  f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
120
122
  )
123
+ add_args_health(parser)
121
124
 
122
125
  return parser
123
126
 
@@ -19,9 +19,12 @@ import argparse
19
19
  from logging import DEBUG, INFO
20
20
 
21
21
  from flwr.common.args import add_args_flwr_app_common
22
- from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS
22
+ from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ExecPluginType
23
23
  from flwr.common.exit import ExitCode, flwr_exit
24
24
  from flwr.common.logger import log
25
+ from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
26
+ from flwr.supercore.superexec.plugin import ClientAppExecPlugin
27
+ from flwr.supercore.superexec.run_superexec import run_with_deprecation_warning
25
28
  from flwr.supercore.utils import mask_string
26
29
  from flwr.supernode.runtime.run_clientapp import run_clientapp
27
30
 
@@ -35,6 +38,20 @@ def flwr_clientapp() -> None:
35
38
  "flwr-clientapp does not support TLS yet.",
36
39
  )
37
40
 
41
+ # Disallow long-running `flwr-clientapp` processes
42
+ if args.token is None:
43
+ run_with_deprecation_warning(
44
+ cmd="flwr-clientapp",
45
+ plugin_type=ExecPluginType.CLIENT_APP,
46
+ plugin_class=ClientAppExecPlugin,
47
+ stub_class=ClientAppIoStub,
48
+ appio_api_address=args.clientappio_api_address,
49
+ flwr_dir=args.flwr_dir,
50
+ parent_pid=args.parent_pid,
51
+ warn_run_once=args.run_once,
52
+ )
53
+ return
54
+
38
55
  log(INFO, "Start `flwr-clientapp` process")
39
56
  log(
40
57
  DEBUG,
@@ -45,7 +62,6 @@ def flwr_clientapp() -> None:
45
62
  )
46
63
  run_clientapp(
47
64
  clientappio_api_address=args.clientappio_api_address,
48
- run_once=(args.token is not None) or args.run_once,
49
65
  token=args.token,
50
66
  flwr_dir=args.flwr_dir,
51
67
  certificates=None,
@@ -65,24 +81,5 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
65
81
  help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
66
82
  f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
67
83
  )
68
- parser.add_argument(
69
- "--token",
70
- type=str,
71
- required=False,
72
- help="Unique token generated by SuperNode for each ClientApp execution",
73
- )
74
- parser.add_argument(
75
- "--parent-pid",
76
- type=int,
77
- default=None,
78
- help="The PID of the parent process. When set, the process will terminate "
79
- "when the parent process exits.",
80
- )
81
- parser.add_argument(
82
- "--run-once",
83
- action="store_true",
84
- help="When set, this process will start a single ClientApp for a pending "
85
- "message. If there is no pending message, the process will exit.",
86
- )
87
84
  add_args_flwr_app_common(parser=parser)
88
85
  return parser
@@ -171,12 +171,12 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
171
171
  ret -= set(self.token_store.keys())
172
172
  return list(ret)
173
173
 
174
- def create_token(self, run_id: int) -> str:
174
+ def create_token(self, run_id: int) -> Optional[str]:
175
175
  """Create a token for the given run ID."""
176
176
  token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
177
177
  with self.lock_token_store:
178
178
  if run_id in self.token_store:
179
- raise ValueError("Token already created for this run ID")
179
+ return None # Token already created for this run ID
180
180
  self.token_store[run_id] = token
181
181
  self.token_to_run_id[token] = run_id
182
182
  return token
@@ -15,15 +15,16 @@
15
15
  """Abstract base class NodeState."""
16
16
 
17
17
 
18
- from abc import ABC, abstractmethod
18
+ from abc import abstractmethod
19
19
  from collections.abc import Sequence
20
20
  from typing import Optional
21
21
 
22
22
  from flwr.common import Context, Message
23
23
  from flwr.common.typing import Run
24
+ from flwr.supercore.corestate import CoreState
24
25
 
25
26
 
26
- class NodeState(ABC):
27
+ class NodeState(CoreState):
27
28
  """Abstract base class for node state."""
28
29
 
29
30
  @abstractmethod
@@ -168,60 +169,3 @@ class NodeState(ABC):
168
169
  Sequence[int]
169
170
  Sequence of run IDs with pending messages.
170
171
  """
171
-
172
- @abstractmethod
173
- def create_token(self, run_id: int) -> str:
174
- """Create a token for the given run ID.
175
-
176
- Parameters
177
- ----------
178
- run_id : int
179
- The ID of the run for which to create a token.
180
-
181
- Returns
182
- -------
183
- str
184
- A unique token associated with the run ID.
185
- """
186
-
187
- @abstractmethod
188
- def verify_token(self, run_id: int, token: str) -> bool:
189
- """Verify a token for the given run ID.
190
-
191
- Parameters
192
- ----------
193
- run_id : int
194
- The ID of the run for which to verify the token.
195
- token : str
196
- The token to verify.
197
-
198
- Returns
199
- -------
200
- bool
201
- True if the token is valid for the run ID, False otherwise.
202
- """
203
-
204
- @abstractmethod
205
- def delete_token(self, run_id: int) -> None:
206
- """Delete the token for the given run ID.
207
-
208
- Parameters
209
- ----------
210
- run_id : int
211
- The ID of the run for which to delete the token.
212
- """
213
-
214
- @abstractmethod
215
- def get_run_id_by_token(self, token: str) -> Optional[int]:
216
- """Get the run ID associated with a given token.
217
-
218
- Parameters
219
- ----------
220
- token : str
221
- The token to look up.
222
-
223
- Returns
224
- -------
225
- Optional[int]
226
- The run ID if the token is valid, otherwise None.
227
- """