osism 0.20250628.0__py3-none-any.whl → 0.20250709.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.
- osism/api.py +3 -3
- osism/commands/baremetal.py +37 -6
- osism/commands/manage.py +0 -251
- osism/commands/redfish.py +219 -0
- osism/commands/sonic.py +973 -0
- osism/settings.py +3 -0
- osism/tasks/__init__.py +2 -7
- osism/tasks/ansible.py +1 -3
- osism/tasks/conductor/__init__.py +7 -0
- osism/tasks/conductor/config.py +52 -35
- osism/tasks/conductor/ironic.py +96 -102
- osism/tasks/conductor/redfish.py +300 -0
- osism/tasks/conductor/sonic/config_generator.py +38 -14
- osism/tasks/conductor/utils.py +148 -0
- osism/tasks/netbox.py +3 -7
- osism/tasks/reconciler.py +3 -7
- osism/utils/__init__.py +28 -0
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/METADATA +4 -3
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/RECORD +25 -22
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/entry_points.txt +10 -1
- osism-0.20250709.0.dist-info/pbr.json +1 -0
- osism-0.20250628.0.dist-info/pbr.json +0 -1
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/WHEEL +0 -0
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/licenses/AUTHORS +0 -0
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/licenses/LICENSE +0 -0
- {osism-0.20250628.0.dist-info → osism-0.20250709.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,300 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
|
3
|
+
import json
|
4
|
+
from loguru import logger
|
5
|
+
from osism.tasks.conductor.utils import get_redfish_connection
|
6
|
+
|
7
|
+
|
8
|
+
def _normalize_redfish_data(data):
|
9
|
+
"""
|
10
|
+
Convert Redfish data values to strings and clean up None values.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
data (dict): Dictionary with Redfish resource data
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
dict: Dictionary with normalized string values, None values removed
|
17
|
+
"""
|
18
|
+
normalized_data = {}
|
19
|
+
|
20
|
+
for key, value in data.items():
|
21
|
+
if value is not None:
|
22
|
+
if isinstance(value, (dict, list)):
|
23
|
+
# Convert complex objects to JSON strings
|
24
|
+
normalized_data[key] = json.dumps(value)
|
25
|
+
elif isinstance(value, bool):
|
26
|
+
# Convert booleans to lowercase strings
|
27
|
+
normalized_data[key] = str(value).lower()
|
28
|
+
elif not isinstance(value, str):
|
29
|
+
# Convert numbers and other types to strings
|
30
|
+
normalized_data[key] = str(value)
|
31
|
+
else:
|
32
|
+
# Keep strings as-is
|
33
|
+
normalized_data[key] = value
|
34
|
+
|
35
|
+
return normalized_data
|
36
|
+
|
37
|
+
|
38
|
+
def get_resources(hostname, resource_type):
|
39
|
+
"""
|
40
|
+
Get Redfish resources for a specific hostname and resource type.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
hostname (str): The hostname of the target system
|
44
|
+
resource_type (str): The type of resource to retrieve (e.g., EthernetInterfaces)
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
list: Retrieved Redfish resources or empty list if failed
|
48
|
+
"""
|
49
|
+
logger.info(
|
50
|
+
f"Getting Redfish resources for hostname: {hostname}, resource_type: {resource_type}"
|
51
|
+
)
|
52
|
+
|
53
|
+
if resource_type == "EthernetInterfaces":
|
54
|
+
return _get_ethernet_interfaces(hostname)
|
55
|
+
elif resource_type == "NetworkAdapters":
|
56
|
+
return _get_network_adapters(hostname)
|
57
|
+
elif resource_type == "NetworkDeviceFunctions":
|
58
|
+
return _get_network_device_functions(hostname)
|
59
|
+
|
60
|
+
logger.warning(f"Resource type {resource_type} not supported yet")
|
61
|
+
return []
|
62
|
+
|
63
|
+
|
64
|
+
def _get_ethernet_interfaces(hostname):
|
65
|
+
"""
|
66
|
+
Get all EthernetInterfaces from a Redfish-enabled system.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
hostname (str): The hostname of the target system
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
list: List of EthernetInterface dictionaries
|
73
|
+
"""
|
74
|
+
try:
|
75
|
+
# Get Redfish connection using the utility function
|
76
|
+
redfish_conn = get_redfish_connection(hostname, ignore_ssl_errors=True)
|
77
|
+
|
78
|
+
if not redfish_conn:
|
79
|
+
logger.error(f"Could not establish Redfish connection to {hostname}")
|
80
|
+
return []
|
81
|
+
|
82
|
+
ethernet_interfaces = []
|
83
|
+
|
84
|
+
# Navigate through the Redfish service to find EthernetInterfaces
|
85
|
+
# Structure: /redfish/v1/Systems/{system_id}/EthernetInterfaces
|
86
|
+
for system in redfish_conn.get_system_collection().get_members():
|
87
|
+
logger.debug(f"Processing system: {system.identity}")
|
88
|
+
|
89
|
+
# Check if the system has EthernetInterfaces
|
90
|
+
if hasattr(system, "ethernet_interfaces") and system.ethernet_interfaces:
|
91
|
+
for interface in system.ethernet_interfaces.get_members():
|
92
|
+
try:
|
93
|
+
# Extract relevant information from each EthernetInterface
|
94
|
+
interface_data = {
|
95
|
+
"id": interface.identity,
|
96
|
+
"name": getattr(interface, "name", None),
|
97
|
+
"description": getattr(interface, "description", None),
|
98
|
+
"mac_address": getattr(interface, "mac_address", None),
|
99
|
+
"permanent_mac_address": getattr(
|
100
|
+
interface, "permanent_mac_address", None
|
101
|
+
),
|
102
|
+
"speed_mbps": getattr(interface, "speed_mbps", None),
|
103
|
+
"mtu_size": getattr(interface, "mtu_size", None),
|
104
|
+
"link_status": getattr(interface, "link_status", None),
|
105
|
+
"interface_enabled": getattr(
|
106
|
+
interface, "interface_enabled", None
|
107
|
+
),
|
108
|
+
}
|
109
|
+
|
110
|
+
# Normalize data values to strings and clean up None values
|
111
|
+
interface_data = _normalize_redfish_data(interface_data)
|
112
|
+
|
113
|
+
ethernet_interfaces.append(interface_data)
|
114
|
+
logger.debug(
|
115
|
+
f"Found EthernetInterface: {interface_data.get('name', interface_data.get('id'))}"
|
116
|
+
)
|
117
|
+
|
118
|
+
except Exception as exc:
|
119
|
+
logger.warning(
|
120
|
+
f"Error processing EthernetInterface {interface.identity}: {exc}"
|
121
|
+
)
|
122
|
+
continue
|
123
|
+
|
124
|
+
logger.info(
|
125
|
+
f"Retrieved {len(ethernet_interfaces)} EthernetInterfaces from {hostname}"
|
126
|
+
)
|
127
|
+
return ethernet_interfaces
|
128
|
+
|
129
|
+
except Exception as exc:
|
130
|
+
logger.error(f"Error retrieving EthernetInterfaces from {hostname}: {exc}")
|
131
|
+
return []
|
132
|
+
|
133
|
+
|
134
|
+
def _get_network_adapters(hostname):
|
135
|
+
"""
|
136
|
+
Get all NetworkAdapters from a Redfish-enabled system.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
hostname (str): The hostname of the target system
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
list: List of NetworkAdapter dictionaries
|
143
|
+
"""
|
144
|
+
try:
|
145
|
+
# Get Redfish connection using the utility function
|
146
|
+
redfish_conn = get_redfish_connection(hostname, ignore_ssl_errors=True)
|
147
|
+
|
148
|
+
if not redfish_conn:
|
149
|
+
logger.error(f"Could not establish Redfish connection to {hostname}")
|
150
|
+
return []
|
151
|
+
|
152
|
+
network_adapters = []
|
153
|
+
|
154
|
+
# Navigate through the Redfish service to find NetworkAdapters
|
155
|
+
# Structure: /redfish/v1/Chassis/{chassis_id}/NetworkAdapters
|
156
|
+
for chassis in redfish_conn.get_chassis_collection().get_members():
|
157
|
+
logger.debug(f"Processing chassis: {chassis.identity}")
|
158
|
+
|
159
|
+
# Check if the chassis has NetworkAdapters
|
160
|
+
if hasattr(chassis, "network_adapters") and chassis.network_adapters:
|
161
|
+
for adapter in chassis.network_adapters.get_members():
|
162
|
+
try:
|
163
|
+
# Extract relevant information from each NetworkAdapter
|
164
|
+
adapter_data = {
|
165
|
+
"id": adapter.identity,
|
166
|
+
"name": getattr(adapter, "name", None),
|
167
|
+
"description": getattr(adapter, "description", None),
|
168
|
+
"manufacturer": getattr(adapter, "manufacturer", None),
|
169
|
+
"model": getattr(adapter, "model", None),
|
170
|
+
"part_number": getattr(adapter, "part_number", None),
|
171
|
+
"serial_number": getattr(adapter, "serial_number", None),
|
172
|
+
"firmware_version": getattr(
|
173
|
+
adapter, "firmware_version", None
|
174
|
+
),
|
175
|
+
}
|
176
|
+
|
177
|
+
# Normalize data values to strings and clean up None values
|
178
|
+
adapter_data = _normalize_redfish_data(adapter_data)
|
179
|
+
|
180
|
+
network_adapters.append(adapter_data)
|
181
|
+
logger.debug(
|
182
|
+
f"Found NetworkAdapter: {adapter_data.get('name', adapter_data.get('id'))}"
|
183
|
+
)
|
184
|
+
|
185
|
+
except Exception as exc:
|
186
|
+
logger.warning(
|
187
|
+
f"Error processing NetworkAdapter {adapter.identity}: {exc}"
|
188
|
+
)
|
189
|
+
continue
|
190
|
+
|
191
|
+
logger.info(
|
192
|
+
f"Retrieved {len(network_adapters)} NetworkAdapters from {hostname}"
|
193
|
+
)
|
194
|
+
return network_adapters
|
195
|
+
|
196
|
+
except Exception as exc:
|
197
|
+
logger.error(f"Error retrieving NetworkAdapters from {hostname}: {exc}")
|
198
|
+
return []
|
199
|
+
|
200
|
+
|
201
|
+
def _get_network_device_functions(hostname):
|
202
|
+
"""
|
203
|
+
Get all NetworkDeviceFunctions from a Redfish-enabled system.
|
204
|
+
|
205
|
+
Args:
|
206
|
+
hostname (str): The hostname of the target system
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
list: List of NetworkDeviceFunction dictionaries with MAC addresses
|
210
|
+
"""
|
211
|
+
try:
|
212
|
+
# Get Redfish connection using the utility function
|
213
|
+
redfish_conn = get_redfish_connection(hostname, ignore_ssl_errors=True)
|
214
|
+
|
215
|
+
if not redfish_conn:
|
216
|
+
logger.error(f"Could not establish Redfish connection to {hostname}")
|
217
|
+
return []
|
218
|
+
|
219
|
+
network_device_functions = []
|
220
|
+
|
221
|
+
# Navigate through the Redfish service to find NetworkDeviceFunctions
|
222
|
+
# Structure: /redfish/v1/Chassis/{chassis_id}/NetworkAdapters/{adapter_id}/NetworkDeviceFunctions
|
223
|
+
for chassis in redfish_conn.get_chassis_collection().get_members():
|
224
|
+
logger.debug(f"Processing chassis: {chassis.identity}")
|
225
|
+
|
226
|
+
# Check if the chassis has NetworkAdapters
|
227
|
+
if hasattr(chassis, "network_adapters") and chassis.network_adapters:
|
228
|
+
for adapter in chassis.network_adapters.get_members():
|
229
|
+
logger.debug(f"Processing NetworkAdapter: {adapter.identity}")
|
230
|
+
|
231
|
+
try:
|
232
|
+
for (
|
233
|
+
device_func
|
234
|
+
) in adapter.network_device_functions.get_members():
|
235
|
+
try:
|
236
|
+
# Extract MAC address from Ethernet configuration
|
237
|
+
mac_address = None
|
238
|
+
permanent_mac_address = None
|
239
|
+
|
240
|
+
# Try to get MAC from ethernet configuration
|
241
|
+
if (
|
242
|
+
hasattr(device_func, "ethernet")
|
243
|
+
and device_func.ethernet
|
244
|
+
):
|
245
|
+
ethernet_config = device_func.ethernet
|
246
|
+
mac_address = getattr(
|
247
|
+
ethernet_config, "mac_address", None
|
248
|
+
)
|
249
|
+
permanent_mac_address = getattr(
|
250
|
+
ethernet_config, "permanent_mac_address", None
|
251
|
+
)
|
252
|
+
|
253
|
+
# Extract relevant information from each NetworkDeviceFunction
|
254
|
+
device_func_data = {
|
255
|
+
"id": device_func.identity,
|
256
|
+
"name": getattr(device_func, "name", None),
|
257
|
+
"description": getattr(
|
258
|
+
device_func, "description", None
|
259
|
+
),
|
260
|
+
"device_enabled": getattr(
|
261
|
+
device_func, "device_enabled", None
|
262
|
+
),
|
263
|
+
"ethernet_enabled": getattr(
|
264
|
+
device_func, "ethernet_enabled", None
|
265
|
+
),
|
266
|
+
"mac_address": mac_address,
|
267
|
+
"permanent_mac_address": permanent_mac_address,
|
268
|
+
"adapter_id": adapter.identity,
|
269
|
+
"adapter_name": getattr(adapter, "name", None),
|
270
|
+
}
|
271
|
+
|
272
|
+
# Normalize data values to strings and clean up None values
|
273
|
+
device_func_data = _normalize_redfish_data(
|
274
|
+
device_func_data
|
275
|
+
)
|
276
|
+
|
277
|
+
network_device_functions.append(device_func_data)
|
278
|
+
logger.debug(
|
279
|
+
f"Found NetworkDeviceFunction: {device_func_data.get('name', device_func_data.get('id'))}"
|
280
|
+
)
|
281
|
+
|
282
|
+
except Exception as exc:
|
283
|
+
logger.warning(
|
284
|
+
f"Error processing NetworkDeviceFunction {device_func.identity}: {exc}"
|
285
|
+
)
|
286
|
+
continue
|
287
|
+
except Exception as exc:
|
288
|
+
logger.warning(
|
289
|
+
f"Error processing NetworkAdapter {adapter.identity}: {exc}"
|
290
|
+
)
|
291
|
+
continue
|
292
|
+
|
293
|
+
logger.info(
|
294
|
+
f"Retrieved {len(network_device_functions)} NetworkDeviceFunctions from {hostname}"
|
295
|
+
)
|
296
|
+
return network_device_functions
|
297
|
+
|
298
|
+
except Exception as exc:
|
299
|
+
logger.error(f"Error retrieving NetworkDeviceFunctions from {hostname}: {exc}")
|
300
|
+
return []
|
@@ -201,6 +201,9 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None):
|
|
201
201
|
# Add NTP server configuration (device-specific)
|
202
202
|
_add_ntp_configuration(config, device)
|
203
203
|
|
204
|
+
# Add DNS server configuration (device-specific)
|
205
|
+
_add_dns_configuration(config, device)
|
206
|
+
|
204
207
|
# Add VLAN configuration
|
205
208
|
_add_vlan_configuration(config, vlan_info, netbox_interfaces, device)
|
206
209
|
|
@@ -694,18 +697,20 @@ def _determine_peer_type(local_device, connected_device, device_as_mapping=None)
|
|
694
697
|
return "external" # Default to external on error
|
695
698
|
|
696
699
|
|
697
|
-
def
|
698
|
-
"""Get
|
700
|
+
def _get_metalbox_ip_for_device(device):
|
701
|
+
"""Get Metalbox IP for a SONiC device based on OOB connection.
|
699
702
|
|
700
703
|
Returns the IP address of the metalbox device interface that is connected to the
|
701
704
|
OOB switch. If VLANs are used, returns the IP of the VLAN interface where the
|
702
705
|
SONiC switch management interface (eth0) has access.
|
703
706
|
|
707
|
+
This IP is used for both NTP and DNS services.
|
708
|
+
|
704
709
|
Args:
|
705
710
|
device: SONiC device object
|
706
711
|
|
707
712
|
Returns:
|
708
|
-
str: IP address of the
|
713
|
+
str: IP address of the Metalbox or None if not found
|
709
714
|
"""
|
710
715
|
try:
|
711
716
|
# Get the OOB IP configuration for this SONiC device
|
@@ -726,7 +731,7 @@ def _get_ntp_server_for_device(device):
|
|
726
731
|
metalbox_devices = utils.nb.dcim.devices.filter(role="metalbox")
|
727
732
|
|
728
733
|
for metalbox in metalbox_devices:
|
729
|
-
logger.debug(f"Checking metalbox device {metalbox.name} for
|
734
|
+
logger.debug(f"Checking metalbox device {metalbox.name} for services")
|
730
735
|
|
731
736
|
# Get all interfaces on this metalbox
|
732
737
|
interfaces = utils.nb.dcim.interfaces.filter(device_id=metalbox.id)
|
@@ -765,7 +770,7 @@ def _get_ntp_server_for_device(device):
|
|
765
770
|
else "interface"
|
766
771
|
)
|
767
772
|
logger.info(
|
768
|
-
f"Found
|
773
|
+
f"Found Metalbox {ip_only} on {metalbox.name} "
|
769
774
|
f"{interface_type} {interface.name} for SONiC device {device.name}"
|
770
775
|
)
|
771
776
|
return ip_only
|
@@ -773,11 +778,11 @@ def _get_ntp_server_for_device(device):
|
|
773
778
|
# Skip non-IPv4 addresses
|
774
779
|
continue
|
775
780
|
|
776
|
-
logger.warning(f"No suitable
|
781
|
+
logger.warning(f"No suitable Metalbox found for SONiC device {device.name}")
|
777
782
|
return None
|
778
783
|
|
779
784
|
except Exception as e:
|
780
|
-
logger.warning(f"Could not determine
|
785
|
+
logger.warning(f"Could not determine Metalbox IP for device {device.name}: {e}")
|
781
786
|
return None
|
782
787
|
|
783
788
|
|
@@ -846,19 +851,17 @@ def _add_ntp_configuration(config, device):
|
|
846
851
|
metalbox device interface connected to the OOB switch.
|
847
852
|
"""
|
848
853
|
try:
|
849
|
-
# Get the
|
850
|
-
|
854
|
+
# Get the Metalbox IP for this device
|
855
|
+
metalbox_ip = _get_metalbox_ip_for_device(device)
|
851
856
|
|
852
|
-
if
|
857
|
+
if metalbox_ip:
|
853
858
|
# Add single NTP server configuration
|
854
|
-
config["NTP_SERVER"][
|
859
|
+
config["NTP_SERVER"][metalbox_ip] = {
|
855
860
|
"maxpoll": "10",
|
856
861
|
"minpoll": "6",
|
857
862
|
"prefer": "false",
|
858
863
|
}
|
859
|
-
logger.info(
|
860
|
-
f"Added NTP server {ntp_server_ip} to SONiC device {device.name}"
|
861
|
-
)
|
864
|
+
logger.info(f"Added NTP server {metalbox_ip} to SONiC device {device.name}")
|
862
865
|
else:
|
863
866
|
logger.warning(f"No NTP server found for SONiC device {device.name}")
|
864
867
|
|
@@ -873,6 +876,27 @@ def clear_ntp_cache():
|
|
873
876
|
logger.debug("Cleared NTP servers cache")
|
874
877
|
|
875
878
|
|
879
|
+
def _add_dns_configuration(config, device):
|
880
|
+
"""Add DNS_NAMESERVER configuration to device config.
|
881
|
+
|
882
|
+
Each SONiC switch gets exactly one DNS server - the IP address of the
|
883
|
+
metalbox device interface connected to the OOB switch.
|
884
|
+
"""
|
885
|
+
try:
|
886
|
+
# Get the Metalbox IP for this device
|
887
|
+
metalbox_ip = _get_metalbox_ip_for_device(device)
|
888
|
+
|
889
|
+
if metalbox_ip:
|
890
|
+
# Add single DNS server configuration
|
891
|
+
config["DNS_NAMESERVER"][metalbox_ip] = {}
|
892
|
+
logger.info(f"Added DNS server {metalbox_ip} to SONiC device {device.name}")
|
893
|
+
else:
|
894
|
+
logger.warning(f"No DNS server found for SONiC device {device.name}")
|
895
|
+
|
896
|
+
except Exception as e:
|
897
|
+
logger.warning(f"Could not add DNS configuration to device {device.name}: {e}")
|
898
|
+
|
899
|
+
|
876
900
|
def clear_all_caches():
|
877
901
|
"""Clear all caches in config_generator module."""
|
878
902
|
clear_ntp_cache()
|
osism/tasks/conductor/utils.py
CHANGED
@@ -5,6 +5,8 @@ from ansible.parsing.vault import VaultLib, VaultSecret
|
|
5
5
|
from loguru import logger
|
6
6
|
|
7
7
|
from osism import utils
|
8
|
+
import sushy
|
9
|
+
import urllib3
|
8
10
|
|
9
11
|
|
10
12
|
def deep_compare(a, b, updates):
|
@@ -77,3 +79,149 @@ def get_vault():
|
|
77
79
|
logger.error("Unable to get vault secret. Dropping encrypted entries")
|
78
80
|
vault = VaultLib()
|
79
81
|
return vault
|
82
|
+
|
83
|
+
|
84
|
+
def get_redfish_connection(
|
85
|
+
hostname, username=None, password=None, ignore_ssl_errors=True, timeout=None
|
86
|
+
):
|
87
|
+
"""Create a Redfish connection to the specified hostname."""
|
88
|
+
from osism import settings
|
89
|
+
from osism.tasks import openstack
|
90
|
+
|
91
|
+
if not hostname:
|
92
|
+
return None
|
93
|
+
|
94
|
+
# Use configurable timeout if not provided
|
95
|
+
if timeout is None:
|
96
|
+
timeout = settings.REDFISH_TIMEOUT
|
97
|
+
|
98
|
+
# Get Redfish address from Ironic driver_info
|
99
|
+
base_url = f"https://{hostname}"
|
100
|
+
device = None
|
101
|
+
|
102
|
+
# Try to find NetBox device first for conductor configuration fallback
|
103
|
+
if utils.nb:
|
104
|
+
try:
|
105
|
+
# First try to find device by name
|
106
|
+
device = utils.nb.dcim.devices.get(name=hostname)
|
107
|
+
|
108
|
+
# If not found by name, try by inventory_hostname custom field
|
109
|
+
if not device:
|
110
|
+
devices = utils.nb.dcim.devices.filter(cf_inventory_hostname=hostname)
|
111
|
+
if devices:
|
112
|
+
device = devices[0]
|
113
|
+
except Exception as exc:
|
114
|
+
logger.warning(f"Could not resolve hostname {hostname} via NetBox: {exc}")
|
115
|
+
|
116
|
+
try:
|
117
|
+
ironic_node = openstack.baremetal_node_show(hostname, ignore_missing=True)
|
118
|
+
if ironic_node and "driver_info" in ironic_node:
|
119
|
+
driver_info = ironic_node["driver_info"]
|
120
|
+
# Use redfish_address from driver_info if available (contains full URL)
|
121
|
+
if "redfish_address" in driver_info:
|
122
|
+
base_url = driver_info["redfish_address"]
|
123
|
+
logger.info(f"Using Ironic redfish_address {base_url} for {hostname}")
|
124
|
+
else:
|
125
|
+
# Fallback to conductor configuration if Ironic driver_info not available
|
126
|
+
conductor_address = _get_conductor_redfish_address(device)
|
127
|
+
if conductor_address:
|
128
|
+
base_url = conductor_address
|
129
|
+
logger.info(
|
130
|
+
f"Using conductor redfish_address {base_url} for {hostname}"
|
131
|
+
)
|
132
|
+
except Exception as exc:
|
133
|
+
logger.warning(f"Could not get Ironic node for {hostname}: {exc}")
|
134
|
+
# Fallback to conductor configuration on Ironic error
|
135
|
+
conductor_address = _get_conductor_redfish_address(device)
|
136
|
+
if conductor_address:
|
137
|
+
base_url = conductor_address
|
138
|
+
logger.info(f"Using conductor redfish_address {base_url} for {hostname}")
|
139
|
+
|
140
|
+
# Get credentials from conductor configuration if not provided
|
141
|
+
if not username or not password:
|
142
|
+
conductor_username, conductor_password = _get_conductor_redfish_credentials(
|
143
|
+
device
|
144
|
+
)
|
145
|
+
if not username:
|
146
|
+
username = conductor_username
|
147
|
+
if not password:
|
148
|
+
password = conductor_password
|
149
|
+
|
150
|
+
auth = sushy.auth.SessionOrBasicAuth(username=username, password=password)
|
151
|
+
|
152
|
+
try:
|
153
|
+
if ignore_ssl_errors:
|
154
|
+
urllib3.disable_warnings()
|
155
|
+
conn = sushy.Sushy(base_url, auth=auth, verify=False)
|
156
|
+
else:
|
157
|
+
conn = sushy.Sushy(base_url, auth=auth)
|
158
|
+
|
159
|
+
return conn
|
160
|
+
except Exception as exc:
|
161
|
+
logger.error(
|
162
|
+
f"Unable to connect to Redfish API at {base_url} with timeout {timeout}s: {exc}"
|
163
|
+
)
|
164
|
+
return None
|
165
|
+
|
166
|
+
|
167
|
+
def _get_conductor_redfish_credentials(device):
|
168
|
+
"""Get Redfish credentials from conductor configuration and device secrets."""
|
169
|
+
from osism.tasks.conductor.config import get_configuration
|
170
|
+
from osism.tasks.conductor.ironic import _prepare_node_attributes
|
171
|
+
|
172
|
+
try:
|
173
|
+
if not device:
|
174
|
+
return None, None
|
175
|
+
|
176
|
+
# Use _prepare_node_attributes to get processed node attributes
|
177
|
+
def get_ironic_parameters():
|
178
|
+
configuration = get_configuration()
|
179
|
+
return configuration.get("ironic_parameters", {})
|
180
|
+
|
181
|
+
node_attributes = _prepare_node_attributes(device, get_ironic_parameters)
|
182
|
+
|
183
|
+
# Extract Redfish credentials if available
|
184
|
+
if (
|
185
|
+
"driver_info" in node_attributes
|
186
|
+
and node_attributes.get("driver") == "redfish"
|
187
|
+
):
|
188
|
+
driver_info = node_attributes["driver_info"]
|
189
|
+
username = driver_info.get("redfish_username")
|
190
|
+
password = driver_info.get("redfish_password")
|
191
|
+
return username, password
|
192
|
+
|
193
|
+
except Exception as exc:
|
194
|
+
logger.warning(f"Could not get conductor Redfish credentials: {exc}")
|
195
|
+
|
196
|
+
return None, None
|
197
|
+
|
198
|
+
|
199
|
+
def _get_conductor_redfish_address(device):
|
200
|
+
"""Get Redfish address from conductor configuration and device OOB IP."""
|
201
|
+
from osism.tasks.conductor.config import get_configuration
|
202
|
+
from osism.tasks.conductor.ironic import _prepare_node_attributes
|
203
|
+
|
204
|
+
try:
|
205
|
+
if not device:
|
206
|
+
return None
|
207
|
+
|
208
|
+
# Use _prepare_node_attributes to get processed node attributes
|
209
|
+
def get_ironic_parameters():
|
210
|
+
configuration = get_configuration()
|
211
|
+
return configuration.get("ironic_parameters", {})
|
212
|
+
|
213
|
+
node_attributes = _prepare_node_attributes(device, get_ironic_parameters)
|
214
|
+
|
215
|
+
# Extract Redfish address if available
|
216
|
+
if (
|
217
|
+
"driver_info" in node_attributes
|
218
|
+
and node_attributes.get("driver") == "redfish"
|
219
|
+
):
|
220
|
+
driver_info = node_attributes["driver_info"]
|
221
|
+
address = driver_info.get("redfish_address")
|
222
|
+
return address
|
223
|
+
|
224
|
+
except Exception as exc:
|
225
|
+
logger.warning(f"Could not get conductor Redfish address: {exc}")
|
226
|
+
|
227
|
+
return None
|
osism/tasks/netbox.py
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
from celery import Celery
|
4
4
|
from loguru import logger
|
5
|
-
from pottery import Redlock
|
6
5
|
|
7
6
|
from osism import settings, utils
|
8
7
|
from osism.tasks import Config, run_command
|
@@ -30,9 +29,8 @@ def run(self, action, arguments):
|
|
30
29
|
def set_maintenance(self, device_name, state=True):
|
31
30
|
"""Set the maintenance state for a device in the NetBox."""
|
32
31
|
|
33
|
-
lock =
|
32
|
+
lock = utils.create_redlock(
|
34
33
|
key=f"lock_osism_tasks_netbox_set_maintenance_{device_name}",
|
35
|
-
masters={utils.redis},
|
36
34
|
auto_release_time=60,
|
37
35
|
)
|
38
36
|
if lock.acquire(timeout=20):
|
@@ -59,9 +57,8 @@ def set_maintenance(self, device_name, state=True):
|
|
59
57
|
def set_provision_state(self, device_name, state):
|
60
58
|
"""Set the provision state for a device in the NetBox."""
|
61
59
|
|
62
|
-
lock =
|
60
|
+
lock = utils.create_redlock(
|
63
61
|
key=f"lock_osism_tasks_netbox_set_provision_state_{device_name}",
|
64
|
-
masters={utils.redis},
|
65
62
|
auto_release_time=60,
|
66
63
|
)
|
67
64
|
if lock.acquire(timeout=20):
|
@@ -89,9 +86,8 @@ def set_provision_state(self, device_name, state):
|
|
89
86
|
def set_power_state(self, device_name, state):
|
90
87
|
"""Set the provision state for a device in the NetBox."""
|
91
88
|
|
92
|
-
lock =
|
89
|
+
lock = utils.create_redlock(
|
93
90
|
key=f"lock_osism_tasks_netbox_set_provision_state_{device_name}",
|
94
|
-
masters={utils.redis},
|
95
91
|
auto_release_time=60,
|
96
92
|
)
|
97
93
|
if lock.acquire(timeout=20):
|
osism/tasks/reconciler.py
CHANGED
@@ -6,7 +6,6 @@ import subprocess
|
|
6
6
|
|
7
7
|
from celery import Celery
|
8
8
|
from loguru import logger
|
9
|
-
from pottery import Redlock
|
10
9
|
|
11
10
|
from osism import settings, utils
|
12
11
|
from osism.tasks import Config
|
@@ -17,9 +16,8 @@ app.config_from_object(Config)
|
|
17
16
|
|
18
17
|
@app.on_after_configure.connect
|
19
18
|
def setup_periodic_tasks(sender, **kwargs):
|
20
|
-
lock =
|
19
|
+
lock = utils.create_redlock(
|
21
20
|
key="lock_osism_tasks_reconciler_setup_periodic_tasks",
|
22
|
-
masters={utils.redis},
|
23
21
|
)
|
24
22
|
if settings.INVENTORY_RECONCILER_SCHEDULE > 0 and lock.acquire(timeout=10):
|
25
23
|
sender.add_periodic_task(
|
@@ -29,9 +27,8 @@ def setup_periodic_tasks(sender, **kwargs):
|
|
29
27
|
|
30
28
|
@app.task(bind=True, name="osism.tasks.reconciler.run")
|
31
29
|
def run(self, publish=True, flush_cache=False):
|
32
|
-
lock =
|
30
|
+
lock = utils.create_redlock(
|
33
31
|
key="lock_osism_tasks_reconciler_run",
|
34
|
-
masters={utils.redis},
|
35
32
|
auto_release_time=60,
|
36
33
|
)
|
37
34
|
|
@@ -64,9 +61,8 @@ def run(self, publish=True, flush_cache=False):
|
|
64
61
|
|
65
62
|
@app.task(bind=True, name="osism.tasks.reconciler.run_on_change")
|
66
63
|
def run_on_change(self):
|
67
|
-
lock =
|
64
|
+
lock = utils.create_redlock(
|
68
65
|
key="lock_osism_tasks_reconciler_run_on_change",
|
69
|
-
masters={utils.redis},
|
70
66
|
auto_release_time=60,
|
71
67
|
)
|
72
68
|
|