acex-devkit 1.0.0__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/PKG-INFO +2 -1
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/pyproject.toml +2 -1
- acex_devkit-1.0.2/src/acex_devkit/configdiffer/__init__.py +2 -0
- acex_devkit-1.0.2/src/acex_devkit/configdiffer/configdiffer.py +230 -0
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/drivers/base.py +1 -3
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/drivers/base_driver.py +11 -12
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/models/__init__.py +2 -0
- acex_devkit-1.0.2/src/acex_devkit/models/attribute_value.py +108 -0
- acex_devkit-1.0.2/src/acex_devkit/models/composed_configuration.py +579 -0
- acex_devkit-1.0.2/src/acex_devkit/models/external_value.py +42 -0
- acex_devkit-1.0.2/src/acex_devkit/models/logging.py +77 -0
- acex_devkit-1.0.2/src/acex_devkit/models/ned.py +9 -0
- acex_devkit-1.0.2/src/acex_devkit/models/node_response.py +22 -0
- acex_devkit-1.0.2/src/acex_devkit/models/spanning_tree.py +64 -0
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/README.md +0 -0
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/__init__.py +0 -0
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/drivers/__init__.py +0 -0
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/exceptions/__init__.py +0 -0
- {acex_devkit-1.0.0 → acex_devkit-1.0.2}/src/acex_devkit/types/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: acex-devkit
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: ACE-X DevKit - Development kit for building ACE-X drivers and plugins
|
|
5
5
|
License: AGPL-3.0
|
|
6
6
|
Keywords: automation,devkit,sdk,drivers,plugins
|
|
@@ -11,6 +11,7 @@ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: deepdiff (>=8.6.1,<9.0.0)
|
|
14
15
|
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
15
16
|
Requires-Dist: typing-extensions (>=4.0.0,<5.0.0)
|
|
16
17
|
Project-URL: Homepage, https://github.com/acex-labs/acex
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "acex-devkit"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.2"
|
|
4
4
|
description = "ACE-X DevKit - Development kit for building ACE-X drivers and plugins"
|
|
5
5
|
authors = ["Johan Lahti <johan.lahti@acebit.se>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -16,6 +16,7 @@ packages = [
|
|
|
16
16
|
python = "^3.13"
|
|
17
17
|
pydantic = "^2.12.5"
|
|
18
18
|
typing-extensions = "^4.0.0"
|
|
19
|
+
deepdiff = "^8.6.1"
|
|
19
20
|
|
|
20
21
|
[tool.poetry.group.dev.dependencies]
|
|
21
22
|
pytest = "^8.0.0"
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from pydantic import BaseModel, model_validator
|
|
4
|
+
from deepdiff import DeepDiff
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DiffOp(str, Enum):
|
|
8
|
+
ADD = "add"
|
|
9
|
+
REMOVE = "remove"
|
|
10
|
+
CHANGE = "change"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DiffNode(BaseModel):
|
|
14
|
+
op: DiffOp
|
|
15
|
+
before: Optional[Any] = None
|
|
16
|
+
after: Optional[Any] = None
|
|
17
|
+
children: Optional[Dict[str, "DiffNode"]] = None
|
|
18
|
+
|
|
19
|
+
@model_validator(mode="after")
|
|
20
|
+
def validate_invariants(self):
|
|
21
|
+
if self.op == DiffOp.ADD:
|
|
22
|
+
assert self.before is None and self.after is not None
|
|
23
|
+
elif self.op == DiffOp.REMOVE:
|
|
24
|
+
assert self.before is not None and self.after is None
|
|
25
|
+
elif self.op == DiffOp.CHANGE:
|
|
26
|
+
assert self.before is not None and self.after is not None
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Diff(BaseModel):
|
|
31
|
+
root: DiffNode
|
|
32
|
+
|
|
33
|
+
def is_empty(self) -> bool:
|
|
34
|
+
return not bool(self.root.children)
|
|
35
|
+
|
|
36
|
+
def summary(self) -> dict[str, int]:
|
|
37
|
+
stats = {"add": 0, "remove": 0, "change": 0}
|
|
38
|
+
|
|
39
|
+
def walk(node: DiffNode):
|
|
40
|
+
stats[node.op.value] += 1
|
|
41
|
+
if node.children:
|
|
42
|
+
for child in node.children.values():
|
|
43
|
+
walk(child)
|
|
44
|
+
|
|
45
|
+
walk(self.root)
|
|
46
|
+
return stats
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConfigDiffer:
|
|
51
|
+
|
|
52
|
+
def diff(self, *, desired_config: dict, observed_config: dict) -> Diff:
|
|
53
|
+
children = self._diff_dicts(
|
|
54
|
+
desired=desired_config,
|
|
55
|
+
observed=observed_config,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
root = DiffNode(
|
|
59
|
+
op=DiffOp.CHANGE,
|
|
60
|
+
before=observed_config,
|
|
61
|
+
after=desired_config,
|
|
62
|
+
children=children or None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return Diff(root=root)
|
|
66
|
+
|
|
67
|
+
def _remove_keys(self, obj: dict, keys_to_remove: list[str]) -> dict:
|
|
68
|
+
cleaned = {}
|
|
69
|
+
|
|
70
|
+
for k, v in obj.items():
|
|
71
|
+
if k in keys_to_remove:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
if isinstance(v, dict):
|
|
75
|
+
cleaned[k] = self._remove_keys(v, keys_to_remove)
|
|
76
|
+
else:
|
|
77
|
+
cleaned[k] = v
|
|
78
|
+
|
|
79
|
+
return cleaned
|
|
80
|
+
|
|
81
|
+
def _diff_dicts(
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
desired: dict,
|
|
85
|
+
observed: dict,
|
|
86
|
+
) -> Dict[str, DiffNode]:
|
|
87
|
+
"""
|
|
88
|
+
Diff two dicts by traversing down to 'value' attributes and comparing them.
|
|
89
|
+
Returns a hierarchical structure of DiffNodes.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# Remove metadata:
|
|
93
|
+
ignored_keys = ["metadata"]
|
|
94
|
+
observed = self._remove_keys(observed.model_dump(), ignored_keys)
|
|
95
|
+
desired = self._remove_keys(desired.model_dump(), ignored_keys)
|
|
96
|
+
|
|
97
|
+
# Diff
|
|
98
|
+
diff = DeepDiff(
|
|
99
|
+
observed,
|
|
100
|
+
desired
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
result: Dict[str, DiffNode] = {}
|
|
104
|
+
|
|
105
|
+
# Handle added items
|
|
106
|
+
items_added = diff.get("dictionary_item_added", set())
|
|
107
|
+
for path in items_added:
|
|
108
|
+
keys = self._parse_path(path)
|
|
109
|
+
if keys:
|
|
110
|
+
value = self._get_nested_value(desired, keys)
|
|
111
|
+
self._add_to_result(result, keys, DiffNode(
|
|
112
|
+
op=DiffOp.ADD,
|
|
113
|
+
after=value
|
|
114
|
+
))
|
|
115
|
+
|
|
116
|
+
# Handle removed items
|
|
117
|
+
items_removed = diff.get("dictionary_item_removed", set())
|
|
118
|
+
for path in items_removed:
|
|
119
|
+
keys = self._parse_path(path)
|
|
120
|
+
if keys:
|
|
121
|
+
value = self._get_nested_value(observed, keys)
|
|
122
|
+
self._add_to_result(result, keys, DiffNode(
|
|
123
|
+
op=DiffOp.REMOVE,
|
|
124
|
+
before=value
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
# Handle changed values
|
|
128
|
+
values_changed = diff.get("values_changed", {})
|
|
129
|
+
for path, change in values_changed.items():
|
|
130
|
+
keys = self._parse_path(path)
|
|
131
|
+
if keys:
|
|
132
|
+
self._add_to_result(result, keys, DiffNode(
|
|
133
|
+
op=DiffOp.CHANGE,
|
|
134
|
+
before=change["old_value"],
|
|
135
|
+
after=change["new_value"]
|
|
136
|
+
))
|
|
137
|
+
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def _parse_path(self, path: str) -> list[str]:
|
|
141
|
+
"""Parse DeepDiff path like "root['key1']['key2']" into ['key1', 'key2']"""
|
|
142
|
+
import re
|
|
143
|
+
matches = re.findall(r"\['([^']+)'\]|\[\"([^\"]+)\"\]", path)
|
|
144
|
+
return [m[0] or m[1] for m in matches]
|
|
145
|
+
|
|
146
|
+
def _get_nested_value(self, obj: dict, keys: list[str]) -> Any:
|
|
147
|
+
"""Get value from nested dict using list of keys"""
|
|
148
|
+
current = obj
|
|
149
|
+
for key in keys:
|
|
150
|
+
if isinstance(current, dict):
|
|
151
|
+
current = current.get(key)
|
|
152
|
+
else:
|
|
153
|
+
return None
|
|
154
|
+
return current
|
|
155
|
+
|
|
156
|
+
def _add_to_result(self, result: Dict[str, DiffNode], keys: list[str], node: DiffNode):
|
|
157
|
+
"""Add a DiffNode to the result dict at the appropriate nested location"""
|
|
158
|
+
if not keys:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
if len(keys) == 1:
|
|
162
|
+
# Leaf node
|
|
163
|
+
result[keys[0]] = node
|
|
164
|
+
else:
|
|
165
|
+
# Need to create intermediate nodes
|
|
166
|
+
if keys[0] not in result:
|
|
167
|
+
# Create a CHANGE node as intermediate
|
|
168
|
+
result[keys[0]] = DiffNode(
|
|
169
|
+
op=DiffOp.CHANGE,
|
|
170
|
+
before={},
|
|
171
|
+
after={},
|
|
172
|
+
children={}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Ensure it has children dict
|
|
176
|
+
if result[keys[0]].children is None:
|
|
177
|
+
result[keys[0]].children = {}
|
|
178
|
+
|
|
179
|
+
# Recursively add to children
|
|
180
|
+
self._add_to_result(result[keys[0]].children, keys[1:], node)
|
|
181
|
+
|
|
182
|
+
def apply_diff(self, config, diff: Diff):
|
|
183
|
+
"""
|
|
184
|
+
Apply a diff to a configuration.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
config: The base configuration (ComposedConfiguration or dict)
|
|
188
|
+
diff: The diff to apply
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
A new configuration of the same type with the diff applied
|
|
192
|
+
"""
|
|
193
|
+
import copy
|
|
194
|
+
from acex_devkit.models.composed_configuration import ComposedConfiguration
|
|
195
|
+
|
|
196
|
+
# Convert to dict if it's a ComposedConfiguration
|
|
197
|
+
is_composed = isinstance(config, ComposedConfiguration)
|
|
198
|
+
config_dict = config.model_dump(mode="python") if is_composed else config
|
|
199
|
+
|
|
200
|
+
# Deep copy to avoid modifying the original
|
|
201
|
+
result = copy.deepcopy(config_dict)
|
|
202
|
+
|
|
203
|
+
def apply_node(target: dict, node: DiffNode, key: str):
|
|
204
|
+
"""Apply a single diff node to the target dict."""
|
|
205
|
+
if node.op == DiffOp.ADD:
|
|
206
|
+
target[key] = node.after
|
|
207
|
+
elif node.op == DiffOp.REMOVE:
|
|
208
|
+
if key in target:
|
|
209
|
+
del target[key]
|
|
210
|
+
elif node.op == DiffOp.CHANGE:
|
|
211
|
+
if node.children:
|
|
212
|
+
# Has children - recurse deeper
|
|
213
|
+
if key not in target:
|
|
214
|
+
target[key] = {}
|
|
215
|
+
for child_key, child_node in node.children.items():
|
|
216
|
+
apply_node(target[key], child_node, child_key)
|
|
217
|
+
else:
|
|
218
|
+
# Leaf value change
|
|
219
|
+
target[key] = node.after
|
|
220
|
+
|
|
221
|
+
# Apply all root-level changes
|
|
222
|
+
if diff.root.children:
|
|
223
|
+
for key, node in diff.root.children.items():
|
|
224
|
+
apply_node(result, node, key)
|
|
225
|
+
|
|
226
|
+
# Return same type as input
|
|
227
|
+
if is_composed:
|
|
228
|
+
return ComposedConfiguration(**result)
|
|
229
|
+
return result
|
|
230
|
+
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from typing import Any, Dict
|
|
5
5
|
|
|
6
|
-
from acex_devkit.models.logical_node import LogicalNode
|
|
7
|
-
|
|
8
6
|
|
|
9
7
|
class ParserBase(ABC):
|
|
10
8
|
"""Base class for configuration parsers."""
|
|
@@ -98,7 +96,7 @@ class NetworkElementDriver:
|
|
|
98
96
|
self.parser = self.parser_class()
|
|
99
97
|
|
|
100
98
|
@abstractmethod
|
|
101
|
-
def render(self, logical_node: LogicalNode, asset: Any = None) -> Any:
|
|
99
|
+
def render(self, logical_node: "LogicalNode", asset: Any = None) -> Any:
|
|
102
100
|
"""Render logical node to device configuration.
|
|
103
101
|
|
|
104
102
|
Args:
|
|
@@ -39,15 +39,14 @@ class NetworkElementDriver:
|
|
|
39
39
|
self.parser = self.parser_class()
|
|
40
40
|
|
|
41
41
|
@abstractmethod
|
|
42
|
-
def render(self,
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# raise RuntimeError("Verification failed – rollback executed")
|
|
42
|
+
def render(self, configuration: ComposedConfiguration, asset: "Asset") -> Any:
|
|
43
|
+
"""Render configuration from composedconfig and asset."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def parse(self, configuration: str) -> ComposedConfiguration:
|
|
48
|
+
"""
|
|
49
|
+
Parse observed configuration into a composedconfiguration object.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator, field_serializer
|
|
2
|
+
from typing import Union, TypeVar, Generic, Optional, Dict, Any, get_args, get_origin
|
|
3
|
+
from acex_devkit.models import ExternalValue
|
|
4
|
+
|
|
5
|
+
T = TypeVar('T')
|
|
6
|
+
|
|
7
|
+
class AttributeValue(BaseModel, Generic[T]):
|
|
8
|
+
"""
|
|
9
|
+
A generic wrapper for values that may be concrete or external.
|
|
10
|
+
"""
|
|
11
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
12
|
+
|
|
13
|
+
value: Union[T, ExternalValue]
|
|
14
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------
|
|
17
|
+
# 1) PRE-PROCESSOR: Tillåt råa värden, ExternalValue direkt,
|
|
18
|
+
# eller dict → AttributeValue
|
|
19
|
+
# ---------------------------------------------------------
|
|
20
|
+
@model_validator(mode="before")
|
|
21
|
+
@classmethod
|
|
22
|
+
def preprocess_raw(cls, data):
|
|
23
|
+
"""
|
|
24
|
+
Gör varje typ av input till en fullvärdig {"value": ...} dict
|
|
25
|
+
så att Pydantic kan fortsätta normalt.
|
|
26
|
+
"""
|
|
27
|
+
# 1) Rått värde (str, int, ExternalValue etc)
|
|
28
|
+
if not isinstance(data, dict):
|
|
29
|
+
return {"value": data}
|
|
30
|
+
|
|
31
|
+
# 2) Dict som representerar ExternalValue
|
|
32
|
+
if "value" not in data and "ref" in data:
|
|
33
|
+
return {"value": ExternalValue(**data)}
|
|
34
|
+
|
|
35
|
+
# 3) Dict som redan har value → låt vara
|
|
36
|
+
return data
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------
|
|
39
|
+
# 2) VALIDATOR för value
|
|
40
|
+
# ---------------------------------------------------------
|
|
41
|
+
@field_validator("value", mode="before")
|
|
42
|
+
@classmethod
|
|
43
|
+
def normalize_value(cls, v):
|
|
44
|
+
if isinstance(v, cls):
|
|
45
|
+
return v.value
|
|
46
|
+
if isinstance(v, ExternalValue):
|
|
47
|
+
return v
|
|
48
|
+
if isinstance(v, dict) and "ref" in v:
|
|
49
|
+
return ExternalValue(**v)
|
|
50
|
+
return v
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------
|
|
53
|
+
# 3) METADATA-generator (after)
|
|
54
|
+
# Bevarar redan satt metadata!
|
|
55
|
+
# ---------------------------------------------------------
|
|
56
|
+
@model_validator(mode="after")
|
|
57
|
+
def set_automatic_metadata(self):
|
|
58
|
+
"""
|
|
59
|
+
Lägg till metadata utan att ta bort användarens egna.
|
|
60
|
+
"""
|
|
61
|
+
self.metadata = dict(self.metadata or {}) # KOPIA – ändra inte originalet
|
|
62
|
+
|
|
63
|
+
if isinstance(self.value, ExternalValue):
|
|
64
|
+
self.metadata.setdefault("value_type", "external")
|
|
65
|
+
self.metadata.setdefault("attr_ptr", self.value.attr_ptr)
|
|
66
|
+
self.metadata.setdefault("plugin", self.value.plugin)
|
|
67
|
+
self.metadata.setdefault(
|
|
68
|
+
"ev_type",
|
|
69
|
+
self.value.ev_type.value
|
|
70
|
+
if hasattr(self.value.ev_type, "value")
|
|
71
|
+
else self.value.ev_type,
|
|
72
|
+
)
|
|
73
|
+
self.metadata.setdefault("query", self.value.query)
|
|
74
|
+
self.metadata.setdefault("resolved", self.value.resolved)
|
|
75
|
+
|
|
76
|
+
if self.value.kind:
|
|
77
|
+
self.metadata.setdefault("kind", self.value.kind)
|
|
78
|
+
|
|
79
|
+
if self.value.resolved and self.value.resolved_at:
|
|
80
|
+
self.metadata.setdefault(
|
|
81
|
+
"resolved_at",
|
|
82
|
+
self.value.resolved_at.isoformat()
|
|
83
|
+
if hasattr(self.value.resolved_at, "isoformat")
|
|
84
|
+
else str(self.value.resolved_at),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
else:
|
|
88
|
+
self.metadata.setdefault("value_type", "concrete")
|
|
89
|
+
self.metadata.setdefault("type", type(self.value).__name__)
|
|
90
|
+
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------
|
|
94
|
+
# 4) SERIALIZER – external value → returnera dess "value"
|
|
95
|
+
# ---------------------------------------------------------
|
|
96
|
+
@field_serializer("value")
|
|
97
|
+
def serialize_value(self, value):
|
|
98
|
+
if isinstance(value, ExternalValue):
|
|
99
|
+
return value.value
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
# Convenience helpers
|
|
103
|
+
def is_external(self) -> bool:
|
|
104
|
+
return isinstance(self.value, ExternalValue)
|
|
105
|
+
|
|
106
|
+
def get_value(self) -> T:
|
|
107
|
+
return self.value.value if isinstance(self.value, ExternalValue) else self.value
|
|
108
|
+
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from typing import Optional, Dict, List, Literal, ClassVar, Union, Any
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
from acex_devkit.models.external_value import ExternalValue
|
|
7
|
+
from acex_devkit.models.attribute_value import AttributeValue
|
|
8
|
+
from acex_devkit.models.logging import (
|
|
9
|
+
LoggingConfig,
|
|
10
|
+
Console,
|
|
11
|
+
RemoteServer,
|
|
12
|
+
VtyLine,
|
|
13
|
+
FileLogging,
|
|
14
|
+
LoggingEvents
|
|
15
|
+
)
|
|
16
|
+
from acex_devkit.models.spanning_tree import SpanningTree
|
|
17
|
+
|
|
18
|
+
class MetadataValueType(str, Enum):
|
|
19
|
+
CONCRETE = "concrete"
|
|
20
|
+
EXTERNALVALUE = "externalValue"
|
|
21
|
+
REFERENCE = "reference"
|
|
22
|
+
|
|
23
|
+
class Metadata(BaseModel):
|
|
24
|
+
type: Optional[str] = "str"
|
|
25
|
+
value_source: MetadataValueType = MetadataValueType.CONCRETE
|
|
26
|
+
|
|
27
|
+
class Reference(BaseModel):
|
|
28
|
+
pointer: str
|
|
29
|
+
metadata: Metadata = Metadata(type="str", value_source="reference")
|
|
30
|
+
|
|
31
|
+
class ReferenceTo(Reference):
|
|
32
|
+
pointer: str
|
|
33
|
+
metadata: Optional[Dict] = {}
|
|
34
|
+
|
|
35
|
+
class ReferenceFrom(Reference):
|
|
36
|
+
pointer: str
|
|
37
|
+
metadata: Optional[Dict] = {}
|
|
38
|
+
|
|
39
|
+
class RenderedReference(BaseModel):
|
|
40
|
+
from_ptr: str
|
|
41
|
+
to_ptr: str
|
|
42
|
+
|
|
43
|
+
class SystemConfig(BaseModel):
|
|
44
|
+
contact: Optional[AttributeValue[str]] = None
|
|
45
|
+
domain_name: Optional[AttributeValue[str]] = None
|
|
46
|
+
hostname: Optional[AttributeValue[str]] = None
|
|
47
|
+
location: Optional[AttributeValue[str]] = None
|
|
48
|
+
|
|
49
|
+
class TripleA(BaseModel): ...
|
|
50
|
+
|
|
51
|
+
# Trying to avoid using "Logging" or "logging" as names for anything due to conflicts with standard lib.
|
|
52
|
+
class LoggingComponents(BaseModel):
|
|
53
|
+
config: LoggingConfig = LoggingConfig()
|
|
54
|
+
console: Optional[Console] = None
|
|
55
|
+
remote_servers: Optional[Dict[str, RemoteServer]] = {}
|
|
56
|
+
events: Optional[LoggingEvents] = None
|
|
57
|
+
vty: Optional[Dict[str, VtyLine]] = {}
|
|
58
|
+
files: Optional[Dict[str, FileLogging]] = {}
|
|
59
|
+
|
|
60
|
+
class NtpConfig(BaseModel):
|
|
61
|
+
enabled: AttributeValue[bool] = AttributeValue(value=False)
|
|
62
|
+
|
|
63
|
+
class NtpServer(BaseModel):
|
|
64
|
+
address: AttributeValue[str]
|
|
65
|
+
port: Optional[AttributeValue[int]] = None
|
|
66
|
+
version: Optional[AttributeValue[int]] = None
|
|
67
|
+
association_typ: Optional[AttributeValue[str]] = None
|
|
68
|
+
prefer: Optional[AttributeValue[bool]] = None
|
|
69
|
+
source_interface: Optional[AttributeValue[str]] = None
|
|
70
|
+
|
|
71
|
+
class Ntp(BaseModel):
|
|
72
|
+
config: Optional[NtpConfig] = None
|
|
73
|
+
servers: Optional[Dict[str, NtpServer]] = {}
|
|
74
|
+
|
|
75
|
+
class SshServer(BaseModel):
|
|
76
|
+
enable: Optional[AttributeValue[bool]] = None
|
|
77
|
+
protocol_version: Optional[AttributeValue[int]] = AttributeValue(value=2)
|
|
78
|
+
timeout: Optional[AttributeValue[int]] = None
|
|
79
|
+
auth_retries: Optional[AttributeValue[int]] = None
|
|
80
|
+
source_interface: Optional[Reference] = None
|
|
81
|
+
|
|
82
|
+
class AuthorizedKeyAlgorithms(str, Enum):
|
|
83
|
+
SSH_ED25519 = "ssh-ed25519"
|
|
84
|
+
ECDSA_NISTP256 = "ecdsa-sha2-nistp256"
|
|
85
|
+
ECDSA_NISTP384 = "ecdsa-sha2-nistp384"
|
|
86
|
+
ECDSA_NISTP521 = "ecdsa-sha2-nistp521"
|
|
87
|
+
RSA_SHA2_256 = "rsa-sha2-256"
|
|
88
|
+
RSA_SHA2_512 = "rsa-sha2-512"
|
|
89
|
+
SK_SSH_ED25519 = "sk-ssh-ed25519@openssh.com"
|
|
90
|
+
SK_ECDSA_NISTP256 = "sk-ecdsa-sha2-nistp256@openssh.com"
|
|
91
|
+
SSH_RSA = "ssh-rsa"
|
|
92
|
+
SSH_DSS = "ssh-dss"
|
|
93
|
+
|
|
94
|
+
class AuthorizedKey(BaseModel):
|
|
95
|
+
algorithm: AuthorizedKeyAlgorithms
|
|
96
|
+
public_key: str
|
|
97
|
+
|
|
98
|
+
class Ssh(BaseModel):
|
|
99
|
+
config: Optional[SshServer] = None
|
|
100
|
+
host_keys: Optional[Dict[str, AuthorizedKey]] = {}
|
|
101
|
+
|
|
102
|
+
class Acl(BaseModel): ...
|
|
103
|
+
class Lldp(BaseModel): ...
|
|
104
|
+
|
|
105
|
+
class Vlan(BaseModel):
|
|
106
|
+
name: AttributeValue[str]
|
|
107
|
+
vlan_id: Optional[AttributeValue[int]] = None
|
|
108
|
+
vlan_name: Optional[AttributeValue[str]] = None
|
|
109
|
+
network_instance: Optional[AttributeValue[str]] = None
|
|
110
|
+
metadata: Optional[Metadata] = Metadata()
|
|
111
|
+
|
|
112
|
+
class Interface(BaseModel):
|
|
113
|
+
"Base class for all interfaces"
|
|
114
|
+
index: AttributeValue[int]
|
|
115
|
+
name: AttributeValue[str]
|
|
116
|
+
|
|
117
|
+
description: Optional[AttributeValue[str]] = None
|
|
118
|
+
enabled: Optional[AttributeValue[bool]] = None
|
|
119
|
+
ipv4: Optional[AttributeValue[str]] = None
|
|
120
|
+
|
|
121
|
+
metadata: Optional[Metadata] = Metadata()
|
|
122
|
+
type: Literal[
|
|
123
|
+
"ethernetCsmacd",
|
|
124
|
+
"ieee8023adLag",
|
|
125
|
+
"l3ipvlan",
|
|
126
|
+
"softwareLoopback",
|
|
127
|
+
"subinterface",
|
|
128
|
+
"managementInterface"
|
|
129
|
+
] = "ethernetCsmacd"
|
|
130
|
+
|
|
131
|
+
model_config = {
|
|
132
|
+
"discriminator": "type"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class EthernetCsmacdInterface(Interface):
|
|
137
|
+
"Physical Interface"
|
|
138
|
+
type: Literal["ethernetCsmacd"] = "ethernetCsmacd"
|
|
139
|
+
|
|
140
|
+
# Egenskaper för fysiska interface
|
|
141
|
+
stack_index: Optional[AttributeValue[int]] = None
|
|
142
|
+
module_index: Optional[AttributeValue[int]] = None
|
|
143
|
+
subinterfaces: list["SubInterface"] = Field(default_factory=list)
|
|
144
|
+
speed: Optional[AttributeValue[int]] = None
|
|
145
|
+
duplex: Optional[AttributeValue[str]] = None
|
|
146
|
+
switchport: Optional[AttributeValue[bool]] = None
|
|
147
|
+
switchport_mode: Optional[AttributeValue[Literal["access", "trunk"]]] = None
|
|
148
|
+
trunk_allowed_vlans: Optional[AttributeValue[List[int]]] = None
|
|
149
|
+
native_vlan: Optional[AttributeValue[int]] = None
|
|
150
|
+
access_vlan: Optional[AttributeValue[int]] = None
|
|
151
|
+
vlan_id: Optional[AttributeValue[int]] = None
|
|
152
|
+
voice_vlan: Optional[AttributeValue[int]] = None
|
|
153
|
+
mtu: Optional[AttributeValue[int]] = None # No default set as it differs between devices and vendors
|
|
154
|
+
negotiation: Optional[AttributeValue[bool]] = None
|
|
155
|
+
|
|
156
|
+
# LACP relaterade attribut
|
|
157
|
+
aggregate_id: Optional[AttributeValue[int]] = None
|
|
158
|
+
lacp_enabled: Optional[AttributeValue[bool]] = None
|
|
159
|
+
lacp_mode: Optional[AttributeValue[Literal["active", "passive", "on", "auto"]]] = None
|
|
160
|
+
lacp_port_priority: Optional[AttributeValue[int]] = None
|
|
161
|
+
#lacp_system_id_mac: Optional[AttributeValue[str]] = None
|
|
162
|
+
lacp_interval: Optional[AttributeValue[Literal["fast", "slow"]]] = None
|
|
163
|
+
|
|
164
|
+
# Spanning-tree relaterade attribut
|
|
165
|
+
stp_port_priority: Optional[int] = None
|
|
166
|
+
stp_cost: Optional[int] = None
|
|
167
|
+
stp_edge_port: Optional[bool] = False # Disabled by default
|
|
168
|
+
stp_bpdu_filter: Optional[bool] = False # Disabled by default
|
|
169
|
+
stp_bpdu_guard: Optional[bool] = False # Disabled by default
|
|
170
|
+
stp_loop_guard: Optional[bool] = False # Disabled by default
|
|
171
|
+
stp_root_guard: Optional[bool] = False # Disabled by default
|
|
172
|
+
stp_portfast: Optional[bool] = False # Disabled by default
|
|
173
|
+
stp_link_type: Optional[Literal["point-to-point", "shared"]] = None # e.g., "point-to-point", "shared"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class Ieee8023adLagInterface(Interface):
|
|
177
|
+
"LAG Interface"
|
|
178
|
+
type: Literal["ieee8023adLag"] = "ieee8023adLag"
|
|
179
|
+
#aggregate_id: AttributeValue[int] = None
|
|
180
|
+
aggregate_id: int = None
|
|
181
|
+
members: list[str] = Field(default_factory=list)
|
|
182
|
+
max_ports: Optional[AttributeValue[int]] = None
|
|
183
|
+
switchport: Optional[AttributeValue[bool]] = None
|
|
184
|
+
switchport_mode: Optional[AttributeValue[Literal["access", "trunk"]]] = None
|
|
185
|
+
trunk_allowed_vlans: Optional[AttributeValue[List[int]]] = None
|
|
186
|
+
native_vlan: Optional[AttributeValue[int]] = None
|
|
187
|
+
mtu: Optional[AttributeValue[int]] = None # No default set as it differs between devices and vendors
|
|
188
|
+
|
|
189
|
+
class L3IpvlanInterface(Interface):
|
|
190
|
+
"SVI Interface"
|
|
191
|
+
type: Literal["l3ipvlan"] = "l3ipvlan"
|
|
192
|
+
vlan_id: Optional[int] = None
|
|
193
|
+
|
|
194
|
+
class SoftwareLoopbackInterface(Interface):
|
|
195
|
+
"Loopback Interface"
|
|
196
|
+
type: Literal["softwareLoopback"] = "softwareLoopback"
|
|
197
|
+
|
|
198
|
+
# Loopback har varken vlan, duplex eller speed
|
|
199
|
+
vlan_id: Optional[int] = None
|
|
200
|
+
ipv4: Optional[AttributeValue[str]] = None
|
|
201
|
+
|
|
202
|
+
class SubInterface(Interface):
|
|
203
|
+
"Subinterface"
|
|
204
|
+
type: Literal["subinterface"] = "subinterface"
|
|
205
|
+
|
|
206
|
+
vlan_id: Optional[int] = None
|
|
207
|
+
ipv4: Optional[AttributeValue[str]] = None
|
|
208
|
+
|
|
209
|
+
class ManagementInterface(Interface):
|
|
210
|
+
"Management Interface"
|
|
211
|
+
type: Literal["managementInterface"] = "managementInterface"
|
|
212
|
+
|
|
213
|
+
# Mgmt har inte vlan
|
|
214
|
+
vlan_id: Optional[int] = None
|
|
215
|
+
|
|
216
|
+
class RouteTarget(BaseModel):
|
|
217
|
+
value: str # TODO: Add constraints and validators...
|
|
218
|
+
|
|
219
|
+
class ImportExportPolicy(BaseModel):
|
|
220
|
+
export_route_target: Optional[List[RouteTarget]] = None
|
|
221
|
+
import_route_target: Optional[List[RouteTarget]] = None
|
|
222
|
+
|
|
223
|
+
class InterInstancePolicy(BaseModel):
|
|
224
|
+
import_export_policy: ImportExportPolicy
|
|
225
|
+
|
|
226
|
+
class NetworkInstance(BaseModel):
|
|
227
|
+
name: AttributeValue[str]
|
|
228
|
+
description: Optional[AttributeValue[str]] = None
|
|
229
|
+
vlans: Optional[Dict[str, Vlan]] = {}
|
|
230
|
+
interfaces: Optional[Dict[str, Reference]] = {}
|
|
231
|
+
inter_instance_policies: Optional[Dict[str, InterInstancePolicy]] = {}
|
|
232
|
+
|
|
233
|
+
class LacpConfig(BaseModel):
|
|
234
|
+
system_priority: Optional[AttributeValue[int]] = None
|
|
235
|
+
system_id_mac: Optional[AttributeValue[str]] = None
|
|
236
|
+
load_balance_algorithm: Optional[AttributeValue[list[Literal["src-mac", "dst-mac", "src-dst-mac", "src-ip", "dst-ip", "src-dst-ip", "src-port", "dst-port", "src-dst-port"]]]] = None
|
|
237
|
+
|
|
238
|
+
class Lacp(BaseModel):
|
|
239
|
+
config: Optional[LacpConfig] = LacpConfig()
|
|
240
|
+
interfaces: Optional[Dict[str, Interface]] = {}
|
|
241
|
+
|
|
242
|
+
# SNMP
|
|
243
|
+
class SnmpAccess(str, Enum):
|
|
244
|
+
READ_ONLY = "READ_ONLY"
|
|
245
|
+
READ_WRITE = "READ_WRITE"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class SnmpSecurityLevel(str, Enum):
|
|
249
|
+
NO_AUTH_NO_PRIV = "NO_AUTH_NO_PRIV"
|
|
250
|
+
AUTH_NO_PRIV = "AUTH_NO_PRIV"
|
|
251
|
+
AUTH_PRIV = "AUTH_PRIV"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class SnmpAuthProtocol(str, Enum):
|
|
255
|
+
MD5 = "MD5"
|
|
256
|
+
SHA1 = "SHA"
|
|
257
|
+
SHA224 = "SHA-224"
|
|
258
|
+
SHA256 = "SHA-256"
|
|
259
|
+
SHA384 = "SHA-384"
|
|
260
|
+
SHA512 = "SHA-512"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class SnmpPrivProtocol(str, Enum):
|
|
264
|
+
DES = "DES"
|
|
265
|
+
TRIPLE_DES = "3DES"
|
|
266
|
+
AES128 = "AES128"
|
|
267
|
+
AES192 = "AES192"
|
|
268
|
+
AES256 = "AES256"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class SnmpConfig(BaseModel):
|
|
272
|
+
enabled: AttributeValue[bool] = AttributeValue(value=False)
|
|
273
|
+
engine_id: Optional[AttributeValue[str]] = None
|
|
274
|
+
location: Optional[AttributeValue[str]] = None
|
|
275
|
+
contact: Optional[AttributeValue[str]] = None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class SnmpCommunity(BaseModel):
|
|
279
|
+
name: AttributeValue[str]
|
|
280
|
+
community: Optional[AttributeValue[str]] = None # Community string
|
|
281
|
+
access: Optional[AttributeValue[SnmpAccess]] = AttributeValue(value=SnmpAccess.READ_ONLY)
|
|
282
|
+
view: Optional[AttributeValue[str]] = None
|
|
283
|
+
ipv4_acl: Optional[AttributeValue[str]] = None # Cisco and "liknande" vendors
|
|
284
|
+
ipv6_acl: Optional[AttributeValue[str]] = None
|
|
285
|
+
source_interface: Optional[Reference] = None
|
|
286
|
+
clients: Optional[AttributeValue[List[str]]] = None # Juniper specific
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class SnmpUser(BaseModel):
|
|
290
|
+
username: AttributeValue[str]
|
|
291
|
+
security_level: Optional[AttributeValue[SnmpSecurityLevel]] = AttributeValue(value=SnmpSecurityLevel.NO_AUTH_NO_PRIV)
|
|
292
|
+
auth_protocol: Optional[AttributeValue[SnmpAuthProtocol]] = None
|
|
293
|
+
auth_password: Optional[AttributeValue[str]] = None
|
|
294
|
+
priv_protocol: Optional[AttributeValue[SnmpPrivProtocol]] = None
|
|
295
|
+
priv_password: Optional[AttributeValue[str]] = None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class SnmpView(BaseModel):
|
|
299
|
+
name: AttributeValue[str]
|
|
300
|
+
oid: AttributeValue[str]
|
|
301
|
+
included: Optional[AttributeValue[bool]] = AttributeValue(value=True)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class SnmpServer(BaseModel):
|
|
305
|
+
name: str
|
|
306
|
+
address: AttributeValue[str]
|
|
307
|
+
port: Optional[AttributeValue[int]] = AttributeValue(value=162)
|
|
308
|
+
enabled: Optional[AttributeValue[bool]] = AttributeValue(value=True)
|
|
309
|
+
version: Optional[AttributeValue[Literal["v2c", "v3"]]] = None
|
|
310
|
+
community: Optional[AttributeValue[str]] = None
|
|
311
|
+
username: Optional[AttributeValue[str]] = None
|
|
312
|
+
security_level: Optional[AttributeValue[SnmpSecurityLevel]] = None
|
|
313
|
+
source_interface: Optional[Reference] = None
|
|
314
|
+
network_instance: Optional[AttributeValue[str]] = None
|
|
315
|
+
|
|
316
|
+
# ----------------------------
|
|
317
|
+
# Enum-based trap groups
|
|
318
|
+
# ----------------------------
|
|
319
|
+
# En stor class med Enum
|
|
320
|
+
class TrapEventOptions(str, Enum):
|
|
321
|
+
VRF_UP = "vrf-up"
|
|
322
|
+
VRF_DOWN = "vrf-down"
|
|
323
|
+
VNET_TRUNK_UP = "vnet-trunk-up"
|
|
324
|
+
VNET_TRUNK_DOWN = "vnet-trunk-down"
|
|
325
|
+
ALL = "all"
|
|
326
|
+
RfEvent = 'enabled'
|
|
327
|
+
VlanMembershipEvent = 'enabled'
|
|
328
|
+
ErrdisableEvent = 'enabled'
|
|
329
|
+
CHANGE = "change"
|
|
330
|
+
MOVE = "move"
|
|
331
|
+
THRESHOLD = "threshold"
|
|
332
|
+
AUTHENTICATION = "authentication"
|
|
333
|
+
LINKDOWN = "linkdown"
|
|
334
|
+
LINKUP = "linkup"
|
|
335
|
+
COLDSTART = "coldstart"
|
|
336
|
+
WARMSTART = "warmstart"
|
|
337
|
+
FLOWMONEVENT = "trapflowmonevent"
|
|
338
|
+
ENTITYPERFEVENT = "trapentityperfevent"
|
|
339
|
+
PCALLHOMEEVENT_MESSAGE_SEND_FAIL = "trapcallhomeevent_MESSAGE_SEND_FAIL"
|
|
340
|
+
CALLHOMEEVENT_SERVER_FAIL = "trapcallhomeevent_SERVER_FAIL"
|
|
341
|
+
TTYEVENT = "trapttyevent"
|
|
342
|
+
EIGRPEVENT = "trapeigrpevent"
|
|
343
|
+
OSPF_STATE_CHANGE = "ospf_state_change"
|
|
344
|
+
OSPF_ERRORS = "ospf_errors"
|
|
345
|
+
OSPF_RETRANSMIT = "ospf_retransmit"
|
|
346
|
+
OSPF_LSA = "ospf_lsa"
|
|
347
|
+
OSPF_CISCO_TRANS_CHANGE = "ospf_cisco_trans_change"
|
|
348
|
+
OSPF_CISCO_SHAMLINK_INTERFACE = "ospf_cisco_shamlink_interface"
|
|
349
|
+
OSPF_CISCO_SHAMLINK_NEIGHBOR = "ospf_cisco_shamlink_neighbor"
|
|
350
|
+
BFD_EVENT = "bfd_event"
|
|
351
|
+
CISCO_SMART_LICENSE_EVENT = "cisco_smart_license_event"
|
|
352
|
+
AUTH_FRAMEWORK_SEC_VIOLATION = "auth_framework_sec_violation"
|
|
353
|
+
REP_EVENT = "rep_event"
|
|
354
|
+
MEMORY_BUFFERPEAK = "memory_bufferpeak"
|
|
355
|
+
CONFIG_COPY = "config_copy"
|
|
356
|
+
CONFIG = "config"
|
|
357
|
+
CONFIG_CTID = "config_ctid"
|
|
358
|
+
ENERGYWISE_EVENT = "energy_wise_event"
|
|
359
|
+
FRU_CTRL_EVENT = "fru_ctrl_event"
|
|
360
|
+
ENTITY_EVENT = "entity_event"
|
|
361
|
+
FLASH_INSERTION = "flash_insertion"
|
|
362
|
+
FLASH_REMOVAL = "flash_removal"
|
|
363
|
+
FLASH_LOWSPACE = "flash_lowspace"
|
|
364
|
+
POWER_ETHERNET_POLICE = "power_ethernet_police"
|
|
365
|
+
POWER_ETHERNET_GROUP_THRESHOLD = "power_ethernet_group_threshold"
|
|
366
|
+
CPU_THRESHOLD = "cpu_threshold"
|
|
367
|
+
SYSLOG = "syslog"
|
|
368
|
+
UDLD_LINK_FAIL_RPT = "udld_link_fail_rpt"
|
|
369
|
+
UDLD_STATUS_CHANGE = "udld_status_change"
|
|
370
|
+
VTP_EVENT = "vtp_event"
|
|
371
|
+
VLAN_CREATE = "vlancreate"
|
|
372
|
+
VLAN_DELETE = "vlandelete"
|
|
373
|
+
PORT_SECURITY = "port_security"
|
|
374
|
+
ENV_MON = "env_mon"
|
|
375
|
+
STACKWISE = "stackwise"
|
|
376
|
+
MVPN = "mvpn"
|
|
377
|
+
PW_VC = "pw_vc"
|
|
378
|
+
IPSLA = "ipsla"
|
|
379
|
+
DHCP = "dhcp"
|
|
380
|
+
EVENT_MANAGER = "event_manager"
|
|
381
|
+
IKE_POLICY_ADD = "ike_policy_add"
|
|
382
|
+
IKE_POLICY_DELETE = "ike_policy_delete"
|
|
383
|
+
IKE_TUNNEL_START = "ike_tunnel_start"
|
|
384
|
+
IKE_TUNNEL_STOP = "ike_tunnel_stop"
|
|
385
|
+
IPSEC_CRYPTOMAP_ADD = "ipsec_cryptomap_add"
|
|
386
|
+
IPSEC_CRYPTOMAP_DELETE = "ipsec_cryptomap_delete"
|
|
387
|
+
IPSEC_CRYPTOMAP_ATTACH = "ipsec_cryptomap_attach"
|
|
388
|
+
IPSEC_CRYPTOMAP_DETACH = "ipsec_cryptomap_detach"
|
|
389
|
+
IPSEC_TUNNEL_START = "ipsec_tunnel_start"
|
|
390
|
+
IPSEC_TUNNEL_STOP = "ipsec_tunnel_stop"
|
|
391
|
+
IPSEC_TOO_MANY_SAS = "ipsec_too_many_sas"
|
|
392
|
+
OSPFV3_STATE_CHANGE = "ospfv3_state_change"
|
|
393
|
+
OSPFV3_ERRORS = "ospfv3_errors"
|
|
394
|
+
IP_MULTICAST = "ip_multicast"
|
|
395
|
+
MSDP = "msdp"
|
|
396
|
+
PIM_NEIGHBOR_CHANGE = "pim_neighbor_change"
|
|
397
|
+
PIM_RP_MAPPING_CHANGE = "pim_rp_mapping_change"
|
|
398
|
+
INVALID_PIM_MESSAGE = "invalid_pim_message"
|
|
399
|
+
BRIDGE_NEWROOT = "bridge_newroot"
|
|
400
|
+
BRIDGE_TOPOLOGYCHANGE = "bridge_topologychange"
|
|
401
|
+
STPX_INCONSISTENCY = "stpx_inconsistency"
|
|
402
|
+
STPX_ROOT_INCONSISTENCY = "stpx_root_inconsistency"
|
|
403
|
+
STPX_LOOP_INCONSISTENCY = "stpx_loop_inconsistency"
|
|
404
|
+
BGP_CBG2 = "bgp_cbg2"
|
|
405
|
+
HSRP = "hsrp"
|
|
406
|
+
ISIS = "isis"
|
|
407
|
+
CEF_RESOURCE_FAILURE = "cef_resource_failure"
|
|
408
|
+
CEF_PEER_STATE_CHANGE = "cef_peer_state_change"
|
|
409
|
+
CEF_PEER_FIB_STATE_CHANGE = "cef_peer_fib_state_change"
|
|
410
|
+
CEF_INCONSISTENCY = "cef_inconsistency"
|
|
411
|
+
LISP = "lisp"
|
|
412
|
+
NHRP_NHS = "nhrp_nhs"
|
|
413
|
+
NHRP_NHC = "nhrp_nhc"
|
|
414
|
+
NHRP_NHP = "nhrp_nhp"
|
|
415
|
+
NHRP_QUOTA_EXCEEDED = "nhrp_quota_exceeded"
|
|
416
|
+
LOCAL_AUTH = "local_auth"
|
|
417
|
+
ENTITY_DIAG_BOOT_UP_FAIL = "entity_diag_boot_up_fail"
|
|
418
|
+
ENTITY_DIAG_HM_TEST_RECOVER = "entity_diag_hm_test_recover"
|
|
419
|
+
ENTITY_DIAG_HM_THRESH_REACHED = "entity_diag_hm_thresh_reached"
|
|
420
|
+
ENTITY_DIAG_SCHEDULED_TEST_FAIL = "entity_diag_scheduled_test_fail"
|
|
421
|
+
MPLS_RFC_LDP = "mpls_rfc_ldp"
|
|
422
|
+
MPLS_LDP = "mpls_ldp"
|
|
423
|
+
MPLS_RFC_TRAFFIC_ENG = "mpls_rfc_traffic_eng"
|
|
424
|
+
MPLS_TRAFFIC_ENG = "mpls_traffic_eng"
|
|
425
|
+
MPLS_FAST_REROUTE_PROTECTED = "mpls_fast_reroute_protected"
|
|
426
|
+
MPLS_VPN = "mpls_vpn"
|
|
427
|
+
MPLS_RFC_VPN = "mpls_rfc_vpn"
|
|
428
|
+
BULKSTAT_COLLECTION = "bulkstat_collection"
|
|
429
|
+
BULKSTAT_TRANSFER = "bulkstat_transfer"
|
|
430
|
+
|
|
431
|
+
class TrapEvent(BaseModel):
|
|
432
|
+
name: str
|
|
433
|
+
event_name: TrapEventOptions
|
|
434
|
+
|
|
435
|
+
#class SnmpTrap(BaseModel): ...
|
|
436
|
+
|
|
437
|
+
class Snmp(BaseModel):
|
|
438
|
+
config: SnmpConfig = SnmpConfig()
|
|
439
|
+
communities: Optional[Dict[str, SnmpCommunity]] = {}
|
|
440
|
+
users: Optional[Dict[str, SnmpUser]] = {}
|
|
441
|
+
trap_servers: Optional[Dict[str, SnmpServer]] = {}
|
|
442
|
+
trap_events: Optional[Dict[str, TrapEvent]] = {}
|
|
443
|
+
views: Optional[Dict[str, SnmpView]] = {}
|
|
444
|
+
|
|
445
|
+
# AAA
|
|
446
|
+
class aaaBaseClass(BaseModel):
|
|
447
|
+
name: str = None
|
|
448
|
+
|
|
449
|
+
class aaaTacacsAttributes(aaaBaseClass):
|
|
450
|
+
port: Optional[int] = 49
|
|
451
|
+
secret_key: Optional[str] = None
|
|
452
|
+
secret_key_hashed: Optional[str] = None
|
|
453
|
+
address: Optional[str] = None
|
|
454
|
+
timeout: Optional[int] = 30
|
|
455
|
+
source_address: Optional[str] = None #Optional[Reference] = None # should be reference
|
|
456
|
+
|
|
457
|
+
class aaaRadiusAttributes(aaaBaseClass):
|
|
458
|
+
auth_port: Optional[int] = 1812
|
|
459
|
+
acct_port: Optional[int] = 1813
|
|
460
|
+
secret_key: Optional[str] = None
|
|
461
|
+
secret_key_hashed: Optional[str] = None
|
|
462
|
+
address: Optional[str] = None
|
|
463
|
+
timeout: Optional[int] = 30
|
|
464
|
+
source_address: Optional[str] = None #Optional[Reference] = None # should be reference
|
|
465
|
+
retransmit_attempts: Optional[int] = 3
|
|
466
|
+
|
|
467
|
+
class aaaServerGroupAttributes(BaseModel):
|
|
468
|
+
enable: Optional[bool] = False
|
|
469
|
+
type: Optional[Literal['tacacs','radius']] = None
|
|
470
|
+
#servers: Optional[list] = None
|
|
471
|
+
#address: Optional[str] = None
|
|
472
|
+
#timeout: Optional[int] = 30
|
|
473
|
+
tacacs: Optional[Reference] = None
|
|
474
|
+
radius: Optional[Reference] = None
|
|
475
|
+
|
|
476
|
+
# Authentication Models
|
|
477
|
+
class aaaAuthenticationMethods(aaaBaseClass):
|
|
478
|
+
method: Optional[List[str]] = None # Ex. ['TACACS_GROUP','LOCAL'], TACACS_GROUP is reference to server group
|
|
479
|
+
|
|
480
|
+
class authenticationUser(aaaBaseClass):
|
|
481
|
+
username: Optional[str] = None
|
|
482
|
+
password: Optional[str] = None
|
|
483
|
+
password_hahsed: Optional[str] = None
|
|
484
|
+
ssh_key: Optional[str] = None
|
|
485
|
+
role: Optional[str] = None
|
|
486
|
+
|
|
487
|
+
class aaaAuthenticationUsers(aaaBaseClass):
|
|
488
|
+
username: Optional[Dict[str, authenticationUser]] = {}
|
|
489
|
+
|
|
490
|
+
class adminUser(aaaBaseClass): # when to use this?
|
|
491
|
+
admin_password: Optional[str] = None
|
|
492
|
+
admin_password_hashed: Optional[str] = None
|
|
493
|
+
|
|
494
|
+
class aaaAuthenticationAdminUsers(BaseModel):
|
|
495
|
+
config: Optional[Dict[str, adminUser]] = {}
|
|
496
|
+
|
|
497
|
+
class aaaAuthentication(BaseModel):
|
|
498
|
+
config: Optional[Dict[str, aaaAuthenticationMethods]] = {}
|
|
499
|
+
admin_user: Optional[Dict[str, aaaAuthenticationAdminUsers]] = {}
|
|
500
|
+
users: Optional[Dict[str, aaaAuthenticationUsers]] = {}
|
|
501
|
+
|
|
502
|
+
# Authorization Models
|
|
503
|
+
class aaaAuthorizationMethods(aaaBaseClass):
|
|
504
|
+
method: Optional[List[str]] = None # Ex. ['TACACS_GROUP','LOCAL']
|
|
505
|
+
|
|
506
|
+
class aaaAuthorizationEvent(aaaBaseClass):
|
|
507
|
+
event_type: dict = {
|
|
508
|
+
'event-type':'command',
|
|
509
|
+
'method':['tacacs_group']
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
class aaaAuthorizationEvents(BaseModel):
|
|
513
|
+
event: Optional[Dict[str, aaaAuthorizationEvent]] = {}
|
|
514
|
+
|
|
515
|
+
class aaaAuthorization(BaseModel):
|
|
516
|
+
config: Optional[Dict[str, aaaAuthorizationMethods]] = {}
|
|
517
|
+
events: Optional[Dict[str, aaaAuthorizationEvents]] = {}
|
|
518
|
+
|
|
519
|
+
# Accounting Models
|
|
520
|
+
class aaaAccountingMethods(BaseModel):
|
|
521
|
+
method: Optional[List[str]] = None # Ex. ['TACACS_GROUP','LOCAL']
|
|
522
|
+
|
|
523
|
+
class aaaAccountingEvents(BaseModel):
|
|
524
|
+
event: list = [
|
|
525
|
+
{
|
|
526
|
+
'event-type': 'command',
|
|
527
|
+
'config': {
|
|
528
|
+
'event-type': 'command',
|
|
529
|
+
'method': ['tacacs_group']
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
'event-type': 'system',
|
|
534
|
+
'config': {
|
|
535
|
+
'event-type': 'system',
|
|
536
|
+
'method': ['tacacs_group']
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
]
|
|
540
|
+
|
|
541
|
+
class aaaAccounting(BaseModel):
|
|
542
|
+
config: aaaAccountingMethods = aaaAccountingMethods()
|
|
543
|
+
events: aaaAccountingEvents = aaaAccountingEvents()
|
|
544
|
+
|
|
545
|
+
class TripleA(BaseModel):
|
|
546
|
+
#config: dict = None
|
|
547
|
+
server_groups: Optional[Dict[str, aaaServerGroupAttributes]] = {}
|
|
548
|
+
tacacs: Optional[Dict[str, aaaTacacsAttributes]] = {}
|
|
549
|
+
radius: Optional[Dict[str, aaaRadiusAttributes]] = {}
|
|
550
|
+
authentication: aaaAuthentication = aaaAuthentication()
|
|
551
|
+
authorization: aaaAuthorization = aaaAuthorization()
|
|
552
|
+
accounting: aaaAccounting = aaaAccounting()
|
|
553
|
+
|
|
554
|
+
class System(BaseModel):
|
|
555
|
+
config: SystemConfig = SystemConfig()
|
|
556
|
+
aaa: Optional[TripleA] = TripleA()
|
|
557
|
+
logging: Optional[LoggingComponents] = LoggingComponents() # Trying to avoid using "Logging" or "logging" as names for anything due to conflicts with standard lib.
|
|
558
|
+
ntp: Optional[Ntp] = Ntp()
|
|
559
|
+
ssh: Optional[Ssh] = Ssh()
|
|
560
|
+
snmp: Optional[Snmp] = {}
|
|
561
|
+
|
|
562
|
+
# For different types of interfaces that are fine for response model:
|
|
563
|
+
InterfaceType = Union[
|
|
564
|
+
EthernetCsmacdInterface,
|
|
565
|
+
Ieee8023adLagInterface,
|
|
566
|
+
L3IpvlanInterface,
|
|
567
|
+
SoftwareLoopbackInterface,
|
|
568
|
+
SubInterface,
|
|
569
|
+
ManagementInterface,
|
|
570
|
+
]
|
|
571
|
+
|
|
572
|
+
class ComposedConfiguration(BaseModel):
|
|
573
|
+
system: Optional[System] = System()
|
|
574
|
+
acl: Optional[Acl] = Acl()
|
|
575
|
+
lldp: Optional[Lldp] = Lldp()
|
|
576
|
+
lacp: Optional[Lacp] = Lacp()
|
|
577
|
+
interfaces: Dict[str, InterfaceType] = {}
|
|
578
|
+
network_instances: Dict[str, NetworkInstance] = {"global": NetworkInstance(name="global")}
|
|
579
|
+
stp: Optional[SpanningTree] = {}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, field_validator, field_serializer, PrivateAttr
|
|
2
|
+
from sqlmodel import SQLModel, Field
|
|
3
|
+
from typing import Literal, Callable, Optional, Any
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EVType(Enum):
|
|
9
|
+
data = "data"
|
|
10
|
+
resource = "resource"
|
|
11
|
+
|
|
12
|
+
class ExternalValue(SQLModel, table=True):
|
|
13
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
14
|
+
attr_ptr: str = Field(default=None, primary_key=True)
|
|
15
|
+
|
|
16
|
+
# query: dict # same query as was used for fetching the data
|
|
17
|
+
query: str = '{"json_query": "in_stringformat"}'
|
|
18
|
+
value: Optional[str] = None
|
|
19
|
+
kind: str # object kind/type
|
|
20
|
+
ev_type: EVType = Field(default=EVType.data)
|
|
21
|
+
plugin: str
|
|
22
|
+
resolved: bool = Field(default=False) # True when value has been resolved
|
|
23
|
+
resolved_at: Optional[datetime] = Field(default=None) # Only set when resolved
|
|
24
|
+
|
|
25
|
+
# Privat attribut för callable (inte i JSON eller databas)
|
|
26
|
+
_callable: Optional[Callable] = PrivateAttr(default=None)
|
|
27
|
+
|
|
28
|
+
@field_validator('ev_type', mode='before')
|
|
29
|
+
@classmethod
|
|
30
|
+
def validate_ev_type(cls, v):
|
|
31
|
+
if isinstance(v, str):
|
|
32
|
+
return EVType(v)
|
|
33
|
+
return v
|
|
34
|
+
|
|
35
|
+
@field_serializer('ev_type')
|
|
36
|
+
def serialize_ev_type(self, value) -> str:
|
|
37
|
+
if isinstance(value, EVType):
|
|
38
|
+
return value.value
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
return value
|
|
41
|
+
return str(value)
|
|
42
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from acex_devkit.models.attribute_value import AttributeValue
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LoggingServerBase(BaseModel): ...
|
|
8
|
+
#name: str = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoggingSeverity(str, Enum):
|
|
12
|
+
EMERGENCY = "EMERGENCY"
|
|
13
|
+
ALERT = "ALERT"
|
|
14
|
+
CRITICAL = "CRITICAL"
|
|
15
|
+
ERROR = "ERROR"
|
|
16
|
+
WARNING = "WARNING"
|
|
17
|
+
NOTICE = "NOTICE"
|
|
18
|
+
INFORMATIONAL = "INFORMATIONAL"
|
|
19
|
+
DEBUG = "DEBUG"
|
|
20
|
+
|
|
21
|
+
class LoggingFacility(str, Enum):
|
|
22
|
+
# Some are specific for Juniper devices and are taken directly from their documentation.
|
|
23
|
+
KERN = "KERN"
|
|
24
|
+
USER = "USER"
|
|
25
|
+
DAEMON = "DAEMON"
|
|
26
|
+
AUTHORIZATION = "AUTHORIZATION"
|
|
27
|
+
FTP = "FTP"
|
|
28
|
+
NTP = "NTP"
|
|
29
|
+
DFC = "DFC"
|
|
30
|
+
EXTERNAL = "EXTERNAL"
|
|
31
|
+
FIREWALL = "FIREWALL"
|
|
32
|
+
PFE = "PFE"
|
|
33
|
+
CONFLICTLOG = "CONFLICTLOG"
|
|
34
|
+
CHANGELOG = "CHANGELOG"
|
|
35
|
+
INTERACTIVE_COMMANDS = "INTERACTIVE_COMMANDS"
|
|
36
|
+
|
|
37
|
+
class Reference(BaseModel): ...
|
|
38
|
+
|
|
39
|
+
class LoggingConfig(BaseModel):
|
|
40
|
+
rate_limit: Optional[AttributeValue[int]] = None
|
|
41
|
+
severity: Optional[AttributeValue[LoggingSeverity]] = None
|
|
42
|
+
buffer_size: Optional[AttributeValue[int]] = None
|
|
43
|
+
|
|
44
|
+
class Console(BaseModel):
|
|
45
|
+
name: str = None
|
|
46
|
+
line_number: int = None
|
|
47
|
+
logging_synchronous: bool = True
|
|
48
|
+
|
|
49
|
+
class RemoteServer(BaseModel):
|
|
50
|
+
name: str = None
|
|
51
|
+
host: str = None
|
|
52
|
+
port: Optional[int] = 514
|
|
53
|
+
transfer: Optional[str] = 'udp'
|
|
54
|
+
source_address: Optional[AttributeValue[str]] = None # Can be an IP address or an interface reference
|
|
55
|
+
|
|
56
|
+
class VtyLine(BaseModel):
|
|
57
|
+
name: str = None
|
|
58
|
+
line_number: int = None
|
|
59
|
+
logging_synchronous: bool = True
|
|
60
|
+
transport_input: Optional[str] = 'ssh' # default is SSH. Mostly used by Cisco.
|
|
61
|
+
|
|
62
|
+
class FileLogging(BaseModel):
|
|
63
|
+
name: str = None # object name
|
|
64
|
+
filename: str = None # name of the file
|
|
65
|
+
rotate: Optional[int] = None # How many versions to keep. Juniper specific.
|
|
66
|
+
max_size: Optional[int] = None # Max size in bytes. Used both for Cisco and Juniper.
|
|
67
|
+
min_size: Optional[int] = None # Min size in bytes. Only used for Cisco.
|
|
68
|
+
facility: LoggingFacility # Type of log
|
|
69
|
+
severity: LoggingSeverity # Severity level
|
|
70
|
+
|
|
71
|
+
class LoggingEvent(BaseModel):
|
|
72
|
+
enabled: bool
|
|
73
|
+
severity: LoggingSeverity
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class LoggingEvents(BaseModel):
|
|
77
|
+
events: Optional[Dict[str, LoggingEvent]] = {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from acex_devkit.models.composed_configuration import ComposedConfiguration
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LogicalNodeResponse(BaseModel):
|
|
7
|
+
hostname: Optional[str] = Field('R1', title='Hostname')
|
|
8
|
+
role: Optional[str] = Field('core', title='Role')
|
|
9
|
+
site: Optional[str] = Field('HQ', title='Site')
|
|
10
|
+
sequence: Optional[int] = Field(1, title='Sequence')
|
|
11
|
+
id: Optional[int] = Field(None, title='Id')
|
|
12
|
+
configuration: Optional[ComposedConfiguration] = ComposedConfiguration()
|
|
13
|
+
meta_data: Optional[Dict[str, Any]] = Field(None, title='Meta Data')
|
|
14
|
+
|
|
15
|
+
class NodeResponse(BaseModel):
|
|
16
|
+
asset_ref_id: int = Field(..., title='Asset Ref Id')
|
|
17
|
+
asset_ref_type: Optional[AssetRefType] = 'asset'
|
|
18
|
+
logical_node_id: int = Field(..., title='Logical Node Id')
|
|
19
|
+
asset: AssetResponse
|
|
20
|
+
logical_node: LogicalNodeResponse
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from acex_devkit.models.attribute_value import AttributeValue
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional, Dict
|
|
5
|
+
|
|
6
|
+
class SpanningTreeGlobalAttributes(BaseModel):
|
|
7
|
+
mode: Optional[str] = None # Needs to be defined by user. Default for Cisco is RAPID-PVST and for Juniper it's just RSTP
|
|
8
|
+
bpdu_filter: Optional[bool] = False # Disabled by default
|
|
9
|
+
bpdu_guard: Optional[bool] = False # Disabled by default
|
|
10
|
+
loop_guard: Optional[bool] = False # Disabled by default
|
|
11
|
+
portfast: Optional[bool] = False # Disabled by default. Global setting for access ports.
|
|
12
|
+
bridge_assurance: Optional[bool] = False # Disabled by default. Only supported by MST and PVRST+
|
|
13
|
+
#interfaces: Optional[Dict[str, Reference]] = {}
|
|
14
|
+
|
|
15
|
+
class SpanningTreeModeConfig(BaseModel):
|
|
16
|
+
hello_time: Optional[int] = None
|
|
17
|
+
max_age: Optional[int] = None
|
|
18
|
+
forward_delay: Optional[int] = None
|
|
19
|
+
bridge_priority: Optional[int] = None
|
|
20
|
+
hold_count: Optional[int] = None # Range 1..10
|
|
21
|
+
|
|
22
|
+
## RSTP
|
|
23
|
+
class RstpAttributes(SpanningTreeModeConfig): ...
|
|
24
|
+
|
|
25
|
+
class RSTPConfig(BaseModel):
|
|
26
|
+
config: RstpAttributes = RstpAttributes()
|
|
27
|
+
|
|
28
|
+
### MSTP
|
|
29
|
+
class MstpInstanceAttributes(SpanningTreeModeConfig):
|
|
30
|
+
instance_id: AttributeValue[int] # range: 1..4094
|
|
31
|
+
name: Optional[AttributeValue[str]] = None
|
|
32
|
+
vlan: Optional[AttributeValue[list[int]]] = None # List of VLANs mapped to the MST instance
|
|
33
|
+
|
|
34
|
+
class MstpAttributes(SpanningTreeModeConfig):
|
|
35
|
+
revision: Optional[AttributeValue[int]] = None
|
|
36
|
+
max_hop: Optional[AttributeValue[int]] = None # Range 1..255
|
|
37
|
+
|
|
38
|
+
class MSTPConfig(BaseModel):
|
|
39
|
+
config: MstpAttributes = MstpAttributes()
|
|
40
|
+
mst_instances: Optional[Dict[str, MstpInstanceAttributes]] = {}
|
|
41
|
+
|
|
42
|
+
### Rapid PVST
|
|
43
|
+
class RapidPVSTAttributes(SpanningTreeModeConfig):
|
|
44
|
+
"""
|
|
45
|
+
Docstring for RapidPVSTAttributes
|
|
46
|
+
vlan can be a string or list. Depending on how NED is built it will check wether it's a single VLAN or multiple VLANs and then format the data to the correct format
|
|
47
|
+
for the command of the specific vendor.
|
|
48
|
+
For example for Cisco:
|
|
49
|
+
* Single VLAN
|
|
50
|
+
spanning-tree vlan 10 priority 8192
|
|
51
|
+
* Multiple VLANs
|
|
52
|
+
spanning-tree vlan 10-30 priority 8192
|
|
53
|
+
"""
|
|
54
|
+
#vlan_id: Optional[AttributeValue[int]] = None # Single VLAN ID or list of VLANs using Rapid PVST+
|
|
55
|
+
vlan: Optional[AttributeValue[int | list[int]]] = None # Single VLAN ID or list of VLANs using Rapid PVST+
|
|
56
|
+
|
|
57
|
+
class RapidPVSTConfig(BaseModel):
|
|
58
|
+
vlan: Optional[Dict[str, RapidPVSTAttributes]] = {}
|
|
59
|
+
|
|
60
|
+
class SpanningTree(BaseModel):
|
|
61
|
+
config: SpanningTreeGlobalAttributes = SpanningTreeGlobalAttributes()
|
|
62
|
+
rstp: RSTPConfig = RSTPConfig()
|
|
63
|
+
mstp: MSTPConfig = MSTPConfig()
|
|
64
|
+
rapidpvst: RapidPVSTConfig = RapidPVSTConfig()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|