osism 0.20250602.0__py3-none-any.whl → 0.20250616.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,311 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ from loguru import logger
4
+ import yaml
5
+
6
+ from osism import settings, utils
7
+ from osism.tasks import netbox
8
+
9
+
10
+ def get_nb_device_query_list_ironic():
11
+ try:
12
+ supported_nb_device_filters = [
13
+ "site",
14
+ "region",
15
+ "site_group",
16
+ "location",
17
+ "rack",
18
+ "tag",
19
+ "state",
20
+ ]
21
+ nb_device_query_list = yaml.safe_load(settings.NETBOX_FILTER_CONDUCTOR_IRONIC)
22
+ if type(nb_device_query_list) is not list:
23
+ raise TypeError
24
+ for nb_device_query in nb_device_query_list:
25
+ if type(nb_device_query) is not dict:
26
+ raise TypeError
27
+ for key in list(nb_device_query.keys()):
28
+ if key not in supported_nb_device_filters:
29
+ raise ValueError
30
+ # NOTE: Only "location_id" and "rack_id" are supported by NetBox
31
+ if key in ["location", "rack"]:
32
+ value_name = nb_device_query.pop(key, "")
33
+ if key == "location":
34
+ value_id = netbox.get_location_id(value_name)
35
+ elif key == "rack":
36
+ value_id = netbox.get_rack_id(value_name)
37
+ if value_id:
38
+ nb_device_query.update({key + "_id": value_id})
39
+ else:
40
+ raise ValueError(f"Invalid name {value_name} for {key}")
41
+ except (yaml.YAMLError, TypeError):
42
+ logger.error(
43
+ f"Setting NETBOX_FILTER_CONDUCTOR_IRONIC needs to be an array of mappings containing supported NetBox device filters: {supported_nb_device_filters}"
44
+ )
45
+ nb_device_query_list = []
46
+ except ValueError as exc:
47
+ logger.error(f"Unknown value in NETBOX_FILTER_CONDUCTOR_IRONIC: {exc}")
48
+ nb_device_query_list = []
49
+
50
+ return nb_device_query_list
51
+
52
+
53
+ def get_nb_device_query_list_sonic():
54
+ try:
55
+ supported_nb_device_filters = [
56
+ "site",
57
+ "region",
58
+ "site_group",
59
+ "location",
60
+ "rack",
61
+ "tag",
62
+ "state",
63
+ ]
64
+ nb_device_query_list = yaml.safe_load(settings.NETBOX_FILTER_CONDUCTOR_SONIC)
65
+ if type(nb_device_query_list) is not list:
66
+ raise TypeError
67
+ for nb_device_query in nb_device_query_list:
68
+ if type(nb_device_query) is not dict:
69
+ raise TypeError
70
+ for key in list(nb_device_query.keys()):
71
+ if key not in supported_nb_device_filters:
72
+ raise ValueError
73
+ # NOTE: Only "location_id" and "rack_id" are supported by NetBox
74
+ if key in ["location", "rack"]:
75
+ value_name = nb_device_query.pop(key, "")
76
+ if key == "location":
77
+ value_id = netbox.get_location_id(value_name)
78
+ elif key == "rack":
79
+ value_id = netbox.get_rack_id(value_name)
80
+ if value_id:
81
+ nb_device_query.update({key + "_id": value_id})
82
+ else:
83
+ raise ValueError(f"Invalid name {value_name} for {key}")
84
+ except (yaml.YAMLError, TypeError):
85
+ logger.error(
86
+ f"Setting NETBOX_FILTER_CONDUCTOR_SONIC needs to be an array of mappings containing supported NetBox device filters: {supported_nb_device_filters}"
87
+ )
88
+ nb_device_query_list = []
89
+ except ValueError as exc:
90
+ logger.error(f"Unknown value in NETBOX_FILTER_CONDUCTOR_SONIC: {exc}")
91
+ nb_device_query_list = []
92
+
93
+ return nb_device_query_list
94
+
95
+
96
+ def get_device_oob_ip(device):
97
+ """Get out-of-band IP address for device management interface.
98
+
99
+ Args:
100
+ device: NetBox device object
101
+
102
+ Returns:
103
+ tuple: (IP address, prefix length) for management interface or None
104
+ Example: ('192.168.1.10', 24)
105
+ """
106
+ import ipaddress
107
+
108
+ try:
109
+ oob_ip_with_prefix = None
110
+
111
+ # First check if device has oob_ip field set
112
+ if hasattr(device, "oob_ip") and device.oob_ip:
113
+ oob_ip_with_prefix = device.oob_ip
114
+ else:
115
+ # Fall back to management interfaces
116
+ interfaces = utils.nb.dcim.interfaces.filter(device_id=device.id)
117
+
118
+ for interface in interfaces:
119
+ if interface.mgmt_only:
120
+ # Get IP addresses assigned to this interface
121
+ ip_addresses = utils.nb.ipam.ip_addresses.filter(
122
+ assigned_object_id=interface.id,
123
+ )
124
+
125
+ for ip_addr in ip_addresses:
126
+ if ip_addr.address:
127
+ oob_ip_with_prefix = ip_addr.address
128
+ break
129
+ if oob_ip_with_prefix:
130
+ break
131
+
132
+ if oob_ip_with_prefix:
133
+ # Parse the IP address with prefix (e.g., "192.168.1.10/24")
134
+ ip_interface = ipaddress.ip_interface(oob_ip_with_prefix)
135
+ ip_address = str(ip_interface.ip)
136
+ prefix_length = ip_interface.network.prefixlen
137
+
138
+ logger.debug(
139
+ f"Found OOB IP for device {device.name}: {ip_address}/{prefix_length}"
140
+ )
141
+
142
+ # Return tuple of (IP address, prefix length)
143
+ return (ip_address, prefix_length)
144
+
145
+ except Exception as e:
146
+ logger.warning(f"Could not get OOB IP for device {device.name}: {e}")
147
+
148
+ return None
149
+
150
+
151
+ def get_device_vlans(device):
152
+ """Get VLANs configured on device interfaces.
153
+
154
+ Args:
155
+ device: NetBox device object
156
+
157
+ Returns:
158
+ dict: Dictionary with VLAN information
159
+ {
160
+ 'vlans': {vid: {'name': name, 'description': desc}},
161
+ 'vlan_members': {vid: {'port_name': 'tagging_mode'}},
162
+ 'vlan_interfaces': {vid: {'addresses': [ip_with_prefix, ...]}}
163
+ }
164
+ """
165
+ vlans = {}
166
+ vlan_members = {}
167
+ vlan_interfaces = {}
168
+
169
+ try:
170
+ # Get all interfaces for the device and convert to list for multiple iterations
171
+ interfaces = list(utils.nb.dcim.interfaces.filter(device_id=device.id))
172
+
173
+ for interface in interfaces:
174
+ # Skip management interfaces and virtual interfaces
175
+ if interface.mgmt_only or (
176
+ hasattr(interface, "type")
177
+ and interface.type
178
+ and interface.type.value == "virtual"
179
+ ):
180
+ continue
181
+
182
+ # Process untagged VLAN
183
+ if hasattr(interface, "untagged_vlan") and interface.untagged_vlan:
184
+ vlan = interface.untagged_vlan
185
+ vid = vlan.vid
186
+
187
+ # Add VLAN info if not already present
188
+ if vid not in vlans:
189
+ vlans[vid] = {
190
+ "name": vlan.name or f"Vlan{vid}",
191
+ "description": vlan.description or "",
192
+ }
193
+
194
+ # Add interface to VLAN members as untagged
195
+ if vid not in vlan_members:
196
+ vlan_members[vid] = {}
197
+
198
+ # Use original NetBox interface name - conversion will be done in sonic.py
199
+ vlan_members[vid][interface.name] = "untagged"
200
+
201
+ # Process tagged VLANs
202
+ if hasattr(interface, "tagged_vlans") and interface.tagged_vlans:
203
+ for vlan in interface.tagged_vlans:
204
+ vid = vlan.vid
205
+
206
+ # Add VLAN info if not already present
207
+ if vid not in vlans:
208
+ vlans[vid] = {
209
+ "name": vlan.name or f"Vlan{vid}",
210
+ "description": vlan.description or "",
211
+ }
212
+
213
+ # Add interface to VLAN members as tagged
214
+ if vid not in vlan_members:
215
+ vlan_members[vid] = {}
216
+
217
+ # Use original NetBox interface name - conversion will be done in sonic.py
218
+ vlan_members[vid][interface.name] = "tagged"
219
+
220
+ # Get VLAN interfaces (SVIs) - virtual interfaces with VLAN assignments
221
+ for interface in interfaces:
222
+ # Check if interface is virtual type and has VLAN assignment
223
+ if (
224
+ hasattr(interface, "type")
225
+ and interface.type
226
+ and interface.type.value == "virtual"
227
+ and interface.name.startswith("Vlan")
228
+ ):
229
+ try:
230
+ vid = int(interface.name[4:])
231
+ # Get IP addresses for this VLAN interface
232
+ ip_addresses = utils.nb.ipam.ip_addresses.filter(
233
+ assigned_object_id=interface.id,
234
+ )
235
+
236
+ addresses = []
237
+ for ip_addr in ip_addresses:
238
+ if ip_addr.address:
239
+ addresses.append(ip_addr.address)
240
+
241
+ if addresses:
242
+ if vid not in vlan_interfaces:
243
+ vlan_interfaces[vid] = {}
244
+ # Store all IP addresses for this VLAN interface
245
+ vlan_interfaces[vid]["addresses"] = addresses
246
+ except (ValueError, IndexError):
247
+ # Skip if interface name doesn't follow Vlan<number> pattern
248
+ pass
249
+
250
+ except Exception as e:
251
+ logger.warning(f"Could not get VLANs for device {device.name}: {e}")
252
+
253
+ return {
254
+ "vlans": vlans,
255
+ "vlan_members": vlan_members,
256
+ "vlan_interfaces": vlan_interfaces,
257
+ }
258
+
259
+
260
+ def get_device_loopbacks(device):
261
+ """Get Loopback interfaces configured on device.
262
+
263
+ Args:
264
+ device: NetBox device object
265
+
266
+ Returns:
267
+ dict: Dictionary with Loopback information
268
+ {
269
+ 'loopbacks': {'Loopback0': {'addresses': [ip_with_prefix, ...]}}
270
+ }
271
+ """
272
+ loopbacks = {}
273
+
274
+ try:
275
+ # Get all interfaces for the device
276
+ interfaces = list(utils.nb.dcim.interfaces.filter(device_id=device.id))
277
+
278
+ for interface in interfaces:
279
+ # Check if interface is virtual type and is a Loopback interface
280
+ if (
281
+ hasattr(interface, "type")
282
+ and interface.type
283
+ and interface.type.value == "virtual"
284
+ and interface.name.startswith("Loopback")
285
+ ):
286
+
287
+ try:
288
+ # Get IP addresses for this Loopback interface
289
+ ip_addresses = utils.nb.ipam.ip_addresses.filter(
290
+ assigned_object_id=interface.id,
291
+ )
292
+
293
+ addresses = []
294
+ for ip_addr in ip_addresses:
295
+ if ip_addr.address:
296
+ addresses.append(ip_addr.address)
297
+
298
+ if addresses:
299
+ loopbacks[interface.name] = {"addresses": addresses}
300
+
301
+ except Exception as e:
302
+ logger.debug(
303
+ f"Error processing Loopback interface {interface.name}: {e}"
304
+ )
305
+
306
+ except Exception as e:
307
+ logger.warning(
308
+ f"Could not get Loopback interfaces for device {device.name}: {e}"
309
+ )
310
+
311
+ return {"loopbacks": loopbacks}