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.
Files changed (91) hide show
  1. netbox_cisco_sync/__init__.py +86 -0
  2. netbox_cisco_sync/api/__init__.py +3 -0
  3. netbox_cisco_sync/api/client.py +5 -0
  4. netbox_cisco_sync/api/serializers.py +164 -0
  5. netbox_cisco_sync/api/urls.py +26 -0
  6. netbox_cisco_sync/api/views.py +71 -0
  7. netbox_cisco_sync/catalyst_services.py +107 -0
  8. netbox_cisco_sync/choices.py +40 -0
  9. netbox_cisco_sync/collector.py +21 -0
  10. netbox_cisco_sync/connectors/__init__.py +5 -0
  11. netbox_cisco_sync/connectors/base.py +39 -0
  12. netbox_cisco_sync/connectors/catalyst_center/__init__.py +6 -0
  13. netbox_cisco_sync/connectors/catalyst_center/client.py +157 -0
  14. netbox_cisco_sync/connectors/catalyst_center/collector.py +440 -0
  15. netbox_cisco_sync/connectors/catalyst_center/connector.py +76 -0
  16. netbox_cisco_sync/connectors/meraki/__init__.py +4 -0
  17. netbox_cisco_sync/connectors/meraki/client.py +91 -0
  18. netbox_cisco_sync/connectors/meraki/collector.py +345 -0
  19. netbox_cisco_sync/connectors/meraki/connector.py +45 -0
  20. netbox_cisco_sync/connectors/vmanage/__init__.py +6 -0
  21. netbox_cisco_sync/connectors/vmanage/client.py +76 -0
  22. netbox_cisco_sync/connectors/vmanage/collector.py +129 -0
  23. netbox_cisco_sync/connectors/vmanage/connector.py +32 -0
  24. netbox_cisco_sync/device_type_interfaces.py +15 -0
  25. netbox_cisco_sync/dto.py +85 -0
  26. netbox_cisco_sync/engine.py +9 -0
  27. netbox_cisco_sync/filtersets.py +61 -0
  28. netbox_cisco_sync/forms.py +222 -0
  29. netbox_cisco_sync/interface_cache.py +225 -0
  30. netbox_cisco_sync/jobs.py +461 -0
  31. netbox_cisco_sync/matcher.py +128 -0
  32. netbox_cisco_sync/meraki_services.py +96 -0
  33. netbox_cisco_sync/migrations/0001_initial.py +336 -0
  34. netbox_cisco_sync/migrations/0002_catalyst_detailed_stacks.py +80 -0
  35. netbox_cisco_sync/migrations/0003_catalyst_interface_cache_mode.py +16 -0
  36. netbox_cisco_sync/migrations/0004_discovered_interface_bridge.py +16 -0
  37. netbox_cisco_sync/migrations/0005_discovered_interface_label.py +16 -0
  38. netbox_cisco_sync/migrations/0006_remove_catalyst_detailed_stacks.py +26 -0
  39. netbox_cisco_sync/migrations/0007_catalyst_stack_metadata.py +44 -0
  40. netbox_cisco_sync/migrations/0008_cisco_sync_custom_fields.py +83 -0
  41. netbox_cisco_sync/migrations/0009_ignore_rfc1918_wan_ips.py +32 -0
  42. netbox_cisco_sync/migrations/0010_discovered_interface_lag.py +13 -0
  43. netbox_cisco_sync/migrations/__init__.py +0 -0
  44. netbox_cisco_sync/models.py +640 -0
  45. netbox_cisco_sync/navigation.py +23 -0
  46. netbox_cisco_sync/plugin_settings.py +43 -0
  47. netbox_cisco_sync/request_limits.py +77 -0
  48. netbox_cisco_sync/services.py +109 -0
  49. netbox_cisco_sync/sync/__init__.py +6 -0
  50. netbox_cisco_sync/sync/addresses.py +175 -0
  51. netbox_cisco_sync/sync/device_custom_fields.py +75 -0
  52. netbox_cisco_sync/sync/fhrp.py +108 -0
  53. netbox_cisco_sync/sync/interfaces.py +335 -0
  54. netbox_cisco_sync/sync/orchestrator.py +238 -0
  55. netbox_cisco_sync/sync/result.py +59 -0
  56. netbox_cisco_sync/sync/sites.py +110 -0
  57. netbox_cisco_sync/sync/tags.py +31 -0
  58. netbox_cisco_sync/sync/vlans.py +31 -0
  59. netbox_cisco_sync/sync/wan.py +53 -0
  60. netbox_cisco_sync/tables.py +142 -0
  61. netbox_cisco_sync/templates/netbox_cisco_sync/bulk_sync_confirm.html +42 -0
  62. netbox_cisco_sync/templates/netbox_cisco_sync/buttons/bulk_apply_selected.html +8 -0
  63. netbox_cisco_sync/templates/netbox_cisco_sync/buttons/bulk_enable_selected.html +8 -0
  64. netbox_cisco_sync/templates/netbox_cisco_sync/buttons/bulk_refresh_interfaces.html +8 -0
  65. netbox_cisco_sync/templates/netbox_cisco_sync/device_interface_loading.html +39 -0
  66. netbox_cisco_sync/templates/netbox_cisco_sync/device_sync_edit.html +199 -0
  67. netbox_cisco_sync/templates/netbox_cisco_sync/devicecatalystcenterconfig_list.html +52 -0
  68. netbox_cisco_sync/templates/netbox_cisco_sync/devicemerakiconfig_list.html +29 -0
  69. netbox_cisco_sync/templates/netbox_cisco_sync/devicevmanageconfig_list.html +29 -0
  70. netbox_cisco_sync/templates/netbox_cisco_sync/inc/discovered_device_tabs.html +56 -0
  71. netbox_cisco_sync/templates/netbox_cisco_sync/inc/discovery_overlay.html +23 -0
  72. netbox_cisco_sync/templates/netbox_cisco_sync/inc/interface_refresh_overlay.html +16 -0
  73. netbox_cisco_sync/templates/netbox_cisco_sync/inc/preserve_device_selection.html +15 -0
  74. netbox_cisco_sync/templates/netbox_cisco_sync/inc/settings_source_tabs.html +17 -0
  75. netbox_cisco_sync/templates/netbox_cisco_sync/settings.html +36 -0
  76. netbox_cisco_sync/templates/netbox_cisco_sync/syncrun.html +37 -0
  77. netbox_cisco_sync/templates/netbox_cisco_sync/syncrun_revert.html +3 -0
  78. netbox_cisco_sync/urls.py +28 -0
  79. netbox_cisco_sync/view_modules/__init__.py +1 -0
  80. netbox_cisco_sync/view_modules/catalyst_center.py +250 -0
  81. netbox_cisco_sync/view_modules/common.py +574 -0
  82. netbox_cisco_sync/view_modules/meraki.py +195 -0
  83. netbox_cisco_sync/view_modules/settings.py +94 -0
  84. netbox_cisco_sync/view_modules/sync_runs.py +60 -0
  85. netbox_cisco_sync/view_modules/vmanage.py +211 -0
  86. netbox_cisco_sync/views.py +67 -0
  87. netbox_cisco_sync-1.0.1.dist-info/METADATA +417 -0
  88. netbox_cisco_sync-1.0.1.dist-info/RECORD +91 -0
  89. netbox_cisco_sync-1.0.1.dist-info/WHEEL +5 -0
  90. netbox_cisco_sync-1.0.1.dist-info/licenses/LICENSE +21 -0
  91. 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,3 @@
1
+ from ..connectors.vmanage import VmanageClient, VmanageError
2
+
3
+ __all__ = ("VmanageClient", "VmanageError")
@@ -0,0 +1,5 @@
1
+ """Compatibility exports for the former vManage API client location."""
2
+
3
+ from ..connectors.vmanage.client import VmanageClient, VmanageError
4
+
5
+ __all__ = ("VmanageClient", "VmanageError")
@@ -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,5 @@
1
+ """Platform-specific data sources exposed through a common connector contract."""
2
+
3
+ from .base import BaseConnector, ConnectorError
4
+
5
+ __all__ = ("BaseConnector", "ConnectorError")
@@ -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"})
@@ -0,0 +1,6 @@
1
+ """Cisco Catalyst Center connector."""
2
+
3
+ from .client import CatalystCenterClient, CatalystCenterError
4
+ from .connector import CatalystCenterConnector
5
+
6
+ __all__ = ("CatalystCenterClient", "CatalystCenterConnector", "CatalystCenterError")