Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue263 : Added model filter in neighbordb #403

Merged
merged 7 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,9 @@ Rules:

- if multiple node-specific entries reference the same unique_id, only the first will be in effect - all others will be ignored
- if both the **node** and **interfaces** attributes are specified and a node's unique_id is a match, but the topology information is not, then the overall match will fail and the global patterns will not be considered
- if both the **model** and **interfaces** attributes are specified and a node's model is a match, but the topology information is not, then the overall match will fail and the global patterns will not be considered
- if there is no matching node-specific pattern for a node's unique_id, then the server will attempt to match the node against the global patterns (in the order they are specified in ``neighbordb``)
- **node** and **model** are mutually exclusive and can't be specified in the same pattern
- if a node-specific pattern matches, the server will automatically generate an open pattern in the node's folder. This pattern will match any device with at least one LLDP-capable neighbor. Example: ``any: any:any``

.. code-block:: yaml
Expand All @@ -488,8 +490,9 @@ Rules:
...
patterns:
- name: <single line description of pattern>
definition: <defintion_url>
definition: <definition_url>
node: <unique_id>
model: <model_regexp>
config-handler: <config-handler>
variables:
<variable_name>: <function>
Expand All @@ -504,7 +507,7 @@ Rules:

Mandatory attributes: **name**, **definition**, and either **node**, **interfaces** or both.

Optional attributes: **variables**, **config-handler**.
Optional attributes: **variables**, **config-handler**, **model**.

variables
'''''''''
Expand All @@ -528,6 +531,11 @@ node: unique_id

Serial number or MAC address, depending on the global 'identifier' attribute in **ztpserver.conf**.

model: model_regexp
'''''''''''''''''''

Defines a regex pattern to match the node model against.

interfaces: port\_name
''''''''''''''''''''''

Expand Down
21 changes: 21 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,27 @@ Example #5
In this case, the pattern matches if `any` local interface is connected to a
device with `spine` in the hostname and to the 4th or 5th slot in the chassis.

Example #6
''''''''''

.. code-block:: yaml

---
- name: old switch
definition: old-switch
model: "DCS-7010T-48"
interfaces:
- Ethernet49: $uplink:any
- name: new switch
definition: new-switch
model: "DCS-7010TX-48-F"
interfaces:
- Ethernet49: $uplink:any

In this case, the two patterns match the same uplink switch on the same
local interface, but with a different model. This will allow to use a
different definition to upload a version of EOS compatible with the device.


More examples
`````````````
Expand Down
30 changes: 30 additions & 0 deletions test/neighbordb/model_pattern_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
tag: model_pattern_test
tests:
- pattern
valid_patterns:
nodes:
- node pattern
globals:
- pattern with model
- pattern with model and interfaces

neighbordb:
patterns:
- name: node pattern
definition: test
node: 2b3c

- name: invalid pattern with node and model
definition: test
model: modelA
node: 2b3caa

- name: pattern with model
definition: test
model: modelA

- name: pattern with model and interfaces
definition: test
model: modelA
interfaces:
- Ethernet1: localhost:Ethernet1
108 changes: 108 additions & 0 deletions test/neighbordb/model_topology_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
tag: model_pattern_test
tests:
- pattern
- topology
valid_patterns:
nodes:
- node pattern
globals:
- pattern with modelA
- pattern with modelB and neighbour localhost:Ethernet1
- pattern with modelB and neighbour localhost:Ethernet2
- pattern with modelB and no neighbours
- pattern with modelC and neighbour localhost:Ethernet1

nodes:
pass:
- name: node_2b3c
match: node pattern
- name: node_3b3c
match: pattern with modelA
- name: node_4b3c
match: pattern with modelB and neighbour localhost:Ethernet1
- name: node_5b3c
match: pattern with modelB and neighbour localhost:Ethernet2
- name: node_6b3c
match: pattern with modelB and no neighbours
fail:
- name: node_7b3c

node_2b3c:
serialnumber: 2b3c
model: modelA
neighbors:
Ethernet1:
- device: localhost
port: Ethernet1

node_3b3c:
serialnumber: 3b3c
model: modelA
neighbors:
Ethernet1:
- device: localhost
port: Ethernet1

node_4b3c:
serialnumber: 4b3c
model: modelB
neighbors:
Ethernet1:
- device: localhost
port: Ethernet1

node_5b3c:
serialnumber: 5b3c
model: modelB
neighbors:
Ethernet1:
- device: localhost
port: Ethernet2

node_6b3c:
serialnumber: 6b3c
model: modelB
neighbors:
Ethernet1:
- device: localhost
port: Ethernet3

node_7b3c:
serialnumber: 7b3c
model: modelC
neighbors:
Ethernet1:
- device: localhost
port: Ethernet3

neighbordb:
patterns:
- name: node pattern
definition: test
node: 2b3c

- name: pattern with modelA
definition: test
model: modelA

- name: pattern with modelB and neighbour localhost:Ethernet1
definition: test
model: modelB
interfaces:
- Ethernet1: localhost:Ethernet1

- name: pattern with modelB and neighbour localhost:Ethernet2
definition: test
model: modelB
interfaces:
- Ethernet1: localhost:Ethernet2

- name: pattern with modelB and no neighbours
definition: test
model: modelB

- name: pattern with modelC and neighbour localhost:Ethernet1
definition: test
model: modelC
interfaces:
- Ethernet1: localhost:Ethernet1
8 changes: 8 additions & 0 deletions ztpserver/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ def __init__(
node=None,
variables=None,
node_id=None,
model=None,
):
self.name = name
self.definition = definition
Expand All @@ -505,6 +506,8 @@ def __init__(
self.node_id = node_id
self.variables = variables or {}

self.model = model

self.interfaces = []
if interfaces:
self.add_interfaces(interfaces)
Expand Down Expand Up @@ -551,6 +554,7 @@ def serialize(self):
"definition": self.definition,
"variables": self.variables,
"node": self.node,
"model": self.model,
"config-handler": self.config_handler,
}

Expand Down Expand Up @@ -640,6 +644,10 @@ def match_node(self, node):
# while selecting the set of nodes which are eligible for a
# match.

# Match the model first
if self.model and not re.match(self.model, node.model):
return False

patterns = []
for entry in self.interfaces:
for pattern in entry["patterns"]:
Expand Down
20 changes: 9 additions & 11 deletions ztpserver/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from ztpserver.utils import expand_range, parse_interface

REQUIRED_PATTERN_ATTRIBUTES = ["name", "definition"]
OPTIONAL_PATTERN_ATTRIBUTES = ["node", "variables", "interfaces"]
OPTIONAL_PATTERN_ATTRIBUTES = ["node", "variables", "interfaces", "model", "config_handler"]
INTERFACE_PATTERN_KEYWORDS = ["any", "none"]
ANTINODE_PATTERN = rf"[^{string.hexdigits}]"
KW_ANY_RE = re.compile(r" *any *")
Expand Down Expand Up @@ -170,17 +170,15 @@ def validate_attributes(self):
if attr not in self.data:
raise ValidationError(f"missing attribute: {attr}")

if "node" not in self.data and "interfaces" not in self.data:
raise ValidationError("missing attribute: 'node' OR 'interfaces'")
if "node" not in self.data and "model" not in self.data and "interfaces" not in self.data:
raise ValidationError("missing attribute: 'node' OR 'model' OR 'interfaces'")

for attr in OPTIONAL_PATTERN_ATTRIBUTES:
if attr not in self.data:
log.warning(
"%s: PatternValidator warning: '%s' is missing optional attribute (%s)",
self.node_id,
self.data["name"],
attr,
)
if "node" in self.data and "model" in self.data:
raise ValidationError("'node' AND 'model' are mutually exclusive")

for attr in self.data:
if attr not in REQUIRED_PATTERN_ATTRIBUTES + OPTIONAL_PATTERN_ATTRIBUTES:
raise ValidationError(f"{attr} not allowed")

def validate_name(self):
if not self.data or "name" not in self.data:
Expand Down