diff --git a/docs/netlab/exec.md b/docs/netlab/exec.md index 43c78626ea..94c2109cd9 100644 --- a/docs/netlab/exec.md +++ b/docs/netlab/exec.md @@ -3,6 +3,8 @@ **netlab exec** command uses information stored in the _netlab_ snapshot file and reported with the **[`netlab inspect --node`](inspect.md)** command to execute a command on one or more lab devices using SSH or **docker exec**. +[**netlab inspect** documentation](netlab-inspect-node) describes how to specify the nodes on which the command will be executed. + ## Usage ```text @@ -20,6 +22,7 @@ options: -q, --quiet Report only major errors --dry-run Print the hosts and the commands that would be executed on them, but do not execute them + --header Add node headers before command printouts -i INSTANCE, --instance INSTANCE Specify lab instance to execute commands in diff --git a/docs/netlab/inspect.md b/docs/netlab/inspect.md index a933f1a83b..a644285781 100644 --- a/docs/netlab/inspect.md +++ b/docs/netlab/inspect.md @@ -58,7 +58,7 @@ Finally, the data selection argument is evaluated as a Python expression, so you (netlab-inspect-node)= ## Node Inspection Examples -You can use the `--node` parameter to inspect the data structure of a single node, a list of nodes (separated by commas), or a wildcard expression. +You can use the `--node` parameter to inspect the data structure of a single node, a group of nodes, or a wildcard (glob) expression. You can also specify multiple parameters separated by commas. | To display this information... | ...use this command | |--------------------------------|---------------------| @@ -66,7 +66,9 @@ You can use the `--node` parameter to inspect the data structure of a single nod | interface data for node `r1` | `netlab inspect --node r1 interfaces` | | first interface on node `r1` | `netlab inspect --node r1 'interfaces[0]'` | | BGP parameters on R1 and R2 | `netlab inspect --node r1,r2 bgp` | +| BGP parameters of routers in group as65101 | `netlab inspect --node as65101 bgp` | | VRFs on all nodes | `netlab inspect --node '*' vrfs` | +| VLANs on all nodes start with 'r' | `netlab inspect --node 'r*' vlans` | ```{warning} You can use Python expressions only when specifying a single node to inspect. diff --git a/docs/netlab/report.md b/docs/netlab/report.md index 0080a10094..8d198ae163 100644 --- a/docs/netlab/report.md +++ b/docs/netlab/report.md @@ -36,3 +36,7 @@ The **[netlab show reports](netlab-show-reports)** command displays up-to-date l * `netlab report bgp-neighbor.md` creates the table of BGP neighbors in Markdown format * `netlab report bgp-neighbor.ascii` creates the Markdown BGP neighbors report and renders it as ASCII text using the [rich.markdown](https://rich.readthedocs.io/en/stable/markdown.html) library. * `netlab report addressing --node r1,r2` creates addressing report for R1 and R2. + +```{tip} +[**netlab inspect** documentation](netlab-inspect-node) describes how to specify the nodes on which the command will be executed. +``` diff --git a/netsim/cli/_nodeset.py b/netsim/cli/_nodeset.py index 309039c9af..9656718b6f 100644 --- a/netsim/cli/_nodeset.py +++ b/netsim/cli/_nodeset.py @@ -11,6 +11,7 @@ from box import Box from ..utils import log from .. import data +from ..augment import groups # Is a string a glob expression? # @@ -33,6 +34,15 @@ def add_glob(glob: str, names: list, results: list) -> int: Given a nodeset (as a string) and the lab topology, return the list of nodes matching the nodeset. Generate errors as appropriate and abort if needed. + +Nodeset is a comma-separate list of: + +* Node names +* Group names +* Glob expressions matching node names + +The function does its best to maintain the order in which the nodes +were specified. """ def parse_nodeset(ns: str, topology: Box) -> list: n_names = list(topology.nodes.keys()) @@ -41,6 +51,9 @@ def parse_nodeset(ns: str, topology: Box) -> list: if is_glob(n_element): if not add_glob(n_element,n_names,n_list): log.error(f'Wildcard node specification {n_element} does not match any nodes',log.IncorrectValue,'') + elif n_element in topology.groups: + node_list = groups.group_members(topology,n_element) + n_list = n_list + [ n for n in node_list if n not in n_list ] else: if n_element not in n_names: log.error(f'Invalid node name {n_element}',log.IncorrectValue,'') diff --git a/netsim/cli/exec.py b/netsim/cli/exec.py index 9ef91fc929..d385e1590c 100644 --- a/netsim/cli/exec.py +++ b/netsim/cli/exec.py @@ -35,6 +35,11 @@ def exec_parse(args: typing.List[str]) -> typing.Tuple[argparse.Namespace, typin dest='dry_run', action='store_true', help='Print the hosts and the commands that would be executed on them, but do not execute them') + parser.add_argument( + '--header', + dest='header', + action='store_true', + help='Add node headers before command printouts') parser.add_argument( dest='node', action='store', help='Node(s) to run command on') @@ -53,21 +58,18 @@ def run(cli_args: typing.List[str]) -> None: rest = quote_list(rest) topology = load_snapshot(args) - selector = args.node - args = argparse.Namespace(show=None,verbose=False, quiet=True,Output=True) - if selector in topology.nodes: - connect_to_node(node=selector,args=args,rest=rest,topology=topology,log_level=log_level) - elif selector in topology.groups: - node_list = group_members(topology,selector) - for node in node_list: - connect_to_node(node=node,args=args,rest=rest,topology=topology,log_level=log_level) - else: - node_list = _nodeset.parse_nodeset(selector,topology) - for node in node_list: - connect_to_node(node=node,args=args,rest=rest,topology=topology,log_level=log_level) - - - - - + node_list = _nodeset.parse_nodeset(args.node,topology) + p_header = args.header + args = argparse.Namespace(show=None,verbose=False,quiet=True,Output=True) + for node in node_list: + if p_header: + print('=' * 80) + print(f'{node}: executing {" ".join(rest)}') + print('=' * 80) + connect_to_node( + node=node, + args=args, + rest=rest, + topology=topology, + log_level=LogLevel.NONE if p_header else log_level)