annet 2.5.4__py3-none-any.whl → 3.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of annet might be problematic. Click here for more details.
- annet/adapters/file/provider.py +49 -26
- annet/adapters/netbox/common/manufacturer.py +3 -21
- annet/adapters/netbox/common/models.py +4 -3
- annet/adapters/netbox/v24/models.py +4 -3
- annet/annet.py +8 -1
- annet/annlib/netdev/devdb/data/devdb.json +1 -0
- annet/annlib/netdev/views/hardware.py +15 -67
- annet/annlib/patching.py +7 -5
- annet/annlib/rbparser/acl.py +3 -2
- annet/annlib/rbparser/ordering.py +3 -2
- annet/annlib/rbparser/platform.py +4 -0
- annet/annlib/rulebook/common.py +1 -1
- annet/api/__init__.py +2 -1
- annet/generators/__init__.py +1 -1
- annet/hardware.py +8 -12
- annet/parallel.py +28 -9
- annet/rulebook/__init__.py +2 -1
- annet/rulebook/deploying.py +3 -2
- annet/rulebook/patching.py +2 -2
- annet/rulebook/texts/arista.rul +25 -3
- annet/rulebook/texts/iosxr.deploy +27 -0
- annet/rulebook/texts/iosxr.order +85 -0
- annet/rulebook/texts/iosxr.rul +110 -0
- annet/tabparser.py +1 -1
- annet/vendors/__init__.py +33 -0
- annet/vendors/base.py +25 -0
- annet/vendors/registry.py +84 -0
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/METADATA +1 -1
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/RECORD +37 -30
- annet_generators/example/__init__.py +6 -1
- annet_generators/example/hostname.py +113 -0
- annet_generators/rpl_example/generator.py +3 -0
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/WHEEL +0 -0
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/entry_points.txt +0 -0
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/licenses/AUTHORS +0 -0
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {annet-2.5.4.dist-info → annet-3.0.0.dist-info}/top_level.txt +0 -0
annet/adapters/file/provider.py
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
from annet.storage import Query
|
|
1
|
+
import dataclasses
|
|
3
2
|
from dataclasses import dataclass, fields
|
|
4
|
-
from typing import
|
|
5
|
-
|
|
6
|
-
from annet.connectors import AdapterWithName
|
|
7
|
-
from annet.storage import Device as DeviceCls
|
|
8
|
-
from annet.annlib.netdev.views.hardware import vendor_to_hw, HardwareView
|
|
3
|
+
from typing import Any, Iterable, List, Optional, Sequence
|
|
4
|
+
|
|
9
5
|
import yaml
|
|
10
6
|
|
|
7
|
+
from annet.annlib.netdev.views.dump import DumpableView
|
|
8
|
+
from annet.annlib.netdev.views.hardware import HardwareView
|
|
9
|
+
from annet.connectors import AdapterWithName
|
|
10
|
+
from annet.hardware import hardware_connector
|
|
11
|
+
from annet.storage import Device as DeviceProtocol
|
|
12
|
+
from annet.storage import Query, Storage, StorageProvider
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
@dataclass
|
|
13
16
|
class Interface(DumpableView):
|
|
@@ -23,29 +26,46 @@ class Interface(DumpableView):
|
|
|
23
26
|
@dataclass
|
|
24
27
|
class DeviceStorage:
|
|
25
28
|
fqdn: str
|
|
26
|
-
|
|
29
|
+
|
|
30
|
+
vendor: Optional[str] = None
|
|
31
|
+
hw_model: Optional[str] = None
|
|
32
|
+
sw_version: Optional[str] = None
|
|
33
|
+
|
|
27
34
|
hostname: Optional[str] = None
|
|
28
35
|
serial: Optional[str] = None
|
|
29
36
|
id: Optional[str] = None
|
|
30
37
|
interfaces: Optional[list[Interface]] = None
|
|
31
38
|
storage: Optional[Storage] = None
|
|
32
39
|
|
|
40
|
+
hw: HardwareView = dataclasses.field(init=False)
|
|
41
|
+
|
|
33
42
|
def __post_init__(self):
|
|
34
43
|
if not self.id:
|
|
35
44
|
self.id = self.fqdn
|
|
36
45
|
if not self.hostname:
|
|
37
46
|
self.hostname = self.fqdn.split(".")[0]
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
|
|
47
|
+
|
|
48
|
+
if self.hw_model:
|
|
49
|
+
hw = HardwareView(self.hw_model, self.sw_version)
|
|
50
|
+
if self.vendor and self.vendor != hw.vendor:
|
|
51
|
+
raise Exception(f"Vendor {self.vendor} is not vendor from hw model ({hw.vendor})")
|
|
52
|
+
else:
|
|
53
|
+
self.vendor = hw.vendor
|
|
54
|
+
else:
|
|
55
|
+
hw_provider = hardware_connector.get()
|
|
56
|
+
hw: HardwareView = hw_provider.vendor_to_hw(self.vendor)
|
|
57
|
+
if not hw:
|
|
58
|
+
raise Exception("unknown vendor")
|
|
59
|
+
self.hw_model = hw.model
|
|
41
60
|
self.hw = hw
|
|
61
|
+
|
|
42
62
|
if isinstance(self.interfaces, list):
|
|
43
63
|
interfaces = []
|
|
44
64
|
for iface in self.interfaces:
|
|
45
65
|
try:
|
|
46
66
|
interfaces.append(Interface(**iface))
|
|
47
67
|
except Exception as e:
|
|
48
|
-
raise Exception("unable to parse %s as Interface %s" % (iface, e))
|
|
68
|
+
raise Exception("unable to parse %s as Interface: %s" % (iface, e))
|
|
49
69
|
self.interfaces = interfaces
|
|
50
70
|
|
|
51
71
|
def set_storage(self, storage: Storage):
|
|
@@ -53,7 +73,7 @@ class DeviceStorage:
|
|
|
53
73
|
|
|
54
74
|
|
|
55
75
|
@dataclass
|
|
56
|
-
class Device(
|
|
76
|
+
class Device(DeviceProtocol, DumpableView):
|
|
57
77
|
dev: DeviceStorage
|
|
58
78
|
|
|
59
79
|
def __hash__(self):
|
|
@@ -79,7 +99,7 @@ class Device(DeviceCls, DumpableView):
|
|
|
79
99
|
|
|
80
100
|
@property
|
|
81
101
|
def storage(self) -> Storage:
|
|
82
|
-
return self
|
|
102
|
+
return self.dev.storage
|
|
83
103
|
|
|
84
104
|
@property
|
|
85
105
|
def hw(self) -> HardwareView:
|
|
@@ -91,7 +111,11 @@ class Device(DeviceCls, DumpableView):
|
|
|
91
111
|
|
|
92
112
|
@property
|
|
93
113
|
def neighbours_ids(self):
|
|
94
|
-
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def neighbours_fqdns(self) -> list[str]:
|
|
118
|
+
return []
|
|
95
119
|
|
|
96
120
|
def make_lag(self, lag: int, ports: Sequence[str], lag_min_links: Optional[int]) -> Interface:
|
|
97
121
|
raise NotImplementedError
|
|
@@ -105,8 +129,8 @@ class Device(DeviceCls, DumpableView):
|
|
|
105
129
|
def find_interface(self, name: str) -> Optional[Interface]:
|
|
106
130
|
raise NotImplementedError
|
|
107
131
|
|
|
108
|
-
def
|
|
109
|
-
|
|
132
|
+
def flush_perf(self):
|
|
133
|
+
pass
|
|
110
134
|
|
|
111
135
|
|
|
112
136
|
@dataclass
|
|
@@ -120,7 +144,7 @@ class Devices:
|
|
|
120
144
|
try:
|
|
121
145
|
devices.append(Device(dev=DeviceStorage(**dev)))
|
|
122
146
|
except Exception as e:
|
|
123
|
-
raise Exception("unable to parse %s as Device %s" % (dev, e))
|
|
147
|
+
raise Exception("unable to parse %s as Device: %s" % (dev, e))
|
|
124
148
|
self.devices = devices
|
|
125
149
|
|
|
126
150
|
|
|
@@ -193,12 +217,12 @@ class FS(Storage):
|
|
|
193
217
|
return [dev.fqdn for dev in result]
|
|
194
218
|
|
|
195
219
|
def make_devices(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
220
|
+
self,
|
|
221
|
+
query: Query | list,
|
|
222
|
+
preload_neighbors=False,
|
|
223
|
+
use_mesh=None,
|
|
224
|
+
preload_extra_fields=False,
|
|
225
|
+
**kwargs,
|
|
202
226
|
) -> list[Device]:
|
|
203
227
|
if isinstance(query, list):
|
|
204
228
|
query = Query.new(query)
|
|
@@ -231,8 +255,7 @@ def filter_query(devices: list[Device], query: Query) -> list[Device]:
|
|
|
231
255
|
|
|
232
256
|
def read_inventory(path: str, storage: Storage) -> Devices:
|
|
233
257
|
with open(path, "r") as f:
|
|
234
|
-
|
|
235
|
-
file_data = yaml.load(data, Loader=yaml.BaseLoader)
|
|
258
|
+
file_data = yaml.load(f, Loader=yaml.SafeLoader)
|
|
236
259
|
res = dataclass_from_dict(Devices, file_data)
|
|
237
260
|
for dev in res.devices:
|
|
238
261
|
dev.dev.set_storage(storage)
|
|
@@ -4,31 +4,13 @@ from annet.annlib.netdev.views.hardware import HardwareView
|
|
|
4
4
|
|
|
5
5
|
logger = getLogger(__name__)
|
|
6
6
|
|
|
7
|
-
_VENDORS = {
|
|
8
|
-
"cisco": "Cisco",
|
|
9
|
-
"catalyst": "Cisco Catalyst",
|
|
10
|
-
"nexus": "Cisco Nexus",
|
|
11
|
-
"huawei": "Huawei",
|
|
12
|
-
"optixtrans": "Huawei OptiXtrans",
|
|
13
|
-
"juniper": "Juniper",
|
|
14
|
-
"arista": "Arista",
|
|
15
|
-
"pc": "PC",
|
|
16
|
-
"nokia": "Nokia",
|
|
17
|
-
"aruba": "Aruba",
|
|
18
|
-
"routeros": "RouterOS",
|
|
19
|
-
"ribbon": "Ribbon",
|
|
20
|
-
"b4com": "B4com",
|
|
21
|
-
"h3c": "H3C",
|
|
22
|
-
}
|
|
23
|
-
|
|
24
7
|
|
|
25
8
|
def get_hw(manufacturer: str, model: str, platform_name: str):
|
|
26
|
-
#
|
|
9
|
+
# By some reason Netbox calls Mellanox SN as MSN, so we fix them here
|
|
27
10
|
if manufacturer == "Mellanox" and model.startswith("MSN"):
|
|
28
11
|
model = model.replace("MSN", "SN", 1)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return hw
|
|
12
|
+
|
|
13
|
+
return HardwareView(manufacturer + " " + model, platform_name)
|
|
32
14
|
|
|
33
15
|
|
|
34
16
|
def get_breed(manufacturer: str, model: str):
|
|
@@ -5,8 +5,9 @@ from ipaddress import ip_interface, IPv6Interface
|
|
|
5
5
|
from typing import List, Optional, Any, Dict, Sequence, TypeVar, Generic
|
|
6
6
|
|
|
7
7
|
from annet.annlib.netdev.views.dump import DumpableView
|
|
8
|
-
from annet.annlib.netdev.views.hardware import HardwareView, lag_name
|
|
8
|
+
from annet.annlib.netdev.views.hardware import HardwareView, lag_name
|
|
9
9
|
from annet.storage import Storage
|
|
10
|
+
from annet.vendors import registry_connector
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@dataclass
|
|
@@ -140,7 +141,7 @@ class Interface(Entity, Generic[_IpAddressT]):
|
|
|
140
141
|
mode: Optional[InterfaceMode]
|
|
141
142
|
untagged_vlan: Optional[InterfaceVlan]
|
|
142
143
|
tagged_vlans: Optional[List[InterfaceVlan]]
|
|
143
|
-
tags: List[EntityWithSlug]
|
|
144
|
+
tags: List[EntityWithSlug] = field(default_factory=list)
|
|
144
145
|
display: str = ""
|
|
145
146
|
ip_addresses: List[_IpAddressT] = field(default_factory=list)
|
|
146
147
|
vrf: Optional[Entity] = None
|
|
@@ -259,7 +260,7 @@ class NetboxDevice(Entity, Generic[_InterfaceT]):
|
|
|
259
260
|
return lag_interface
|
|
260
261
|
|
|
261
262
|
def _svi_name(self, svi: int) -> str:
|
|
262
|
-
return
|
|
263
|
+
return registry_connector.get().match(self.hw).svi_name(svi)
|
|
263
264
|
|
|
264
265
|
def add_svi(self, svi: int) -> _InterfaceT:
|
|
265
266
|
name = self._svi_name(svi)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
from ipaddress import ip_interface, IPv6Interface
|
|
4
|
-
from typing import List, Optional, Any, Dict, Sequence
|
|
4
|
+
from typing import List, Optional, Any, Dict, Sequence
|
|
5
5
|
|
|
6
6
|
from annet.annlib.netdev.views.dump import DumpableView
|
|
7
|
-
from annet.annlib.netdev.views.hardware import HardwareView, lag_name
|
|
7
|
+
from annet.annlib.netdev.views.hardware import HardwareView, lag_name
|
|
8
8
|
from annet.storage import Storage
|
|
9
|
+
from annet.vendors import registry_connector
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclass
|
|
@@ -259,7 +260,7 @@ class NetboxDevice(Entity):
|
|
|
259
260
|
return lag_interface
|
|
260
261
|
|
|
261
262
|
def _svi_name(self, svi: int) -> str:
|
|
262
|
-
return
|
|
263
|
+
return registry_connector.get().match(self.hw).svi_name(svi)
|
|
263
264
|
|
|
264
265
|
def add_svi(self, svi: int) -> Interface:
|
|
265
266
|
name = self._svi_name(svi)
|
annet/annet.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
import logging
|
|
2
3
|
import sys
|
|
3
4
|
|
|
4
5
|
import annet
|
|
@@ -18,8 +19,14 @@ def main():
|
|
|
18
19
|
parser.add_commands(parser.find_subcommands(cli.list_subcommands()))
|
|
19
20
|
try:
|
|
20
21
|
return parser.dispatch(pre_call=annet.init, add_help_command=True)
|
|
21
|
-
except (generators.GeneratorError, annet.ExecError):
|
|
22
|
+
except (generators.GeneratorError, annet.ExecError) as e:
|
|
23
|
+
logging.error(e)
|
|
22
24
|
return 1
|
|
25
|
+
except BaseException as e:
|
|
26
|
+
if formatted_output := getattr(e, "formatted_output", None):
|
|
27
|
+
logging.error(formatted_output)
|
|
28
|
+
return 1
|
|
29
|
+
raise e
|
|
23
30
|
|
|
24
31
|
|
|
25
32
|
if __name__ == "__main__":
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import functools
|
|
1
2
|
from typing import Optional
|
|
2
3
|
|
|
3
4
|
from annet.annlib.netdev.devdb import parse_hw_model
|
|
@@ -51,20 +52,31 @@ class HardwareLeaf(DumpableView):
|
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
class HardwareView(HardwareLeaf):
|
|
54
|
-
def __init__(self, hw_model, sw_version):
|
|
55
|
-
|
|
55
|
+
def __init__(self, hw_model, sw_version: Optional[str] = None):
|
|
56
|
+
true_sequences, false_sequences = parse_hw_model(hw_model or "")
|
|
56
57
|
super().__init__((), true_sequences, false_sequences)
|
|
57
58
|
self.model = hw_model or ""
|
|
58
59
|
self._soft = sw_version or ""
|
|
59
60
|
|
|
60
61
|
@property
|
|
61
62
|
def vendor(self) -> Optional[str]:
|
|
62
|
-
|
|
63
|
+
from annet.hardware import hardware_connector
|
|
64
|
+
return hardware_connector.get().hw_to_vendor(self)
|
|
63
65
|
|
|
64
66
|
@property
|
|
65
67
|
def soft(self) -> str:
|
|
66
68
|
return self._soft
|
|
67
69
|
|
|
70
|
+
@soft.setter
|
|
71
|
+
def soft(self, value: str) -> None:
|
|
72
|
+
self._soft = value
|
|
73
|
+
|
|
74
|
+
def match(self, expr: str) -> bool:
|
|
75
|
+
dev_path = expr.split(".")
|
|
76
|
+
if dev_path and dev_path[0] == "hw":
|
|
77
|
+
dev_path = dev_path[1:]
|
|
78
|
+
return bool(functools.reduce(getattr, dev_path, self))
|
|
79
|
+
|
|
68
80
|
def __hash__(self):
|
|
69
81
|
return hash(self.model)
|
|
70
82
|
|
|
@@ -72,59 +84,6 @@ class HardwareView(HardwareLeaf):
|
|
|
72
84
|
return self.model == other.model
|
|
73
85
|
|
|
74
86
|
|
|
75
|
-
def hw_to_vendor(hw: HardwareView) -> Optional[str]:
|
|
76
|
-
if hw.Nexus:
|
|
77
|
-
return "nexus"
|
|
78
|
-
elif hw.Cisco:
|
|
79
|
-
return "cisco"
|
|
80
|
-
elif hw.OptiXtrans:
|
|
81
|
-
return "optixtrans"
|
|
82
|
-
elif hw.Huawei:
|
|
83
|
-
return "huawei"
|
|
84
|
-
elif hw.Juniper:
|
|
85
|
-
return "juniper"
|
|
86
|
-
elif hw.Arista:
|
|
87
|
-
return "arista"
|
|
88
|
-
elif hw.PC:
|
|
89
|
-
return "pc"
|
|
90
|
-
elif hw.Nokia:
|
|
91
|
-
return "nokia"
|
|
92
|
-
elif hw.RouterOS:
|
|
93
|
-
return "routeros"
|
|
94
|
-
elif hw.Aruba:
|
|
95
|
-
return "aruba"
|
|
96
|
-
elif hw.Ribbon:
|
|
97
|
-
return "ribbon"
|
|
98
|
-
elif hw.H3C:
|
|
99
|
-
return "h3c"
|
|
100
|
-
elif hw.B4com:
|
|
101
|
-
return "b4com"
|
|
102
|
-
return None
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def vendor_to_hw(vendor):
|
|
106
|
-
hw = HardwareView(
|
|
107
|
-
{
|
|
108
|
-
"cisco": "Cisco",
|
|
109
|
-
"catalyst": "Cisco Catalyst",
|
|
110
|
-
"nexus": "Cisco Nexus",
|
|
111
|
-
"huawei": "Huawei",
|
|
112
|
-
"optixtrans": "Huawei OptiXtrans",
|
|
113
|
-
"juniper": "Juniper",
|
|
114
|
-
"arista": "Arista",
|
|
115
|
-
"pc": "PC",
|
|
116
|
-
"nokia": "Nokia",
|
|
117
|
-
"aruba": "Aruba",
|
|
118
|
-
"routeros": "RouterOS",
|
|
119
|
-
"ribbon": "Ribbon",
|
|
120
|
-
"h3c": "H3C",
|
|
121
|
-
"b4com": "B4com",
|
|
122
|
-
}.get(vendor.lower(), vendor),
|
|
123
|
-
None,
|
|
124
|
-
)
|
|
125
|
-
return hw
|
|
126
|
-
|
|
127
|
-
|
|
128
87
|
def lag_name(hw: HardwareView, nlagg: int) -> str:
|
|
129
88
|
if hw.Huawei:
|
|
130
89
|
return f"Eth-Trunk{nlagg}"
|
|
@@ -145,14 +104,3 @@ def lag_name(hw: HardwareView, nlagg: int) -> str:
|
|
|
145
104
|
if hw.Nokia:
|
|
146
105
|
return f"lagg-{nlagg}"
|
|
147
106
|
raise NotImplementedError(hw)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def svi_name(hw: HardwareView, num: int) -> str:
|
|
151
|
-
if hw.Juniper:
|
|
152
|
-
return f"irb.{num}"
|
|
153
|
-
elif hw.Huawei:
|
|
154
|
-
return f"Vlanif{num}"
|
|
155
|
-
elif hw.Arista or hw.Cisco:
|
|
156
|
-
return f"Vlan{num}"
|
|
157
|
-
else:
|
|
158
|
-
return f"vlan{num}"
|
annet/annlib/patching.py
CHANGED
|
@@ -19,6 +19,7 @@ from .rulebook.common import call_diff_logic
|
|
|
19
19
|
from .rulebook.common import default as common_default
|
|
20
20
|
from .tabparser import CommonFormatter
|
|
21
21
|
from .types import Diff, Op
|
|
22
|
+
from ..vendors import registry_connector
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
# =====
|
|
@@ -224,14 +225,15 @@ class Orderer:
|
|
|
224
225
|
return (f_order or 0), cmd_direct, odict(children), f_rule
|
|
225
226
|
|
|
226
227
|
def order_config(self, config):
|
|
227
|
-
if self.vendor not in
|
|
228
|
+
if self.vendor not in registry_connector.get():
|
|
228
229
|
return config
|
|
229
|
-
|
|
230
|
-
ordered = []
|
|
231
|
-
reverse_prefix = platform.VENDOR_REVERSES[self.vendor]
|
|
232
230
|
if not config:
|
|
233
231
|
return odict()
|
|
234
|
-
|
|
232
|
+
|
|
233
|
+
ordered = []
|
|
234
|
+
reverse_prefix = registry_connector.get()[self.vendor].reverse
|
|
235
|
+
|
|
236
|
+
for row, children in config.items():
|
|
235
237
|
cmd_direct = not row.startswith(reverse_prefix)
|
|
236
238
|
(order, direct, rb, _) = self.get_order(row, cmd_direct)
|
|
237
239
|
child_orderer = Orderer(rb, self.vendor)
|
annet/annlib/rbparser/acl.py
CHANGED
|
@@ -5,7 +5,8 @@ from typing import Any, Callable, List, Optional
|
|
|
5
5
|
from valkit import add_validator_magic
|
|
6
6
|
from valkit.common import valid_bool, valid_number, valid_string_list
|
|
7
7
|
|
|
8
|
-
from . import
|
|
8
|
+
from annet.vendors import registry_connector
|
|
9
|
+
from . import syntax
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
# =====
|
|
@@ -13,7 +14,7 @@ from . import platform, syntax
|
|
|
13
14
|
def compile_acl_text(text, vendor, allow_ignore=False):
|
|
14
15
|
return _compile_acl(
|
|
15
16
|
trees=[syntax.parse_text(text, _PARAMS_SCHEME)],
|
|
16
|
-
reverse_prefix=
|
|
17
|
+
reverse_prefix=registry_connector.get()[vendor].reverse,
|
|
17
18
|
allow_ignore=allow_ignore,
|
|
18
19
|
vendor=vendor,
|
|
19
20
|
)
|
|
@@ -4,7 +4,8 @@ from collections import OrderedDict as odict
|
|
|
4
4
|
|
|
5
5
|
from valkit.common import valid_bool, valid_string_list
|
|
6
6
|
|
|
7
|
-
from . import
|
|
7
|
+
from annet.vendors import registry_connector
|
|
8
|
+
from . import syntax
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
# =====
|
|
@@ -25,7 +26,7 @@ def compile_ordering_text(text, vendor):
|
|
|
25
26
|
"default": None,
|
|
26
27
|
}
|
|
27
28
|
}),
|
|
28
|
-
reverse_prefix=
|
|
29
|
+
reverse_prefix=registry_connector.get()[vendor].reverse,
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
|
|
@@ -3,6 +3,7 @@ VENDOR_REVERSES = {
|
|
|
3
3
|
"h3c": "undo",
|
|
4
4
|
"optixtrans": "undo",
|
|
5
5
|
"cisco": "no",
|
|
6
|
+
"iosxr": "no",
|
|
6
7
|
"nexus": "no",
|
|
7
8
|
"juniper": "delete",
|
|
8
9
|
"arista": "no",
|
|
@@ -18,6 +19,7 @@ VENDOR_DIFF = {
|
|
|
18
19
|
"huawei": "common.default_diff",
|
|
19
20
|
"optixtrans": "common.default_diff",
|
|
20
21
|
"cisco": "common.default_diff",
|
|
22
|
+
"iosxr": "common.default_diff",
|
|
21
23
|
"nexus": "common.default_diff",
|
|
22
24
|
"juniper": "juniper.default_diff",
|
|
23
25
|
"arista": "common.default_diff",
|
|
@@ -33,6 +35,7 @@ VENDOR_DIFF_ORDERED = {
|
|
|
33
35
|
"huawei": "common.ordered_diff",
|
|
34
36
|
"optixtrans": "common.ordered_diff",
|
|
35
37
|
"cisco": "common.ordered_diff",
|
|
38
|
+
"iosxr": "common.ordered_diff",
|
|
36
39
|
"nexus": "common.ordered_diff",
|
|
37
40
|
"juniper": "juniper.ordered_diff",
|
|
38
41
|
"arista": "common.ordered_diff",
|
|
@@ -49,6 +52,7 @@ VENDOR_EXIT = {
|
|
|
49
52
|
"h3c": "quit",
|
|
50
53
|
"optixtrans": "quit",
|
|
51
54
|
"cisco": "exit",
|
|
55
|
+
"iosxr": "exit",
|
|
52
56
|
"nexus": "exit",
|
|
53
57
|
"arista": "exit",
|
|
54
58
|
"juniper": "",
|
annet/annlib/rulebook/common.py
CHANGED
|
@@ -297,7 +297,7 @@ def apply(hw, do_commit, do_finalize, **_):
|
|
|
297
297
|
after.add_cmd(Command("abort")) # просто exit оставит висеть configure session
|
|
298
298
|
if do_finalize:
|
|
299
299
|
after.add_cmd(Command("write memory"))
|
|
300
|
-
elif hw.ASR or hw.XRV:
|
|
300
|
+
elif hw.ASR or hw.XRV or hw.XR:
|
|
301
301
|
# коммит сам сохраняет изменения в стартап do_finalize не применим
|
|
302
302
|
before.add_cmd(Command("configure exclusive"))
|
|
303
303
|
if do_commit:
|
annet/api/__init__.py
CHANGED
|
@@ -48,6 +48,7 @@ from annet.parallel import Parallel, TaskResult
|
|
|
48
48
|
from annet.reference import RefTracker
|
|
49
49
|
from annet.storage import Device, get_storage
|
|
50
50
|
from annet.types import Diff, ExitCode, OldNewResult, Op, PCDiff, PCDiffFile
|
|
51
|
+
from annet.vendors import registry_connector
|
|
51
52
|
|
|
52
53
|
DEFAULT_INDENT = " "
|
|
53
54
|
|
|
@@ -786,7 +787,7 @@ def guess_hw(config_text: str):
|
|
|
786
787
|
текста конфига и annet/rulebook/texts/*.rul"""
|
|
787
788
|
scores = {}
|
|
788
789
|
hw_provider = hardware_connector.get()
|
|
789
|
-
for vendor in
|
|
790
|
+
for vendor in registry_connector.get():
|
|
790
791
|
hw = hw_provider.vendor_to_hw(vendor)
|
|
791
792
|
fmtr = tabparser.make_formatter(hw)
|
|
792
793
|
rb = rulebook.get_rulebook(hw)
|
annet/generators/__init__.py
CHANGED
|
@@ -197,7 +197,7 @@ def _run_partial_generator(gen: "PartialGenerator", run_args: GeneratorPartialRu
|
|
|
197
197
|
raise
|
|
198
198
|
except Exception as err:
|
|
199
199
|
filename, lineno = gen.get_running_line()
|
|
200
|
-
logger.
|
|
200
|
+
logger.error("Generator error in file '%s:%i'", filename, lineno)
|
|
201
201
|
raise GeneratorError(f"{gen} on {device}") from err
|
|
202
202
|
|
|
203
203
|
fmtr = tabparser.make_formatter(device.hw)
|
annet/hardware.py
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from annet.annlib.netdev.views.hardware import HardwareView
|
|
5
|
-
|
|
4
|
+
from annet.annlib.netdev.views.hardware import HardwareView
|
|
5
|
+
from annet.vendors import registry_connector
|
|
6
6
|
from annet.connectors import Connector
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
try:
|
|
10
|
-
from annet.annlib.netdev.views.hardware import vendor_to_hw
|
|
11
|
-
except ImportError:
|
|
12
|
-
from netdev.views.hardware import vendor_to_hw
|
|
13
|
-
|
|
14
|
-
|
|
15
9
|
class _HardwareConnector(Connector["HarwareProvider"]):
|
|
16
10
|
name = "Hardware"
|
|
17
11
|
ep_name = "hardware"
|
|
@@ -31,7 +25,7 @@ class HarwareProvider(abc.ABC):
|
|
|
31
25
|
pass
|
|
32
26
|
|
|
33
27
|
@abc.abstractmethod
|
|
34
|
-
def hw_to_vendor(self, hw: Any) -> str:
|
|
28
|
+
def hw_to_vendor(self, hw: Any) -> str | None:
|
|
35
29
|
pass
|
|
36
30
|
|
|
37
31
|
|
|
@@ -40,7 +34,9 @@ class AnnetHardwareProvider(HarwareProvider):
|
|
|
40
34
|
return HardwareView(hw_model, sw_version)
|
|
41
35
|
|
|
42
36
|
def vendor_to_hw(self, vendor: str) -> HardwareView:
|
|
43
|
-
return
|
|
37
|
+
return registry_connector.get().get(vendor.lower()).hardware
|
|
44
38
|
|
|
45
|
-
def hw_to_vendor(self, hw: HardwareView) -> str:
|
|
46
|
-
|
|
39
|
+
def hw_to_vendor(self, hw: HardwareView) -> str | None:
|
|
40
|
+
if vendor := registry_connector.get().match(hw, None):
|
|
41
|
+
return vendor.NAME
|
|
42
|
+
return None
|
annet/parallel.py
CHANGED
|
@@ -15,13 +15,14 @@ import tempfile
|
|
|
15
15
|
import time
|
|
16
16
|
import traceback
|
|
17
17
|
import warnings
|
|
18
|
-
from typing import Any, List, Optional, Type
|
|
18
|
+
from typing import Any, List, Optional, Type, Callable
|
|
19
19
|
from uuid import uuid4
|
|
20
20
|
|
|
21
21
|
from contextlog import get_logger
|
|
22
22
|
|
|
23
23
|
import annet
|
|
24
24
|
from annet import tracing
|
|
25
|
+
from annet.connectors import Connector
|
|
25
26
|
from annet.lib import catch_ctrl_c, find_exc_in_stack
|
|
26
27
|
from annet.output import capture_output
|
|
27
28
|
from annet.tracing import tracing_connector
|
|
@@ -38,6 +39,20 @@ class PoolWorkerTask:
|
|
|
38
39
|
payload: Optional[Any] = None
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
class _PickleSafeTracebackFormatterConnector(Connector[Callable[[BaseException], str]]):
|
|
43
|
+
name = "PickleSafeTraceBackFormatter"
|
|
44
|
+
ep_name = "pickle_safe_traceback_formatter"
|
|
45
|
+
|
|
46
|
+
def _get_default(self) -> Callable[[], Callable[[BaseException], str]]:
|
|
47
|
+
def wrapper():
|
|
48
|
+
return lambda x: "".join(traceback.format_exception(x))
|
|
49
|
+
|
|
50
|
+
return wrapper
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
pickle_safe_traceback_formatter_connector = _PickleSafeTracebackFormatterConnector()
|
|
54
|
+
|
|
55
|
+
|
|
41
56
|
class PickleSafeException(Exception):
|
|
42
57
|
"""An exception that can be safely pickled and passed between processes."""
|
|
43
58
|
|
|
@@ -59,9 +74,14 @@ class PickleSafeException(Exception):
|
|
|
59
74
|
pretty_lines.append(self.formatted_output)
|
|
60
75
|
return "\n".join(pretty_lines)
|
|
61
76
|
|
|
62
|
-
@
|
|
63
|
-
def from_exc(orig_exc: Exception, device_id: str
|
|
64
|
-
return PickleSafeException(
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_exc(cls, orig_exc: Exception, device_id: str) -> "PickleSafeException":
|
|
79
|
+
return PickleSafeException(
|
|
80
|
+
orig_exc.__class__,
|
|
81
|
+
str(orig_exc),
|
|
82
|
+
device_id,
|
|
83
|
+
pickle_safe_traceback_formatter_connector.get()(orig_exc)
|
|
84
|
+
)
|
|
65
85
|
|
|
66
86
|
|
|
67
87
|
@catch_ctrl_c
|
|
@@ -164,7 +184,7 @@ def _pool_worker(pool, index, task_queue, done_queue):
|
|
|
164
184
|
except KeyboardInterrupt: # pylint: disable=try-except-raise
|
|
165
185
|
raise
|
|
166
186
|
except Exception as exc:
|
|
167
|
-
safe_exc = PickleSafeException.from_exc(exc, task.payload
|
|
187
|
+
safe_exc = PickleSafeException.from_exc(exc, task.payload)
|
|
168
188
|
ret_exc = safe_exc
|
|
169
189
|
task_result.exc = safe_exc
|
|
170
190
|
task_result.result = None
|
|
@@ -301,11 +321,10 @@ class Parallel:
|
|
|
301
321
|
**self.kwargs
|
|
302
322
|
)
|
|
303
323
|
except Exception as exc:
|
|
324
|
+
safe_exc = PickleSafeException.from_exc(exc, device_id)
|
|
304
325
|
if not tolerate_fails:
|
|
305
|
-
raise
|
|
306
|
-
exc
|
|
307
|
-
exc.formatted_output = traceback.format_exc()
|
|
308
|
-
task_result.exc = exc
|
|
326
|
+
raise safe_exc
|
|
327
|
+
task_result.exc = safe_exc
|
|
309
328
|
if self.capture_output:
|
|
310
329
|
task_result.extra["cap_stdout"] = cap_stdout.read()
|
|
311
330
|
task_result.extra["cap_stderr"] = cap_stderr.read()
|
annet/rulebook/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@ from annet.annlib.rbparser.platform import VENDOR_REVERSES, VENDOR_ALIASES
|
|
|
10
10
|
from annet.connectors import CachedConnector
|
|
11
11
|
from annet.rulebook.deploying import compile_deploying_text
|
|
12
12
|
from annet.rulebook.patching import compile_patching_text
|
|
13
|
+
from annet.vendors import registry_connector
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class RulebookProvider(ABC):
|
|
@@ -63,7 +64,7 @@ class DefaultRulebookProvider(RulebookProvider):
|
|
|
63
64
|
if hw in self._rulebook_cache:
|
|
64
65
|
return self._rulebook_cache[hw]
|
|
65
66
|
|
|
66
|
-
assert hw.vendor in
|
|
67
|
+
assert hw.vendor in registry_connector.get(), "Unknown vendor: %s" % (hw.vendor)
|
|
67
68
|
rul_vendor_name = VENDOR_ALIASES.get(hw.vendor, hw.vendor)
|
|
68
69
|
patching = compile_patching_text(self._render_rul(rul_vendor_name + ".rul", hw), rul_vendor_name)
|
|
69
70
|
|