Skip to content

Commit d1feda2

Browse files
authored
Use common nodeset parsing function in netlab exec/inspect/report (#2019)
Closes #1783
1 parent bc85660 commit d1feda2

File tree

5 files changed

+42
-18
lines changed

5 files changed

+42
-18
lines changed

docs/netlab/exec.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
**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**.
55

6+
[**netlab inspect** documentation](netlab-inspect-node) describes how to specify the nodes on which the command will be executed.
7+
68
## Usage
79

810
```text
@@ -20,6 +22,7 @@ options:
2022
-q, --quiet Report only major errors
2123
--dry-run Print the hosts and the commands that would be executed on them,
2224
but do not execute them
25+
--header Add node headers before command printouts
2326
-i INSTANCE, --instance INSTANCE
2427
Specify lab instance to execute commands in
2528

docs/netlab/inspect.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,17 @@ Finally, the data selection argument is evaluated as a Python expression, so you
5858
(netlab-inspect-node)=
5959
## Node Inspection Examples
6060

61-
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.
61+
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.
6262

6363
| To display this information... | ...use this command |
6464
|--------------------------------|---------------------|
6565
| data for node `r1` | `netlab inspect --node r1` |
6666
| interface data for node `r1` | `netlab inspect --node r1 interfaces` |
6767
| first interface on node `r1` | `netlab inspect --node r1 'interfaces[0]'` |
6868
| BGP parameters on R1 and R2 | `netlab inspect --node r1,r2 bgp` |
69+
| BGP parameters of routers in group as65101 | `netlab inspect --node as65101 bgp` |
6970
| VRFs on all nodes | `netlab inspect --node '*' vrfs` |
71+
| VLANs on all nodes start with 'r' | `netlab inspect --node 'r*' vlans` |
7072

7173
```{warning}
7274
You can use Python expressions only when specifying a single node to inspect.

docs/netlab/report.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ The **[netlab show reports](netlab-show-reports)** command displays up-to-date l
3636
* `netlab report bgp-neighbor.md` creates the table of BGP neighbors in Markdown format
3737
* `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.
3838
* `netlab report addressing --node r1,r2` creates addressing report for R1 and R2.
39+
40+
```{tip}
41+
[**netlab inspect** documentation](netlab-inspect-node) describes how to specify the nodes on which the command will be executed.
42+
```

netsim/cli/_nodeset.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from box import Box
1212
from ..utils import log
1313
from .. import data
14+
from ..augment import groups
1415

1516
# Is a string a glob expression?
1617
#
@@ -33,6 +34,15 @@ def add_glob(glob: str, names: list, results: list) -> int:
3334
Given a nodeset (as a string) and the lab topology, return the list
3435
of nodes matching the nodeset. Generate errors as appropriate and
3536
abort if needed.
37+
38+
Nodeset is a comma-separate list of:
39+
40+
* Node names
41+
* Group names
42+
* Glob expressions matching node names
43+
44+
The function does its best to maintain the order in which the nodes
45+
were specified.
3646
"""
3747
def parse_nodeset(ns: str, topology: Box) -> list:
3848
n_names = list(topology.nodes.keys())
@@ -41,6 +51,9 @@ def parse_nodeset(ns: str, topology: Box) -> list:
4151
if is_glob(n_element):
4252
if not add_glob(n_element,n_names,n_list):
4353
log.error(f'Wildcard node specification {n_element} does not match any nodes',log.IncorrectValue,'')
54+
elif n_element in topology.groups:
55+
node_list = groups.group_members(topology,n_element)
56+
n_list = n_list + [ n for n in node_list if n not in n_list ]
4457
else:
4558
if n_element not in n_names:
4659
log.error(f'Invalid node name {n_element}',log.IncorrectValue,'')

netsim/cli/exec.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ def exec_parse(args: typing.List[str]) -> typing.Tuple[argparse.Namespace, typin
3535
dest='dry_run',
3636
action='store_true',
3737
help='Print the hosts and the commands that would be executed on them, but do not execute them')
38+
parser.add_argument(
39+
'--header',
40+
dest='header',
41+
action='store_true',
42+
help='Add node headers before command printouts')
3843
parser.add_argument(
3944
dest='node', action='store',
4045
help='Node(s) to run command on')
@@ -53,21 +58,18 @@ def run(cli_args: typing.List[str]) -> None:
5358

5459
rest = quote_list(rest)
5560
topology = load_snapshot(args)
56-
selector = args.node
57-
args = argparse.Namespace(show=None,verbose=False, quiet=True,Output=True)
58-
if selector in topology.nodes:
59-
connect_to_node(node=selector,args=args,rest=rest,topology=topology,log_level=log_level)
60-
elif selector in topology.groups:
61-
node_list = group_members(topology,selector)
62-
for node in node_list:
63-
connect_to_node(node=node,args=args,rest=rest,topology=topology,log_level=log_level)
64-
else:
65-
node_list = _nodeset.parse_nodeset(selector,topology)
66-
for node in node_list:
67-
connect_to_node(node=node,args=args,rest=rest,topology=topology,log_level=log_level)
68-
69-
70-
71-
72-
61+
node_list = _nodeset.parse_nodeset(args.node,topology)
62+
p_header = args.header
7363

64+
args = argparse.Namespace(show=None,verbose=False,quiet=True,Output=True)
65+
for node in node_list:
66+
if p_header:
67+
print('=' * 80)
68+
print(f'{node}: executing {" ".join(rest)}')
69+
print('=' * 80)
70+
connect_to_node(
71+
node=node,
72+
args=args,
73+
rest=rest,
74+
topology=topology,
75+
log_level=LogLevel.NONE if p_header else log_level)

0 commit comments

Comments
 (0)