netbox-cisco-sync 1.0.1__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.
- netbox_cisco_sync/__init__.py +86 -0
- netbox_cisco_sync/api/__init__.py +3 -0
- netbox_cisco_sync/api/client.py +5 -0
- netbox_cisco_sync/api/serializers.py +164 -0
- netbox_cisco_sync/api/urls.py +26 -0
- netbox_cisco_sync/api/views.py +71 -0
- netbox_cisco_sync/catalyst_services.py +107 -0
- netbox_cisco_sync/choices.py +40 -0
- netbox_cisco_sync/collector.py +21 -0
- netbox_cisco_sync/connectors/__init__.py +5 -0
- netbox_cisco_sync/connectors/base.py +39 -0
- netbox_cisco_sync/connectors/catalyst_center/__init__.py +6 -0
- netbox_cisco_sync/connectors/catalyst_center/client.py +157 -0
- netbox_cisco_sync/connectors/catalyst_center/collector.py +440 -0
- netbox_cisco_sync/connectors/catalyst_center/connector.py +76 -0
- netbox_cisco_sync/connectors/meraki/__init__.py +4 -0
- netbox_cisco_sync/connectors/meraki/client.py +91 -0
- netbox_cisco_sync/connectors/meraki/collector.py +345 -0
- netbox_cisco_sync/connectors/meraki/connector.py +45 -0
- netbox_cisco_sync/connectors/vmanage/__init__.py +6 -0
- netbox_cisco_sync/connectors/vmanage/client.py +76 -0
- netbox_cisco_sync/connectors/vmanage/collector.py +129 -0
- netbox_cisco_sync/connectors/vmanage/connector.py +32 -0
- netbox_cisco_sync/device_type_interfaces.py +15 -0
- netbox_cisco_sync/dto.py +85 -0
- netbox_cisco_sync/engine.py +9 -0
- netbox_cisco_sync/filtersets.py +61 -0
- netbox_cisco_sync/forms.py +222 -0
- netbox_cisco_sync/interface_cache.py +225 -0
- netbox_cisco_sync/jobs.py +461 -0
- netbox_cisco_sync/matcher.py +128 -0
- netbox_cisco_sync/meraki_services.py +96 -0
- netbox_cisco_sync/migrations/0001_initial.py +336 -0
- netbox_cisco_sync/migrations/0002_catalyst_detailed_stacks.py +80 -0
- netbox_cisco_sync/migrations/0003_catalyst_interface_cache_mode.py +16 -0
- netbox_cisco_sync/migrations/0004_discovered_interface_bridge.py +16 -0
- netbox_cisco_sync/migrations/0005_discovered_interface_label.py +16 -0
- netbox_cisco_sync/migrations/0006_remove_catalyst_detailed_stacks.py +26 -0
- netbox_cisco_sync/migrations/0007_catalyst_stack_metadata.py +44 -0
- netbox_cisco_sync/migrations/0008_cisco_sync_custom_fields.py +83 -0
- netbox_cisco_sync/migrations/0009_ignore_rfc1918_wan_ips.py +32 -0
- netbox_cisco_sync/migrations/0010_discovered_interface_lag.py +13 -0
- netbox_cisco_sync/migrations/__init__.py +0 -0
- netbox_cisco_sync/models.py +640 -0
- netbox_cisco_sync/navigation.py +23 -0
- netbox_cisco_sync/plugin_settings.py +43 -0
- netbox_cisco_sync/request_limits.py +77 -0
- netbox_cisco_sync/services.py +109 -0
- netbox_cisco_sync/sync/__init__.py +6 -0
- netbox_cisco_sync/sync/addresses.py +175 -0
- netbox_cisco_sync/sync/device_custom_fields.py +75 -0
- netbox_cisco_sync/sync/fhrp.py +108 -0
- netbox_cisco_sync/sync/interfaces.py +335 -0
- netbox_cisco_sync/sync/orchestrator.py +238 -0
- netbox_cisco_sync/sync/result.py +59 -0
- netbox_cisco_sync/sync/sites.py +110 -0
- netbox_cisco_sync/sync/tags.py +31 -0
- netbox_cisco_sync/sync/vlans.py +31 -0
- netbox_cisco_sync/sync/wan.py +53 -0
- netbox_cisco_sync/tables.py +142 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/bulk_sync_confirm.html +42 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/buttons/bulk_apply_selected.html +8 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/buttons/bulk_enable_selected.html +8 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/buttons/bulk_refresh_interfaces.html +8 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/device_interface_loading.html +39 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/device_sync_edit.html +199 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/devicecatalystcenterconfig_list.html +52 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/devicemerakiconfig_list.html +29 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/devicevmanageconfig_list.html +29 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/inc/discovered_device_tabs.html +56 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/inc/discovery_overlay.html +23 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/inc/interface_refresh_overlay.html +16 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/inc/preserve_device_selection.html +15 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/inc/settings_source_tabs.html +17 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/settings.html +36 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/syncrun.html +37 -0
- netbox_cisco_sync/templates/netbox_cisco_sync/syncrun_revert.html +3 -0
- netbox_cisco_sync/urls.py +28 -0
- netbox_cisco_sync/view_modules/__init__.py +1 -0
- netbox_cisco_sync/view_modules/catalyst_center.py +250 -0
- netbox_cisco_sync/view_modules/common.py +574 -0
- netbox_cisco_sync/view_modules/meraki.py +195 -0
- netbox_cisco_sync/view_modules/settings.py +94 -0
- netbox_cisco_sync/view_modules/sync_runs.py +60 -0
- netbox_cisco_sync/view_modules/vmanage.py +211 -0
- netbox_cisco_sync/views.py +67 -0
- netbox_cisco_sync-1.0.1.dist-info/METADATA +417 -0
- netbox_cisco_sync-1.0.1.dist-info/RECORD +91 -0
- netbox_cisco_sync-1.0.1.dist-info/WHEEL +5 -0
- netbox_cisco_sync-1.0.1.dist-info/licenses/LICENSE +21 -0
- netbox_cisco_sync-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Plugin NetBox synchronizujący inventory z obsługiwanych platform Cisco."""
|
|
2
|
+
|
|
3
|
+
from netbox.plugins import PluginConfig
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.1"
|
|
6
|
+
|
|
7
|
+
DEFAULT_PLUGIN_SETTINGS = {
|
|
8
|
+
# Czas jednej zbiorczej sesji Refresh lub Apply, w sekundach.
|
|
9
|
+
"sync_session_timeout_seconds": 300,
|
|
10
|
+
# Pusty slug oznacza wybór pierwszego VLAN-u o szukanym VID, niezależnie od grupy.
|
|
11
|
+
"preferred_vlan_group_slug": "",
|
|
12
|
+
# Ustawienia vManage są oddzielone od innych źródeł, aby dodanie kolejnej
|
|
13
|
+
# platformy nie zmieniało niejawnie jej filtrów ani nazw obiektów NetBox.
|
|
14
|
+
"vmanage_ignored_interface_names": [
|
|
15
|
+
"nvi0",
|
|
16
|
+
"vmanage_system",
|
|
17
|
+
"null0",
|
|
18
|
+
"appnav-compress",
|
|
19
|
+
],
|
|
20
|
+
"vmanage_ignored_interface_patterns": [
|
|
21
|
+
r"virtualportgroup\d+",
|
|
22
|
+
r"loopback655\d{2}",
|
|
23
|
+
r"service-engine.*",
|
|
24
|
+
],
|
|
25
|
+
"vmanage_default_platform_name": "Cisco SD-WAN",
|
|
26
|
+
"vmanage_default_platform_slug": "sd-wan-cisco",
|
|
27
|
+
"vmanage_site_id_field": "SdwanSiteId",
|
|
28
|
+
"vmanage_controller_types": ["vmanage", "vsmart", "vbond", "vedge-vbond"],
|
|
29
|
+
"vmanage_edge_types": ["vedge", "cedge"],
|
|
30
|
+
# Catalyst Center korzysta z osobnych filtrów. Puste listy oznaczają, że
|
|
31
|
+
# domyślnie nie odrzucamy interfejsów na podstawie nazw właściwych SD-WAN.
|
|
32
|
+
"ccc_ignored_interface_names": [],
|
|
33
|
+
"ccc_ignored_interface_patterns": [],
|
|
34
|
+
"ccc_default_platform_name": "Cisco Catalyst Center",
|
|
35
|
+
"ccc_default_platform_slug": "cisco-catalyst-center",
|
|
36
|
+
"meraki_ignored_interface_names": [],
|
|
37
|
+
"meraki_ignored_interface_patterns": [],
|
|
38
|
+
"meraki_default_platform_name": "Cisco Meraki",
|
|
39
|
+
"meraki_default_platform_slug": "cisco-meraki",
|
|
40
|
+
"meraki_site_id_field": "MerakiSiteId",
|
|
41
|
+
# Dokładne mapowanie prędkości portu MS na wartości choices NetBox.
|
|
42
|
+
"meraki_speed_to_interface_type": {
|
|
43
|
+
100: "100base-tx",
|
|
44
|
+
1000: "1000base-t",
|
|
45
|
+
2500: "2.5gbase-t",
|
|
46
|
+
5000: "5gbase-t",
|
|
47
|
+
10000: "10gbase-t",
|
|
48
|
+
25000: "25gbase-x-sfp28",
|
|
49
|
+
40000: "40gbase-x-qsfpp",
|
|
50
|
+
100000: "100gbase-x-qsfp28",
|
|
51
|
+
},
|
|
52
|
+
"meraki_family_interface_types": {
|
|
53
|
+
"MS": "1000base-t",
|
|
54
|
+
"MX": "1000base-t",
|
|
55
|
+
"MR": "ieee802.11ax",
|
|
56
|
+
"MG": "lte",
|
|
57
|
+
"MV": "other",
|
|
58
|
+
"MT": "other",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class NetBoxCiscoSyncConfig(PluginConfig):
|
|
64
|
+
name = "netbox_cisco_sync"
|
|
65
|
+
label = "netbox_cisco_sync"
|
|
66
|
+
verbose_name = "NetBox Cisco Sync"
|
|
67
|
+
description = "Synchronize Cisco platform inventory into NetBox."
|
|
68
|
+
version = __version__
|
|
69
|
+
author = "Lukasz Luczak"
|
|
70
|
+
author_email = "bleblas@gmail.com"
|
|
71
|
+
base_url = "cisco-sync"
|
|
72
|
+
min_version = "4.6.0"
|
|
73
|
+
max_version = "4.6.99"
|
|
74
|
+
required_settings = []
|
|
75
|
+
default_settings = DEFAULT_PLUGIN_SETTINGS
|
|
76
|
+
|
|
77
|
+
def ready(self):
|
|
78
|
+
super().ready()
|
|
79
|
+
# Sam import rejestruje dekorowane zadania cykliczne w workerze NetBox.
|
|
80
|
+
from . import jobs # noqa: F401
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Tymczasowy alias zachowuje zgodność integracji importujących starą nazwę klasy.
|
|
84
|
+
NetBoxVmanageSyncConfig = NetBoxCiscoSyncConfig
|
|
85
|
+
|
|
86
|
+
config = NetBoxCiscoSyncConfig
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Serializery REST API wymagane również przez change log NetBoxa."""
|
|
2
|
+
|
|
3
|
+
from dcim.api.serializers import DeviceSerializer, SiteSerializer
|
|
4
|
+
from netbox.api.serializers import NetBoxModelSerializer
|
|
5
|
+
|
|
6
|
+
from ..models import (
|
|
7
|
+
CatalystCenterConfig,
|
|
8
|
+
DeviceCatalystCenterConfig,
|
|
9
|
+
DeviceMerakiConfig,
|
|
10
|
+
DeviceVmanageConfig,
|
|
11
|
+
DiscoveredInterface,
|
|
12
|
+
SyncRun,
|
|
13
|
+
VmanageSyncConfig,
|
|
14
|
+
MerakiConfig,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DeviceVmanageConfigSerializer(NetBoxModelSerializer):
|
|
19
|
+
"""Reprezentacja wykrytego urządzenia i jego mapowania do NetBoxa."""
|
|
20
|
+
|
|
21
|
+
netbox_site = SiteSerializer(nested=True, allow_null=True)
|
|
22
|
+
device = DeviceSerializer(nested=True, allow_null=True)
|
|
23
|
+
|
|
24
|
+
class Meta:
|
|
25
|
+
model = DeviceVmanageConfig
|
|
26
|
+
fields = (
|
|
27
|
+
"id", "url", "display", "system_ip", "vmanage_uuid", "hostname",
|
|
28
|
+
"serial_number", "device_model", "vmanage_site_id", "vmanage_site_name",
|
|
29
|
+
"latitude", "longitude", "device_groups", "reachability", "netbox_site",
|
|
30
|
+
"device", "match_method", "manual_site_override", "enabled",
|
|
31
|
+
"interface_selection", "interfaces_last_refreshed", "last_discovered",
|
|
32
|
+
"last_sync_at", "last_sync_status", "last_sync_message", "tags", "custom_fields",
|
|
33
|
+
"created", "last_updated",
|
|
34
|
+
)
|
|
35
|
+
brief_fields = ("id", "url", "display", "system_ip", "hostname", "device")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DeviceCatalystCenterConfigSerializer(NetBoxModelSerializer):
|
|
39
|
+
"""Reprezentacja urządzenia wykrytego przez Catalyst Center."""
|
|
40
|
+
|
|
41
|
+
netbox_site = SiteSerializer(nested=True, allow_null=True)
|
|
42
|
+
device = DeviceSerializer(nested=True, allow_null=True)
|
|
43
|
+
|
|
44
|
+
class Meta:
|
|
45
|
+
model = DeviceCatalystCenterConfig
|
|
46
|
+
fields = (
|
|
47
|
+
"id", "url", "display", "catalyst_id", "management_ip", "hostname",
|
|
48
|
+
"serial_number", "switch_stack_serials", "switch_stack_info", "device_model",
|
|
49
|
+
"catalyst_site_id", "catalyst_site_name",
|
|
50
|
+
"latitude", "longitude", "reachability", "catalyst_role", "catalyst_family", "netbox_site", "device",
|
|
51
|
+
"match_method", "manual_site_override", "enabled",
|
|
52
|
+
"interface_selection", "interfaces_last_refreshed", "last_discovered",
|
|
53
|
+
"last_sync_at", "last_sync_status", "last_sync_message", "tags", "custom_fields",
|
|
54
|
+
"created", "last_updated",
|
|
55
|
+
)
|
|
56
|
+
brief_fields = ("id", "url", "display", "management_ip", "hostname", "device")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DeviceMerakiConfigSerializer(NetBoxModelSerializer):
|
|
60
|
+
"""Reprezentacja urządzenia wykrytego przez Meraki Dashboard."""
|
|
61
|
+
netbox_site = SiteSerializer(nested=True, allow_null=True)
|
|
62
|
+
device = DeviceSerializer(nested=True, allow_null=True)
|
|
63
|
+
|
|
64
|
+
class Meta:
|
|
65
|
+
model = DeviceMerakiConfig
|
|
66
|
+
fields = (
|
|
67
|
+
"id", "url", "display", "serial_number", "hostname", "device_model",
|
|
68
|
+
"product_family", "firmware", "network_id", "network_name", "management_ip",
|
|
69
|
+
"latitude", "longitude", "netbox_site", "device", "match_method",
|
|
70
|
+
"manual_site_override", "enabled", "interface_selection", "interfaces_last_refreshed",
|
|
71
|
+
"last_discovered", "last_sync_at",
|
|
72
|
+
"last_sync_status", "last_sync_message", "tags", "custom_fields", "created", "last_updated",
|
|
73
|
+
)
|
|
74
|
+
brief_fields = ("id", "url", "display", "serial_number", "hostname", "device")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DiscoveredInterfaceSerializer(NetBoxModelSerializer):
|
|
78
|
+
"""Reprezentacja relacyjnego cache interfejsów platform źródłowych."""
|
|
79
|
+
|
|
80
|
+
class Meta:
|
|
81
|
+
model = DiscoveredInterface
|
|
82
|
+
fields = (
|
|
83
|
+
"id", "url", "display", "vmanage_config", "catalyst_config",
|
|
84
|
+
"meraki_config", "name", "normalized_name", "description", "label",
|
|
85
|
+
"interface_type", "admin_up", "oper_up", "mac_address",
|
|
86
|
+
"ipv4_address", "ipv4_mask", "ipv6_addresses", "mtu",
|
|
87
|
+
"speed_mbps", "vpn_id", "selected", "present", "last_seen",
|
|
88
|
+
"mode", "untagged_vlan", "tagged_vlans",
|
|
89
|
+
"bridge_name",
|
|
90
|
+
"tags", "custom_fields", "created", "last_updated",
|
|
91
|
+
)
|
|
92
|
+
brief_fields = ("id", "url", "display", "name", "selected", "present")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class SyncRunSerializer(NetBoxModelSerializer):
|
|
96
|
+
"""Historia Compare/Apply serializowana m.in. przed usunięciem rekordu."""
|
|
97
|
+
|
|
98
|
+
config = DeviceVmanageConfigSerializer(nested=True, allow_null=True)
|
|
99
|
+
catalyst_config = DeviceCatalystCenterConfigSerializer(nested=True, allow_null=True)
|
|
100
|
+
meraki_config = DeviceMerakiConfigSerializer(nested=True, allow_null=True)
|
|
101
|
+
device = DeviceSerializer(nested=True, allow_null=True)
|
|
102
|
+
|
|
103
|
+
class Meta:
|
|
104
|
+
model = SyncRun
|
|
105
|
+
fields = (
|
|
106
|
+
"id", "url", "display", "config", "catalyst_config", "meraki_config", "device", "trigger", "mode", "status",
|
|
107
|
+
"devices_created", "devices_updated", "interfaces_created", "interfaces_updated",
|
|
108
|
+
"ips_created", "macs_created", "fhrp_groups_created", "message",
|
|
109
|
+
"source_data",
|
|
110
|
+
"reverted", "tags", "custom_fields", "created", "last_updated",
|
|
111
|
+
)
|
|
112
|
+
brief_fields = ("id", "url", "display", "config", "catalyst_config", "meraki_config", "device", "mode", "status")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class VmanageSyncConfigSerializer(NetBoxModelSerializer):
|
|
116
|
+
"""Globalne ustawienia; hasło może być zapisane, ale nigdy zwrócone przez API."""
|
|
117
|
+
|
|
118
|
+
class Meta:
|
|
119
|
+
model = VmanageSyncConfig
|
|
120
|
+
fields = (
|
|
121
|
+
"id", "url", "display", "base_url", "username", "password", "verify_ssl",
|
|
122
|
+
"ca_bundle", "request_timeout", "request_retries", "update_existing",
|
|
123
|
+
"sync_interfaces", "ignore_disabled_interfaces", "sync_ipv4", "sync_ipv6",
|
|
124
|
+
"sync_mac_addresses", "set_primary_ips", "preferred_primary_ipv4_interface",
|
|
125
|
+
"sync_fhrp", "gps_max_distance_km", "default_device_role", "sync_interval_hours",
|
|
126
|
+
"history_keep_days", "tags", "custom_fields", "created", "last_updated",
|
|
127
|
+
)
|
|
128
|
+
brief_fields = ("id", "url", "display", "base_url", "username")
|
|
129
|
+
# Sekret jest akceptowany przy zapisie, ale nie może pojawić się w odpowiedzi
|
|
130
|
+
# API ani w serializowanym change logu NetBoxa.
|
|
131
|
+
extra_kwargs = {"password": {"write_only": True}}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CatalystCenterConfigSerializer(NetBoxModelSerializer):
|
|
135
|
+
"""Ustawienia Catalyst Center z sekretem wyłącznie do zapisu."""
|
|
136
|
+
|
|
137
|
+
class Meta:
|
|
138
|
+
model = CatalystCenterConfig
|
|
139
|
+
fields = (
|
|
140
|
+
"id", "url", "display", "base_url", "username", "password", "verify_ssl",
|
|
141
|
+
"ca_bundle", "request_timeout", "request_retries", "enabled", "update_existing",
|
|
142
|
+
"sync_interfaces", "ignore_disabled_interfaces", "sync_ipv4", "sync_ipv6",
|
|
143
|
+
"sync_mac_addresses", "set_primary_ips", "preferred_primary_ipv4_interface",
|
|
144
|
+
"import_device_roles", "sync_fhrp", "default_device_role", "sync_interval_hours",
|
|
145
|
+
"gps_max_distance_km", "history_keep_days", "tags", "custom_fields", "created", "last_updated",
|
|
146
|
+
)
|
|
147
|
+
brief_fields = ("id", "url", "display", "base_url", "username", "enabled")
|
|
148
|
+
extra_kwargs = {"password": {"write_only": True}}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class MerakiConfigSerializer(NetBoxModelSerializer):
|
|
152
|
+
"""Ustawienia Meraki z kluczem API dostępnym wyłącznie przy zapisie."""
|
|
153
|
+
class Meta:
|
|
154
|
+
model = MerakiConfig
|
|
155
|
+
fields = (
|
|
156
|
+
"id", "url", "display", "enabled", "base_url", "api_key", "organization_ids",
|
|
157
|
+
"request_timeout", "request_retries", "update_existing", "sync_interfaces",
|
|
158
|
+
"ignore_disabled_interfaces", "sync_ipv4", "sync_ipv6", "sync_mac_addresses",
|
|
159
|
+
"set_primary_ips", "preferred_primary_ipv4_interface", "import_device_families",
|
|
160
|
+
"gps_max_distance_km", "default_device_role", "sync_interval_hours",
|
|
161
|
+
"history_keep_days", "tags", "custom_fields", "created", "last_updated",
|
|
162
|
+
)
|
|
163
|
+
brief_fields = ("id", "url", "display", "enabled", "base_url")
|
|
164
|
+
extra_kwargs = {"api_key": {"write_only": True}}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Router REST API pluginu zgodny z automatycznym ładowaniem przez NetBox."""
|
|
2
|
+
|
|
3
|
+
from netbox.api.routers import NetBoxRouter
|
|
4
|
+
|
|
5
|
+
from .views import (
|
|
6
|
+
CatalystCenterConfigViewSet,
|
|
7
|
+
DeviceCatalystCenterConfigViewSet,
|
|
8
|
+
DeviceMerakiConfigViewSet,
|
|
9
|
+
DeviceVmanageConfigViewSet,
|
|
10
|
+
DiscoveredInterfaceViewSet,
|
|
11
|
+
SyncRunViewSet,
|
|
12
|
+
VmanageSyncConfigViewSet,
|
|
13
|
+
MerakiConfigViewSet,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
router = NetBoxRouter()
|
|
17
|
+
router.register("device-vmanage-configs", DeviceVmanageConfigViewSet)
|
|
18
|
+
router.register("device-catalyst-center-configs", DeviceCatalystCenterConfigViewSet)
|
|
19
|
+
router.register("device-meraki-configs", DeviceMerakiConfigViewSet)
|
|
20
|
+
router.register("discovered-interfaces", DiscoveredInterfaceViewSet)
|
|
21
|
+
router.register("sync-runs", SyncRunViewSet)
|
|
22
|
+
router.register("settings", VmanageSyncConfigViewSet)
|
|
23
|
+
router.register("catalyst-center-settings", CatalystCenterConfigViewSet)
|
|
24
|
+
router.register("meraki-settings", MerakiConfigViewSet)
|
|
25
|
+
|
|
26
|
+
urlpatterns = router.urls
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""ViewSety modeli pluginu dla REST API NetBoxa."""
|
|
2
|
+
|
|
3
|
+
from netbox.api.viewsets import NetBoxModelViewSet
|
|
4
|
+
|
|
5
|
+
from .. import filtersets
|
|
6
|
+
from ..models import (
|
|
7
|
+
CatalystCenterConfig,
|
|
8
|
+
DeviceCatalystCenterConfig,
|
|
9
|
+
DeviceMerakiConfig,
|
|
10
|
+
DeviceVmanageConfig,
|
|
11
|
+
DiscoveredInterface,
|
|
12
|
+
SyncRun,
|
|
13
|
+
VmanageSyncConfig,
|
|
14
|
+
MerakiConfig,
|
|
15
|
+
)
|
|
16
|
+
from .serializers import (
|
|
17
|
+
CatalystCenterConfigSerializer,
|
|
18
|
+
DeviceCatalystCenterConfigSerializer,
|
|
19
|
+
DeviceMerakiConfigSerializer,
|
|
20
|
+
DeviceVmanageConfigSerializer,
|
|
21
|
+
DiscoveredInterfaceSerializer,
|
|
22
|
+
SyncRunSerializer,
|
|
23
|
+
VmanageSyncConfigSerializer,
|
|
24
|
+
MerakiConfigSerializer,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DeviceVmanageConfigViewSet(NetBoxModelViewSet):
|
|
29
|
+
queryset = DeviceVmanageConfig.objects.select_related("netbox_site", "device")
|
|
30
|
+
serializer_class = DeviceVmanageConfigSerializer
|
|
31
|
+
filterset_class = filtersets.DeviceVmanageConfigFilterSet
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DeviceCatalystCenterConfigViewSet(NetBoxModelViewSet):
|
|
35
|
+
queryset = DeviceCatalystCenterConfig.objects.select_related("netbox_site", "device")
|
|
36
|
+
serializer_class = DeviceCatalystCenterConfigSerializer
|
|
37
|
+
filterset_class = filtersets.DeviceCatalystCenterConfigFilterSet
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DeviceMerakiConfigViewSet(NetBoxModelViewSet):
|
|
41
|
+
queryset = DeviceMerakiConfig.objects.select_related("netbox_site", "device")
|
|
42
|
+
serializer_class = DeviceMerakiConfigSerializer
|
|
43
|
+
filterset_class = filtersets.DeviceMerakiConfigFilterSet
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DiscoveredInterfaceViewSet(NetBoxModelViewSet):
|
|
47
|
+
queryset = DiscoveredInterface.objects.all()
|
|
48
|
+
serializer_class = DiscoveredInterfaceSerializer
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SyncRunViewSet(NetBoxModelViewSet):
|
|
52
|
+
queryset = SyncRun.objects.select_related("config", "device")
|
|
53
|
+
serializer_class = SyncRunSerializer
|
|
54
|
+
filterset_class = filtersets.SyncRunFilterSet
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class VmanageSyncConfigViewSet(NetBoxModelViewSet):
|
|
58
|
+
# Model jest singletonem, ale rejestracja ViewSetu jest potrzebna, aby NetBox
|
|
59
|
+
# potrafił znaleźć jego serializer podczas tworzenia change logu.
|
|
60
|
+
queryset = VmanageSyncConfig.objects.all()
|
|
61
|
+
serializer_class = VmanageSyncConfigSerializer
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CatalystCenterConfigViewSet(NetBoxModelViewSet):
|
|
65
|
+
queryset = CatalystCenterConfig.objects.all()
|
|
66
|
+
serializer_class = CatalystCenterConfigSerializer
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MerakiConfigViewSet(NetBoxModelViewSet):
|
|
70
|
+
queryset = MerakiConfig.objects.all()
|
|
71
|
+
serializer_class = MerakiConfigSerializer
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Application services for Catalyst Center discovery and collection."""
|
|
2
|
+
|
|
3
|
+
from django.utils import timezone
|
|
4
|
+
|
|
5
|
+
from dcim.models import Site
|
|
6
|
+
|
|
7
|
+
from .choices import MatchMethodChoices
|
|
8
|
+
from .connectors.catalyst_center import CatalystCenterConnector
|
|
9
|
+
from .connectors.catalyst_center.collector import parse_device
|
|
10
|
+
from .matcher import match_site, site_prefix_bindings
|
|
11
|
+
from .models import CatalystCenterConfig, DeviceCatalystCenterConfig
|
|
12
|
+
from .interface_cache import collect_cached_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def discover_catalyst_devices():
|
|
16
|
+
settings = CatalystCenterConfig.get()
|
|
17
|
+
if not settings.enabled:
|
|
18
|
+
raise RuntimeError("Catalyst Center integration is disabled in Settings.")
|
|
19
|
+
devices = CatalystCenterConnector(settings).connect().collect_inventory()
|
|
20
|
+
sites = list(Site.objects.all())
|
|
21
|
+
prefix_bindings = site_prefix_bindings(sites)
|
|
22
|
+
created = updated = 0
|
|
23
|
+
for row in devices:
|
|
24
|
+
config = DeviceCatalystCenterConfig.objects.filter(catalyst_id=row.uuid).first()
|
|
25
|
+
is_new = config is None
|
|
26
|
+
if is_new:
|
|
27
|
+
config = DeviceCatalystCenterConfig(catalyst_id=row.uuid, hostname=row.hostname)
|
|
28
|
+
config.management_ip = row.system_ip or None
|
|
29
|
+
config.hostname = row.hostname
|
|
30
|
+
config.serial_number = row.serial_number
|
|
31
|
+
config.switch_stack_serials = list(row.switch_stack_serials)
|
|
32
|
+
config.switch_stack_info = list(row.switch_stack_info)
|
|
33
|
+
config.device_model = row.model
|
|
34
|
+
config.catalyst_site_id = row.site_id
|
|
35
|
+
config.catalyst_site_name = row.site_name
|
|
36
|
+
config.latitude = row.latitude
|
|
37
|
+
config.longitude = row.longitude
|
|
38
|
+
config.reachability = row.reachability
|
|
39
|
+
config.catalyst_role = row.source_role
|
|
40
|
+
config.catalyst_family = row.source_family
|
|
41
|
+
config.last_discovered = timezone.now()
|
|
42
|
+
if not config.manual_site_override:
|
|
43
|
+
# ID Building z CCC nie jest identyfikatorem SdwanSiteId używanym przez
|
|
44
|
+
# vManage, dlatego dla CCC stosujemy nazwę, a następnie odległość GPS.
|
|
45
|
+
site, method = match_site(
|
|
46
|
+
row,
|
|
47
|
+
sites,
|
|
48
|
+
settings.gps_max_distance_km,
|
|
49
|
+
use_custom_field=False,
|
|
50
|
+
prefix_bindings=prefix_bindings,
|
|
51
|
+
)
|
|
52
|
+
config.netbox_site = site
|
|
53
|
+
config.match_method = method
|
|
54
|
+
elif config.netbox_site_id:
|
|
55
|
+
config.match_method = MatchMethodChoices.MANUAL
|
|
56
|
+
config.save()
|
|
57
|
+
created += int(is_new)
|
|
58
|
+
updated += int(not is_new)
|
|
59
|
+
return {"seen": len(devices), "created": created, "updated": updated}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def catalyst_inventory_dto(config):
|
|
63
|
+
"""Odtwórz podstawowe DTO Catalyst Center zapisane podczas Discovery."""
|
|
64
|
+
data = parse_device(
|
|
65
|
+
{
|
|
66
|
+
"id": config.catalyst_id,
|
|
67
|
+
"hostname": config.hostname,
|
|
68
|
+
"managementIpAddress": str(config.management_ip or ""),
|
|
69
|
+
"serialNumber": config.serial_number,
|
|
70
|
+
"serialNumbers": [config.serial_number, *config.switch_stack_serials],
|
|
71
|
+
"stackSwitchInfo": config.switch_stack_info,
|
|
72
|
+
"platformId": config.device_model,
|
|
73
|
+
"reachabilityStatus": config.reachability,
|
|
74
|
+
"role": config.catalyst_role,
|
|
75
|
+
"family": config.catalyst_family,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": config.catalyst_site_id,
|
|
79
|
+
"name": config.catalyst_site_name,
|
|
80
|
+
"latitude": float(config.latitude) if config.latitude is not None else None,
|
|
81
|
+
"longitude": float(config.longitude) if config.longitude is not None else None,
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
return data
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def collect_catalyst_config(
|
|
88
|
+
config,
|
|
89
|
+
*,
|
|
90
|
+
settings=None,
|
|
91
|
+
include_unselected=False,
|
|
92
|
+
force_refresh=False,
|
|
93
|
+
refresh_missing=True,
|
|
94
|
+
):
|
|
95
|
+
"""Zwróć Catalyst Center DTO przez wspólny przepływ cache."""
|
|
96
|
+
settings = settings or CatalystCenterConfig.get()
|
|
97
|
+
data = catalyst_inventory_dto(config)
|
|
98
|
+
|
|
99
|
+
def collect():
|
|
100
|
+
"""Odpytaj connector wyłącznie przy jawnym odświeżeniu lub pustym cache."""
|
|
101
|
+
connector = CatalystCenterConnector(settings).connect()
|
|
102
|
+
return connector.collect_device(data)
|
|
103
|
+
|
|
104
|
+
return collect_cached_config(
|
|
105
|
+
config, data, settings, collect, include_unselected=include_unselected,
|
|
106
|
+
force_refresh=force_refresh, refresh_missing=refresh_missing,
|
|
107
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Wspólne zestawy wyborów dla modeli, formularzy, tabel i silnika synchronizacji."""
|
|
2
|
+
|
|
3
|
+
from utilities.choices import ChoiceSet
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MatchMethodChoices(ChoiceSet):
|
|
7
|
+
MANUAL = "manual"
|
|
8
|
+
CUSTOM_FIELD = "custom_field"
|
|
9
|
+
NAME = "name"
|
|
10
|
+
GPS = "gps"
|
|
11
|
+
PRIMARY_IP = "primary_ip"
|
|
12
|
+
UNMATCHED = "unmatched"
|
|
13
|
+
|
|
14
|
+
CHOICES = (
|
|
15
|
+
(MANUAL, "Manual", "blue"),
|
|
16
|
+
(CUSTOM_FIELD, "SdwanSiteId", "green"),
|
|
17
|
+
(NAME, "Site name", "cyan"),
|
|
18
|
+
(GPS, "GPS", "purple"),
|
|
19
|
+
(PRIMARY_IP, "Primary IP prefix", "indigo"),
|
|
20
|
+
(UNMATCHED, "Unmatched", "orange"),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SyncModeChoices(ChoiceSet):
|
|
25
|
+
COMPARE = "compare"
|
|
26
|
+
APPLY = "apply"
|
|
27
|
+
CHOICES = ((COMPARE, "Compare", "cyan"), (APPLY, "Apply", "green"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SyncStatusChoices(ChoiceSet):
|
|
31
|
+
OK = "ok"
|
|
32
|
+
FAILED = "failed"
|
|
33
|
+
SKIPPED = "skipped"
|
|
34
|
+
CHOICES = ((OK, "OK", "green"), (FAILED, "Failed", "red"), (SKIPPED, "Skipped", "gray"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SyncTriggerChoices(ChoiceSet):
|
|
38
|
+
MANUAL = "manual"
|
|
39
|
+
SCHEDULED = "scheduled"
|
|
40
|
+
CHOICES = ((MANUAL, "Manual", "blue"), (SCHEDULED, "Scheduled", "purple"))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Compatibility facade for the former vManage collector location."""
|
|
2
|
+
|
|
3
|
+
from .connectors.vmanage.collector import (
|
|
4
|
+
collect_details,
|
|
5
|
+
collect_inventory,
|
|
6
|
+
configured_device_types,
|
|
7
|
+
is_edge,
|
|
8
|
+
parse_device,
|
|
9
|
+
parse_interface,
|
|
10
|
+
parse_vrrp,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"collect_details",
|
|
15
|
+
"collect_inventory",
|
|
16
|
+
"configured_device_types",
|
|
17
|
+
"is_edge",
|
|
18
|
+
"parse_device",
|
|
19
|
+
"parse_interface",
|
|
20
|
+
"parse_vrrp",
|
|
21
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Wspólny kontrakt implementowany przez wszystkie źródła danych Cisco."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConnectorError(RuntimeError):
|
|
9
|
+
"""Kontrolowany błąd źródła, który można bezpiecznie pokazać w NetBox."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseConnector(ABC):
|
|
13
|
+
"""Minimalny kontrakt używany przez Discovery i usługi synchronizacji."""
|
|
14
|
+
|
|
15
|
+
source_type = ""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def connect(self):
|
|
19
|
+
"""Uwierzytelnij połączenie i zwróć gotowy connector."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def collect_inventory(self):
|
|
23
|
+
"""Zwróć lekkie, znormalizowane rekordy urządzeń do Discovery."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def collect_device(self, inventory_device):
|
|
27
|
+
"""Uzupełnij jeden rekord inventory o szczegółowe dane źródłowe."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def init_controller_client(client, settings):
|
|
31
|
+
"""Przygotuj sesję kontrolera bez ujawniania danych dostępowych w logach."""
|
|
32
|
+
client.base_url = settings.base_url.rstrip("/")
|
|
33
|
+
client.username = settings.username
|
|
34
|
+
client.password = settings.password
|
|
35
|
+
client.timeout = settings.request_timeout
|
|
36
|
+
client.retries = settings.request_retries
|
|
37
|
+
client.session = requests.Session()
|
|
38
|
+
client.session.verify = settings.ca_bundle or settings.verify_ssl
|
|
39
|
+
client.session.headers.update({"Accept": "application/json"})
|