Skip to content

Commit

Permalink
Merge pull request #403 from az-blip/issue263_model_filter
Browse files Browse the repository at this point in the history
Issue263 : Added model filter in neighbordb
  • Loading branch information
dlobato authored Jul 2, 2024
2 parents a05baa8 + 8813c43 commit 9588396
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 13 deletions.
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

0 comments on commit 9588396

Please sign in to comment.