Source code for nvflare.fuel.f3.cellnet.fqcn

# Copyright (c) 2023, NVIDIA CORPORATION.  All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Optional, Tuple

from nvflare.fuel.common.fqn import FQN


[docs] class FQCN(FQN): VALID_PATTERN = "^[A-Za-z0-9_.~-]*$"
# CellPipe cells use "~"-delimited, explicitly marked leaf segments so pipe # names can never be confused with other cell names or with each other. "~" # is reserved inside CellPipe fields and is safe for site names such as # "site-1" and tokens such as "ext_trainer": # - plain leaf "cellpipe~plain~<token>~<mode>" for pipes connected to the server # root or to the site's own CP; # - alias leaf "cellpipe~alias~<owner>~<token>~<mode>" for pipes connected # through another cell (e.g. a relay). The alias maps the cell to the # owning site for mTLS identity resolution and stream message # authentication. Both directions of the alias grammar live here so they # cannot drift apart. # # CellPipe cell-name schemes, in historical order: # 1. legacy (through 2.8): a bare "<site>_<token>_<mode>" leaf. Root-connected # pipes use it as the whole FQCN; CP/relay-connected pipes nest it under # the connected cell ("<parent_fqcn>.<site>_<token>_<mode>"). # 2. hierarchical (#4801, never released): "<site>.<token>.<mode>". Replaced # because the extra segments created unconnected FQCN parents that broke # routing (NVBug 6371056). # 3. topology (current): a single prefixed leaf segment under the FQCN of # the cell the pipe actually connects to, # "<parent>.cellpipe~plain~<token>~<mode>", or # "<relay_fqcn>.cellpipe~alias~<owner>~<token>~<mode>" when connected # through another cell. # Mixed-version notes: only scheme-1 whole-FQCN aliases are still accepted by # identity resolution and stream auth. Nested bare aliases are intentionally # unsupported because an unmarked leaf inside a longer FQCN is indistinguishable # from a real cell of that name. The two ends of one pipe pair must run the same # scheme - each end derives the peer's name from its own code, so a CJ and a # training subprocess on different schemes fail with "peer FQCN mismatch". CELL_PIPE_SEPARATOR = "~" CELL_PIPE_PREFIX = f"cellpipe{CELL_PIPE_SEPARATOR}" CELL_PIPE_LEAF_PREFIX = f"{CELL_PIPE_PREFIX}plain{CELL_PIPE_SEPARATOR}" CELL_PIPE_ALIAS_PREFIX = f"{CELL_PIPE_PREFIX}alias{CELL_PIPE_SEPARATOR}" CELL_PIPE_ALIAS_MODES = ("active", "passive")
[docs] def make_cell_pipe_leaf(runtime_id: str, mode: str) -> str: return CELL_PIPE_SEPARATOR.join(("cellpipe", "plain", runtime_id, mode))
[docs] def make_cell_pipe_alias(owner: str, runtime_id: str, mode: str) -> str: return CELL_PIPE_SEPARATOR.join(("cellpipe", "alias", owner, runtime_id, mode))
[docs] def parse_cell_pipe_alias(segment: str) -> Optional[Tuple[str, str, str]]: """Parse a CellPipe alias leaf segment into (owner, runtime_id, mode). Two shapes are accepted: - the current explicit form "cellpipe~alias~<owner>~<runtime_id>~<mode>"; - the bare legacy form "<owner>_<runtime_id>_<mode>" used by pre-2.9 flat CellPipe names, where the whole FQCN is the alias. Callers decide where the bare form is acceptable; it is normally restricted to single-segment FQCNs so an unmarked "<token>_<mode>" leaf inside a longer FQCN is never misread as an alias. The explicit form accepts "-" and "_" in both owner and runtime_id because "~" is its only field separator. The legacy form is parsed from the right, so its runtime_id must not contain "." or "_". Returns None if the segment is not a valid alias. """ if segment.startswith(CELL_PIPE_ALIAS_PREFIX): parts = segment.split(CELL_PIPE_SEPARATOR) if len(parts) != 5: return None owner, runtime_id, mode = parts[2:] if not owner or not runtime_id or "." in owner or "." in runtime_id: return None if mode not in CELL_PIPE_ALIAS_MODES: return None return owner, runtime_id, mode # Legacy flat alias grammar. No pre-2.9 FQCN segment can contain the # reserved "~" separator (it was added to FQCN.VALID_PATTERN by the topology # scheme), so a "~"-bearing segment is never a legacy alias. Rejecting it # here also guarantees a current "~"-delimited leaf can never bare-parse. if CELL_PIPE_SEPARATOR in segment: return None head, sep, mode = segment.rpartition("_") if not sep or mode not in CELL_PIPE_ALIAS_MODES: return None # rpartition splits on the last "_", so runtime_id can never contain "_"; # only the "." constraint needs an explicit check. owner, sep, runtime_id = head.rpartition("_") if not sep or not owner or not runtime_id or "." in runtime_id: return None return owner, runtime_id, mode
[docs] class FqcnInfo: def __init__(self, fqcn: str): self.fqcn = fqcn self.path = FQCN.split(fqcn) self.gen = len(self.path) self.is_root = self.gen == 1 self.root = self.path[0] self.is_on_server = self.root == FQCN.ROOT_SERVER
[docs] def same_family(info1: FqcnInfo, info2: FqcnInfo): return info1.root == info2.root