annet 0.16.8__tar.gz → 0.16.10__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.
Potentially problematic release.
This version of annet might be problematic. Click here for more details.
- {annet-0.16.8/annet.egg-info → annet-0.16.10}/PKG-INFO +3 -1
- annet-0.16.10/README.md +62 -0
- annet-0.16.10/annet/adapters/fetchers/stub/fetcher.py +19 -0
- annet-0.16.10/annet/adapters/file/provider.py +226 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/manufacturer.py +2 -0
- annet-0.16.10/annet/adapters/netbox/common/models.py +276 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/v37/storage.py +31 -3
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/views/hardware.py +31 -0
- annet-0.16.10/annet/bgp_models.py +266 -0
- annet-0.16.10/annet/configs/context.yml +15 -0
- {annet-0.16.8 → annet-0.16.10}/annet/configs/logging.yaml +1 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/__init__.py +18 -4
- {annet-0.16.8 → annet-0.16.10}/annet/generators/jsonfragment.py +13 -12
- annet-0.16.10/annet/mesh/__init__.py +16 -0
- annet-0.16.10/annet/mesh/basemodel.py +180 -0
- annet-0.16.10/annet/mesh/device_models.py +62 -0
- annet-0.16.10/annet/mesh/executor.py +248 -0
- annet-0.16.10/annet/mesh/match_args.py +165 -0
- annet-0.16.10/annet/mesh/models_converter.py +84 -0
- annet-0.16.10/annet/mesh/peer_models.py +98 -0
- annet-0.16.10/annet/mesh/registry.py +212 -0
- annet-0.16.10/annet/rulebook/nexus/__init__.py +0 -0
- annet-0.16.10/annet/rulebook/routeros/__init__.py +0 -0
- annet-0.16.10/annet/rulebook/routeros/file.py +5 -0
- annet-0.16.10/annet/rulebook/texts/pc.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/storage.py +41 -2
- {annet-0.16.8 → annet-0.16.10/annet.egg-info}/PKG-INFO +3 -1
- {annet-0.16.8 → annet-0.16.10}/annet.egg-info/SOURCES.txt +20 -1
- {annet-0.16.8 → annet-0.16.10}/annet.egg-info/requires.txt +3 -0
- annet-0.16.10/annet_generators/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet_generators/example/__init__.py +1 -3
- annet-0.16.10/annet_generators/mesh_example/__init__.py +9 -0
- annet-0.16.10/annet_generators/mesh_example/bgp.py +43 -0
- annet-0.16.10/annet_generators/mesh_example/mesh_logic.py +28 -0
- {annet-0.16.8 → annet-0.16.10}/setup.py +3 -0
- annet-0.16.8/README.md +0 -176
- annet-0.16.8/annet/adapters/netbox/common/models.py +0 -171
- annet-0.16.8/annet/configs/context.yml +0 -25
- {annet-0.16.8 → annet-0.16.10}/AUTHORS +0 -0
- {annet-0.16.8 → annet-0.16.10}/LICENSE +0 -0
- {annet-0.16.8 → annet-0.16.10}/MANIFEST.in +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/__init__.py +0 -0
- {annet-0.16.8/annet/adapters/netbox → annet-0.16.10/annet/adapters/fetchers}/__init__.py +0 -0
- {annet-0.16.8/annet/adapters/netbox/common → annet-0.16.10/annet/adapters/fetchers/stub}/__init__.py +0 -0
- {annet-0.16.8/annet/adapters/netbox/v24 → annet-0.16.10/annet/adapters/file}/__init__.py +0 -0
- {annet-0.16.8/annet/adapters/netbox/v37 → annet-0.16.10/annet/adapters/netbox}/__init__.py +0 -0
- {annet-0.16.8/annet/annlib/netdev → annet-0.16.10/annet/adapters/netbox/common}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/query.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/provider.py +0 -0
- {annet-0.16.8/annet/annlib/netdev/views → annet-0.16.10/annet/adapters/netbox/v24}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.16.8/annet/annlib/rbparser → annet-0.16.10/annet/adapters/netbox/v37}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annet.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/command.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/diff.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/errors.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/filter_acl.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/jsontools.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/lib.py +0 -0
- {annet-0.16.8/annet/annlib/rulebook → annet-0.16.10/annet/annlib/netdev}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/db.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
- {annet-0.16.8/annet/generators/common → annet-0.16.10/annet/annlib/netdev/views}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/output.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/patching.py +0 -0
- {annet-0.16.8/annet/rulebook/arista → annet-0.16.10/annet/annlib/rbparser}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.16.8/annet/rulebook/b4com → annet-0.16.10/annet/annlib/rulebook}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/tabparser.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/annlib/types.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/api/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/argparse.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/cli.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/cli_args.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/connectors.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/deploy.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/diff.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/executor.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/filtering.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/gen.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/base.py +0 -0
- {annet-0.16.8/annet/rulebook/cisco → annet-0.16.10/annet/generators/common}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/common/initial.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/entire.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/exceptions.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/partial.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/perf.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/ref.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/generators/result.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/hardware.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/implicit.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/lib.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/output.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/parallel.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/patching.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/reference.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/__init__.py +0 -0
- {annet-0.16.8/annet/rulebook/huawei → annet-0.16.10/annet/rulebook/arista}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.16.8/annet/rulebook/nexus → annet-0.16.10/annet/rulebook/b4com}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/b4com/file.py +0 -0
- {annet-0.16.8/annet_generators → annet-0.16.10/annet/rulebook/cisco}/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/common.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/deploying.py +0 -0
- /annet-0.16.8/annet/rulebook/texts/pc.deploy → /annet-0.16.10/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/patching.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/b4com.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/tabparser.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/text_term_format.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/tracing.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet/types.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet.egg-info/top_level.txt +0 -0
- {annet-0.16.8 → annet-0.16.10}/annet_generators/example/lldp.py +0 -0
- {annet-0.16.8 → annet-0.16.10}/requirements.txt +0 -0
- {annet-0.16.8 → annet-0.16.10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: annet
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.10
|
|
4
4
|
Summary: annet
|
|
5
5
|
Home-page: https://github.com/annetutil/annet
|
|
6
6
|
License: MIT
|
|
@@ -23,3 +23,5 @@ Requires-Dist: aiohttp>=3.8.4
|
|
|
23
23
|
Requires-Dist: yarl>=1.8.2
|
|
24
24
|
Requires-Dist: adaptix==3.0.0b7
|
|
25
25
|
Requires-Dist: dataclass-rest==0.4
|
|
26
|
+
Provides-Extra: netbox
|
|
27
|
+
Requires-Dist: annetbox[sync]>=0.1.8; extra == "netbox"
|
annet-0.16.10/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Annet - configuration generation and deploying utility for network equipment
|
|
2
|
+
|
|
3
|
+
Annet is a configuration generator that can translate differences between old and new configurations into sequnce of commands. This feature is vital for CLI-based devices, such as Huawei, Cisco IOS, Cisco NX-OS, Juniper. Devices configured via separate config files, Linux, FreeBSD and Cumulus are also supported.
|
|
4
|
+
|
|
5
|
+
It works this way. Annet `gen`erates configuration for a device by running Python code, which usually goes to the Network Source of Truth, like NetBox. Annet then gets the `diff`erence by getting the configuration from the device and comparing it. Finally, Annet translates the difference into a sequence of commands, called a `patch`. After `deploy`ing these commands, the diff will be empty.
|
|
6
|
+
|
|
7
|
+
Annet has a number of modes (subcommands):
|
|
8
|
+
|
|
9
|
+
- ```annet gen``` - generates the entire config for the specified devices or specified parts of it
|
|
10
|
+
- ```annet diff``` - first does gen and then builds diff with current config version
|
|
11
|
+
- ```annet patch``` - first does diff and then generates a list of commands to apply diff on the device
|
|
12
|
+
- ```annet deploy``` - first does patch and then deploys it to the device
|
|
13
|
+
|
|
14
|
+
Usage help can be obtained by calling ```annet -h``` or for a specific command, such as ```annet gen -h```.
|
|
15
|
+
|
|
16
|
+
<img src="https://github.com/annetutil/annet/blob/main/docs/_static/annet_demo.gif?raw=true" width="800" />
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
The path to the configuration file is searched in following order:
|
|
21
|
+
- `ANN_CONTEXT_CONFIG_PATH` env.
|
|
22
|
+
- `~/.annet/context.yml`.
|
|
23
|
+
- `annet/configs/context.yml`.
|
|
24
|
+
|
|
25
|
+
Config example:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
generators:
|
|
29
|
+
default:
|
|
30
|
+
- my_annet_generators.example
|
|
31
|
+
|
|
32
|
+
storage:
|
|
33
|
+
default:
|
|
34
|
+
adapter: annet.adapters.file.provider
|
|
35
|
+
params:
|
|
36
|
+
path: /path/to/file
|
|
37
|
+
|
|
38
|
+
context:
|
|
39
|
+
default:
|
|
40
|
+
generators: default
|
|
41
|
+
storage: default
|
|
42
|
+
|
|
43
|
+
selected_context: default
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Environment variable `ANN_SELECTED_CONTEXT` can be used to override `selected_context` parameter.
|
|
47
|
+
|
|
48
|
+
## Building doc
|
|
49
|
+
|
|
50
|
+
1. Install dependencies:
|
|
51
|
+
|
|
52
|
+
```shell
|
|
53
|
+
pip install -r requirements-doc.txt
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. Build
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
sphinx-build -M html docs docs-build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. Open rendered html in browser [docs-build/html/index.html](docs-build/html/index.html)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from annet.deploy import Fetcher
|
|
2
|
+
from annet.connectors import AdapterWithConfig
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
from annet.storage import Device
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StubFetcher(Fetcher, AdapterWithConfig):
|
|
8
|
+
@classmethod
|
|
9
|
+
def with_config(cls, **kwargs: Dict[str, Any]) -> Fetcher:
|
|
10
|
+
return cls(**kwargs)
|
|
11
|
+
|
|
12
|
+
def fetch_packages(self, devices: List[Device],
|
|
13
|
+
processes: int = 1, max_slots: int = 0):
|
|
14
|
+
raise NotImplementedError()
|
|
15
|
+
|
|
16
|
+
def fetch(self, devices: List[Device],
|
|
17
|
+
files_to_download: Dict[str, List[str]] = None,
|
|
18
|
+
processes: int = 1, max_slots: int = 0):
|
|
19
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from annet.annlib.netdev.views.dump import DumpableView
|
|
2
|
+
from annet.storage import Query
|
|
3
|
+
from dataclasses import dataclass, fields
|
|
4
|
+
from typing import List, Iterable, Optional, Any
|
|
5
|
+
from annet.storage import StorageProvider, Storage
|
|
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
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Interface(DumpableView):
|
|
14
|
+
name: str
|
|
15
|
+
description: str
|
|
16
|
+
enabled: bool = True
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def _dump__list_key(self):
|
|
20
|
+
return self.name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class DeviceStorage:
|
|
25
|
+
fqdn: str
|
|
26
|
+
vendor: str
|
|
27
|
+
hostname: Optional[str] = None
|
|
28
|
+
serial: Optional[str] = None
|
|
29
|
+
id: Optional[str] = None
|
|
30
|
+
interfaces: Optional[list[Interface]] = None
|
|
31
|
+
storage: Optional[Storage] = None
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
if not self.id:
|
|
35
|
+
self.id = self.fqdn
|
|
36
|
+
if not self.hostname:
|
|
37
|
+
self.hostname = self.fqdn.split(".")[0]
|
|
38
|
+
hw = vendor_to_hw(self.vendor)
|
|
39
|
+
if not hw:
|
|
40
|
+
raise Exception("unknown vendor")
|
|
41
|
+
self.hw = hw
|
|
42
|
+
if isinstance(self.interfaces, list):
|
|
43
|
+
interfaces = []
|
|
44
|
+
for iface in self.interfaces:
|
|
45
|
+
try:
|
|
46
|
+
interfaces.append(Interface(**iface))
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise Exception("unable to parse %s as Interface %s" % (iface, e))
|
|
49
|
+
self.interfaces = interfaces
|
|
50
|
+
|
|
51
|
+
def set_storage(self, storage: Storage):
|
|
52
|
+
self.storage = storage
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Device(DeviceCls, DumpableView):
|
|
57
|
+
dev: DeviceStorage
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def hostname(self) -> str:
|
|
61
|
+
return self.dev.hostname
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def fqdn(self) -> str:
|
|
65
|
+
return self.dev.fqdn
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def id(self):
|
|
69
|
+
return self.dev.id
|
|
70
|
+
|
|
71
|
+
def __hash__(self):
|
|
72
|
+
return hash((self.id, type(self)))
|
|
73
|
+
|
|
74
|
+
def __eq__(self, other):
|
|
75
|
+
return type(self) is type(other) and self.fqdn == other.fqdn and self.vendor == other.vendor
|
|
76
|
+
|
|
77
|
+
def is_pc(self) -> bool:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def storage(self) -> Storage:
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def hw(self) -> HardwareView:
|
|
86
|
+
return self.dev.hw
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def breed(self) -> str:
|
|
90
|
+
return self.dev.hw.vendor
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def neighbours_ids(self):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class Devices:
|
|
99
|
+
devices: list[Device]
|
|
100
|
+
|
|
101
|
+
def __post_init__(self):
|
|
102
|
+
if isinstance(self.devices, list):
|
|
103
|
+
devices = []
|
|
104
|
+
for dev in self.devices:
|
|
105
|
+
try:
|
|
106
|
+
devices.append(Device(dev=DeviceStorage(**dev)))
|
|
107
|
+
except Exception as e:
|
|
108
|
+
raise Exception("unable to parse %s as Device %s" % (dev, e))
|
|
109
|
+
self.devices = devices
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Provider(StorageProvider, AdapterWithName):
|
|
113
|
+
def storage(self):
|
|
114
|
+
return storage_factory
|
|
115
|
+
|
|
116
|
+
def opts(self):
|
|
117
|
+
return StorageOpts
|
|
118
|
+
|
|
119
|
+
def query(self):
|
|
120
|
+
return Query
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def name(cls) -> str:
|
|
124
|
+
return "file"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class Query(Query):
|
|
129
|
+
query: List[str]
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def new(cls, query: str | Iterable[str], hosts_range: Optional[slice] = None) -> "Query":
|
|
133
|
+
if hosts_range is not None:
|
|
134
|
+
raise ValueError("host_range is not supported")
|
|
135
|
+
return cls(query=list(query))
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def globs(self):
|
|
139
|
+
return self.query
|
|
140
|
+
|
|
141
|
+
def is_empty(self) -> bool:
|
|
142
|
+
return len(self.query) == 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class StorageOpts:
|
|
146
|
+
def __init__(self, path: str):
|
|
147
|
+
self.path = path
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def parse_params(cls, conf_params: Optional[dict[str, str]], cli_opts: Any):
|
|
151
|
+
path = conf_params.get("path")
|
|
152
|
+
if not path:
|
|
153
|
+
raise Exception("empty path")
|
|
154
|
+
return cls(path=path)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def storage_factory(opts: StorageOpts) -> Storage:
|
|
158
|
+
return FS(opts)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class FS(Storage):
|
|
162
|
+
def __init__(self, opts: StorageOpts):
|
|
163
|
+
self.opts = opts
|
|
164
|
+
self.inventory: Devices = read_inventory(opts.path, self)
|
|
165
|
+
|
|
166
|
+
def __enter__(self):
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def __exit__(self, _, __, ___):
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
def resolve_object_ids_by_query(self, query: Query) -> list[str]:
|
|
173
|
+
result = filter_query(self.inventory.devices, query)
|
|
174
|
+
return [dev.fqdn for dev in result]
|
|
175
|
+
|
|
176
|
+
def resolve_fdnds_by_query(self, query: Query) -> list[str]:
|
|
177
|
+
result = filter_query(self.inventory.devices, query)
|
|
178
|
+
return [dev.fqdn for dev in result]
|
|
179
|
+
|
|
180
|
+
def make_devices(
|
|
181
|
+
self,
|
|
182
|
+
query: Query | list,
|
|
183
|
+
preload_neighbors=False,
|
|
184
|
+
use_mesh=None,
|
|
185
|
+
preload_extra_fields=False,
|
|
186
|
+
**kwargs,
|
|
187
|
+
) -> list[Device]:
|
|
188
|
+
if isinstance(query, list):
|
|
189
|
+
query = Query.new(query)
|
|
190
|
+
result = filter_query(self.inventory.devices, query)
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
def get_device(self, obj_id: str, preload_neighbors=False, use_mesh=None, **kwargs) -> Device:
|
|
194
|
+
result = filter_query(self.inventory.devices, Query.new(obj_id))
|
|
195
|
+
if not result:
|
|
196
|
+
raise Exception("not found")
|
|
197
|
+
return result[0]
|
|
198
|
+
|
|
199
|
+
def flush_perf(self):
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def filter_query(devices: list[Device], query: Query) -> list[Device]:
|
|
204
|
+
result: list[Device] = []
|
|
205
|
+
for dev in devices:
|
|
206
|
+
if dev.fqdn in query.query:
|
|
207
|
+
result.append(dev)
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def read_inventory(path: str, storage: Storage) -> Devices:
|
|
212
|
+
with open(path, "r") as f:
|
|
213
|
+
data = f.read()
|
|
214
|
+
file_data = yaml.load(data, Loader=yaml.BaseLoader)
|
|
215
|
+
res = dataclass_from_dict(Devices, file_data)
|
|
216
|
+
for dev in res.devices:
|
|
217
|
+
dev.dev.set_storage(storage)
|
|
218
|
+
return res
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def dataclass_from_dict(klass: type, d: dict[str, Any]):
|
|
222
|
+
try:
|
|
223
|
+
fieldtypes = {f.name: f.type for f in fields(klass)}
|
|
224
|
+
except TypeError:
|
|
225
|
+
return d
|
|
226
|
+
return klass(**{f: dataclass_from_dict(fieldtypes[f], d[f]) for f in d})
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from ipaddress import ip_interface, IPv6Interface
|
|
4
|
+
from typing import List, Optional, Any, Dict, Sequence, Callable
|
|
5
|
+
|
|
6
|
+
from annet.annlib.netdev.views.dump import DumpableView
|
|
7
|
+
from annet.annlib.netdev.views.hardware import HardwareView, lag_name, svi_name
|
|
8
|
+
from annet.storage import Storage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Entity(DumpableView):
|
|
13
|
+
id: int
|
|
14
|
+
name: str
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def _dump__list_key(self):
|
|
18
|
+
return self.name
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Label:
|
|
23
|
+
value: str
|
|
24
|
+
label: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class IpFamily:
|
|
29
|
+
value: int
|
|
30
|
+
label: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class DeviceType:
|
|
35
|
+
id: int
|
|
36
|
+
manufacturer: Entity
|
|
37
|
+
model: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class DeviceIp(DumpableView):
|
|
42
|
+
id: int
|
|
43
|
+
display: str
|
|
44
|
+
address: str
|
|
45
|
+
family: int
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def _dump__list_key(self):
|
|
49
|
+
return self.address
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Prefix(DumpableView):
|
|
54
|
+
id: int
|
|
55
|
+
prefix: str
|
|
56
|
+
site: Optional[Entity]
|
|
57
|
+
vrf: Optional[Entity]
|
|
58
|
+
tenant: Optional[Entity]
|
|
59
|
+
vlan: Optional[Entity]
|
|
60
|
+
role: Optional[Entity]
|
|
61
|
+
status: Label
|
|
62
|
+
is_pool: bool
|
|
63
|
+
custom_fields: dict[str, Any]
|
|
64
|
+
created: datetime
|
|
65
|
+
last_updated: datetime
|
|
66
|
+
description: Optional[str] = ""
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def _dump__list_key(self):
|
|
70
|
+
return self.prefix
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class IpAddress(DumpableView):
|
|
75
|
+
id: int
|
|
76
|
+
assigned_object_id: int
|
|
77
|
+
display: str
|
|
78
|
+
family: IpFamily
|
|
79
|
+
address: str
|
|
80
|
+
status: Label
|
|
81
|
+
tags: List[Entity]
|
|
82
|
+
created: datetime
|
|
83
|
+
last_updated: datetime
|
|
84
|
+
prefix: Optional[Prefix] = None
|
|
85
|
+
vrf: Optional[Entity] = None
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def _dump__list_key(self):
|
|
89
|
+
return self.address
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class InterfaceConnectedEndpoint(Entity):
|
|
94
|
+
device: Entity
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class InterfaceType:
|
|
99
|
+
value: str
|
|
100
|
+
label: str
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class InterfaceMode:
|
|
105
|
+
value: str
|
|
106
|
+
label: str
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class InterfaceVlan(Entity):
|
|
111
|
+
vid: int
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class Interface(Entity):
|
|
116
|
+
device: Entity
|
|
117
|
+
enabled: bool
|
|
118
|
+
description: str
|
|
119
|
+
type: InterfaceType
|
|
120
|
+
connected_endpoints: Optional[list[InterfaceConnectedEndpoint]]
|
|
121
|
+
mode: Optional[InterfaceMode]
|
|
122
|
+
untagged_vlan: Optional[InterfaceVlan]
|
|
123
|
+
tagged_vlans: Optional[List[InterfaceVlan]]
|
|
124
|
+
display: str = ""
|
|
125
|
+
ip_addresses: List[IpAddress] = field(default_factory=list)
|
|
126
|
+
vrf: Optional[Entity] = None
|
|
127
|
+
mtu: int | None = None
|
|
128
|
+
lag: Entity | None = None
|
|
129
|
+
lag_min_links: int | None = None
|
|
130
|
+
|
|
131
|
+
def add_addr(self, address_mask: str, vrf: str | None) -> None:
|
|
132
|
+
addr = ip_interface(address_mask)
|
|
133
|
+
if vrf is None:
|
|
134
|
+
vrf_obj = None
|
|
135
|
+
else:
|
|
136
|
+
vrf_obj = Entity(id=0, name=vrf)
|
|
137
|
+
|
|
138
|
+
if isinstance(addr, IPv6Interface):
|
|
139
|
+
family = IpFamily(value=6, label="IPv6")
|
|
140
|
+
else:
|
|
141
|
+
family = IpFamily(value=4, label="IPv4")
|
|
142
|
+
self.ip_addresses.append(IpAddress(
|
|
143
|
+
id=0,
|
|
144
|
+
display=address_mask,
|
|
145
|
+
address=address_mask,
|
|
146
|
+
vrf=vrf_obj,
|
|
147
|
+
prefix=None,
|
|
148
|
+
family=family,
|
|
149
|
+
created=datetime.now(timezone.utc),
|
|
150
|
+
last_updated=datetime.now(timezone.utc),
|
|
151
|
+
tags=[],
|
|
152
|
+
status=Label(value="active", label="Active"),
|
|
153
|
+
assigned_object_id=self.id,
|
|
154
|
+
))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class NetboxDevice(Entity):
|
|
159
|
+
url: str
|
|
160
|
+
storage: Storage
|
|
161
|
+
|
|
162
|
+
display: str
|
|
163
|
+
device_type: DeviceType
|
|
164
|
+
device_role: Entity
|
|
165
|
+
tenant: Optional[Entity]
|
|
166
|
+
platform: Optional[Entity]
|
|
167
|
+
serial: str
|
|
168
|
+
asset_tag: Optional[str]
|
|
169
|
+
site: Entity
|
|
170
|
+
rack: Optional[Entity]
|
|
171
|
+
position: Optional[float]
|
|
172
|
+
face: Optional[Label]
|
|
173
|
+
status: Label
|
|
174
|
+
primary_ip: Optional[DeviceIp]
|
|
175
|
+
primary_ip4: Optional[DeviceIp]
|
|
176
|
+
primary_ip6: Optional[DeviceIp]
|
|
177
|
+
tags: List[Entity]
|
|
178
|
+
custom_fields: Dict[str, Any]
|
|
179
|
+
created: datetime
|
|
180
|
+
last_updated: datetime
|
|
181
|
+
|
|
182
|
+
fqdn: str
|
|
183
|
+
hostname: str
|
|
184
|
+
hw: Optional[HardwareView]
|
|
185
|
+
breed: str
|
|
186
|
+
|
|
187
|
+
interfaces: List[Interface]
|
|
188
|
+
neighbours: Optional[List["NetboxDevice"]]
|
|
189
|
+
|
|
190
|
+
# compat
|
|
191
|
+
@property
|
|
192
|
+
def neighbours_fqdns(self) -> list[str]:
|
|
193
|
+
if not self.neighbours:
|
|
194
|
+
return []
|
|
195
|
+
return [dev.fqdn for dev in self.neighbours]
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def neighbours_ids(self):
|
|
199
|
+
if not self.neighbours:
|
|
200
|
+
return []
|
|
201
|
+
return [dev.id for dev in self.neighbours]
|
|
202
|
+
|
|
203
|
+
def __hash__(self):
|
|
204
|
+
return hash((self.id, type(self)))
|
|
205
|
+
|
|
206
|
+
def __eq__(self, other):
|
|
207
|
+
return type(self) is type(other) and self.url == other.url
|
|
208
|
+
|
|
209
|
+
def is_pc(self) -> bool:
|
|
210
|
+
return self.device_type.manufacturer.name == "Mellanox" or self.breed == "pc"
|
|
211
|
+
|
|
212
|
+
def _make_interface(self, name: str, type: InterfaceType) -> Interface:
|
|
213
|
+
return Interface(
|
|
214
|
+
name=name,
|
|
215
|
+
device=self,
|
|
216
|
+
enabled=True,
|
|
217
|
+
description="",
|
|
218
|
+
type=type,
|
|
219
|
+
id=0,
|
|
220
|
+
vrf=None,
|
|
221
|
+
display=name,
|
|
222
|
+
untagged_vlan=None,
|
|
223
|
+
tagged_vlans=[],
|
|
224
|
+
ip_addresses=[],
|
|
225
|
+
connected_endpoints=[],
|
|
226
|
+
mode=None,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def _lag_name(self, lag: int) -> str:
|
|
230
|
+
return lag_name(self.hw, lag)
|
|
231
|
+
|
|
232
|
+
def make_lag(self, lag: int, ports: Sequence[str], lag_min_links: int | None) -> Interface:
|
|
233
|
+
new_name = self._lag_name(lag)
|
|
234
|
+
for target_interface in self.interfaces:
|
|
235
|
+
if target_interface.name == new_name:
|
|
236
|
+
return target_interface
|
|
237
|
+
lag_interface = self._make_interface(
|
|
238
|
+
name=new_name,
|
|
239
|
+
type=InterfaceType(value="lag", label="Link Aggregation Group (LAG)"),
|
|
240
|
+
)
|
|
241
|
+
lag_interface.lag_min_links = lag_min_links
|
|
242
|
+
for interface in self.interfaces:
|
|
243
|
+
if interface.name in ports:
|
|
244
|
+
interface.lag = lag_interface
|
|
245
|
+
self.interfaces.append(lag_interface)
|
|
246
|
+
return lag_interface
|
|
247
|
+
|
|
248
|
+
def _svi_name(self, svi: int) -> str:
|
|
249
|
+
return svi_name(self.hw, svi)
|
|
250
|
+
|
|
251
|
+
def add_svi(self, svi: int) -> Interface:
|
|
252
|
+
name = self._svi_name(svi)
|
|
253
|
+
for interface in self.interfaces:
|
|
254
|
+
if interface.name == name:
|
|
255
|
+
return interface
|
|
256
|
+
interface = self._make_interface(
|
|
257
|
+
name=name,
|
|
258
|
+
type=InterfaceType("virtual", "Virtual")
|
|
259
|
+
)
|
|
260
|
+
self.interfaces.append(interface)
|
|
261
|
+
return interface
|
|
262
|
+
|
|
263
|
+
def _subif_name(self, interface: str, subif: int) -> str:
|
|
264
|
+
return f"{interface}.{subif}"
|
|
265
|
+
|
|
266
|
+
def add_subif(self, interface: str, subif: int) -> Interface:
|
|
267
|
+
name = self._subif_name(interface, subif)
|
|
268
|
+
for target_port in self.interfaces:
|
|
269
|
+
if target_port.name == name:
|
|
270
|
+
return target_port
|
|
271
|
+
target_port = self._make_interface(
|
|
272
|
+
name=name,
|
|
273
|
+
type=InterfaceType("virtual", "Virtual")
|
|
274
|
+
)
|
|
275
|
+
self.interfaces.append(target_port)
|
|
276
|
+
return target_port
|
|
@@ -15,8 +15,7 @@ from annet.adapters.netbox.common.manufacturer import (
|
|
|
15
15
|
from annet.adapters.netbox.common.query import NetboxQuery
|
|
16
16
|
from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
|
|
17
17
|
from annet.annlib.netdev.views.hardware import HardwareView
|
|
18
|
-
from annet.storage import Storage
|
|
19
|
-
|
|
18
|
+
from annet.storage import Storage, Device, Interface
|
|
20
19
|
|
|
21
20
|
logger = getLogger(__name__)
|
|
22
21
|
|
|
@@ -68,7 +67,9 @@ def extend_device(
|
|
|
68
67
|
return res
|
|
69
68
|
|
|
70
69
|
|
|
71
|
-
@impl_converter
|
|
70
|
+
@impl_converter(
|
|
71
|
+
recipe=[link_constant(P[models.Interface].lag_min_links, value=None)],
|
|
72
|
+
)
|
|
72
73
|
def extend_interface(
|
|
73
74
|
interface: api_models.Interface,
|
|
74
75
|
ip_addresses: List[models.IpAddress],
|
|
@@ -89,6 +90,7 @@ class NetboxStorageV37(Storage):
|
|
|
89
90
|
url=opts.url,
|
|
90
91
|
token=opts.token,
|
|
91
92
|
)
|
|
93
|
+
self._all_fqdns: Optional[list[str]] = None
|
|
92
94
|
|
|
93
95
|
def __enter__(self):
|
|
94
96
|
return self
|
|
@@ -106,6 +108,14 @@ class NetboxStorageV37(Storage):
|
|
|
106
108
|
d.name for d in self._load_devices(query)
|
|
107
109
|
]
|
|
108
110
|
|
|
111
|
+
def resolve_all_fdnds(self) -> list[str]:
|
|
112
|
+
if self._all_fqdns is None:
|
|
113
|
+
self._all_fqdns = [
|
|
114
|
+
d.name
|
|
115
|
+
for d in self.netbox.dcim_all_devices().results
|
|
116
|
+
]
|
|
117
|
+
return self._all_fqdns
|
|
118
|
+
|
|
109
119
|
def make_devices(
|
|
110
120
|
self,
|
|
111
121
|
query: Union[NetboxQuery, list],
|
|
@@ -218,6 +228,24 @@ class NetboxStorageV37(Storage):
|
|
|
218
228
|
def flush_perf(self):
|
|
219
229
|
pass
|
|
220
230
|
|
|
231
|
+
def search_connections(self, device: Device, neighbor: Device) -> list[tuple[Interface, Interface]]:
|
|
232
|
+
if device.storage is not self:
|
|
233
|
+
raise ValueError("device does not belong to this storage")
|
|
234
|
+
if neighbor.storage is not self:
|
|
235
|
+
raise ValueError("neighbor does not belong to this storage")
|
|
236
|
+
# both devices are NetboxDevice if they are loaded from this storage
|
|
237
|
+
res = []
|
|
238
|
+
for local_port in device.interfaces:
|
|
239
|
+
if not local_port.connected_endpoints:
|
|
240
|
+
continue
|
|
241
|
+
for endpoint in local_port.connected_endpoints:
|
|
242
|
+
if endpoint.device.id == neighbor.id:
|
|
243
|
+
for remote_port in neighbor.interfaces:
|
|
244
|
+
if remote_port.name == endpoint.name:
|
|
245
|
+
res.append((local_port, remote_port))
|
|
246
|
+
break
|
|
247
|
+
return res
|
|
248
|
+
|
|
221
249
|
|
|
222
250
|
def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
223
251
|
for subquery in query.globs:
|
|
@@ -123,3 +123,34 @@ def vendor_to_hw(vendor):
|
|
|
123
123
|
None,
|
|
124
124
|
)
|
|
125
125
|
return hw
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def lag_name(hw: HardwareView, nlagg: int) -> str:
|
|
129
|
+
if hw.Huawei:
|
|
130
|
+
return f"Eth-Trunk{nlagg}"
|
|
131
|
+
if hw.Cisco:
|
|
132
|
+
return f"port-channel{nlagg}"
|
|
133
|
+
if hw.Nexus:
|
|
134
|
+
return f"port-channel{nlagg}"
|
|
135
|
+
if hw.Arista:
|
|
136
|
+
return f"Port-Channel{nlagg}"
|
|
137
|
+
if hw.Juniper:
|
|
138
|
+
return f"ae{nlagg}"
|
|
139
|
+
if hw.Nokia:
|
|
140
|
+
return f"lag-{nlagg}"
|
|
141
|
+
if hw.PC.Whitebox:
|
|
142
|
+
return f"bond{nlagg}"
|
|
143
|
+
if hw.PC:
|
|
144
|
+
return f"lagg{nlagg}"
|
|
145
|
+
if hw.Nokia:
|
|
146
|
+
return f"lagg-{nlagg}"
|
|
147
|
+
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
|
+
else:
|
|
156
|
+
return f"vlan{num}"
|