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/settings.py CHANGED
@@ -54,3 +54,6 @@ SONIC_EXPORT_IDENTIFIER = os.getenv("SONIC_EXPORT_IDENTIFIER", "serial-number")
54
54
  NETBOX_SECONDARIES = (
55
55
  os.getenv("NETBOX_SECONDARIES", read_secret("NETBOX_SECONDARIES")) or "[]"
56
56
  )
57
+
58
+ # Redfish connection timeout in seconds
59
+ REDFISH_TIMEOUT = int(os.getenv("REDFISH_TIMEOUT", "20"))
osism/tasks/__init__.py CHANGED
@@ -1,12 +1,10 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
 
3
- import logging
4
3
  import os
5
4
  import re
6
5
  import subprocess
7
6
 
8
7
  from loguru import logger
9
- from pottery import Redlock
10
8
 
11
9
  from osism import utils
12
10
 
@@ -77,10 +75,8 @@ def run_ansible_in_environment(
77
75
 
78
76
  # NOTE: Consider arguments in the future
79
77
  if locking:
80
- logging.getLogger("redlock").setLevel(logging.WARNING)
81
- lock = Redlock(
78
+ lock = utils.create_redlock(
82
79
  key=f"lock-ansible-{environment}-{role}",
83
- masters={utils.redis},
84
80
  auto_release_time=auto_release_time,
85
81
  )
86
82
 
@@ -195,9 +191,8 @@ def run_command(
195
191
  command_env.update(env)
196
192
 
197
193
  if locking:
198
- lock = Redlock(
194
+ lock = utils.create_redlock(
199
195
  key=f"lock-{command}",
200
- masters={utils.redis},
201
196
  auto_release_time=auto_release_time,
202
197
  )
203
198
 
osism/tasks/ansible.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
 
3
3
  from celery import Celery
4
- from pottery import Redlock
5
4
 
6
5
  from osism import settings, utils
7
6
  from osism.tasks import Config, run_ansible_in_environment
@@ -12,9 +11,8 @@ app.config_from_object(Config)
12
11
 
13
12
  @app.on_after_configure.connect
14
13
  def setup_periodic_tasks(sender, **kwargs):
15
- lock = Redlock(
14
+ lock = utils.create_redlock(
16
15
  key="lock_osism_tasks_ansible_setup_periodic_tasks",
17
- masters={utils.redis},
18
16
  )
19
17
  if settings.GATHER_FACTS_SCHEDULE > 0 and lock.acquire(timeout=10):
20
18
  sender.add_periodic_task(
@@ -8,6 +8,7 @@ from loguru import logger
8
8
  from osism.tasks import Config
9
9
  from osism.tasks.conductor.config import get_configuration
10
10
  from osism.tasks.conductor.ironic import sync_ironic as _sync_ironic
11
+ from osism.tasks.conductor.redfish import get_resources as _get_redfish_resources
11
12
  from osism.tasks.conductor.sonic import sync_sonic as _sync_sonic
12
13
 
13
14
 
@@ -52,9 +53,15 @@ def sync_sonic(self, device_name=None, show_diff=True):
52
53
  return _sync_sonic(device_name, self.request.id, show_diff)
53
54
 
54
55
 
56
+ @app.task(bind=True, name="osism.tasks.conductor.get_redfish_resources")
57
+ def get_redfish_resources(self, hostname, resource_type):
58
+ return _get_redfish_resources(hostname, resource_type)
59
+
60
+
55
61
  __all__ = [
56
62
  "app",
57
63
  "get_ironic_parameters",
64
+ "get_redfish_resources",
58
65
  "sync_netbox",
59
66
  "sync_ironic",
60
67
  "sync_sonic",
@@ -1,22 +1,12 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
 
3
- import uuid
4
-
5
3
  from loguru import logger
4
+ import validators
6
5
  import yaml
7
6
 
8
7
  from osism.tasks import Config, openstack
9
8
 
10
9
 
11
- def is_uuid(value):
12
- """Check if a string is a valid UUID."""
13
- try:
14
- uuid.UUID(value)
15
- return True
16
- except (ValueError, AttributeError):
17
- return False
18
-
19
-
20
10
  def get_configuration():
21
11
  with open("/etc/conductor.yml") as fp:
22
12
  configuration = yaml.load(fp, Loader=yaml.SafeLoader)
@@ -39,54 +29,81 @@ def get_configuration():
39
29
  image_source = configuration["ironic_parameters"]["instance_info"][
40
30
  "image_source"
41
31
  ]
42
- if not is_uuid(image_source):
32
+ if not validators.uuid(image_source) and not validators.url(
33
+ image_source
34
+ ):
43
35
  result = openstack.image_get(image_source)
44
- configuration["ironic_parameters"]["instance_info"][
45
- "image_source"
46
- ] = result.id
36
+ if result:
37
+ configuration["ironic_parameters"]["instance_info"][
38
+ "image_source"
39
+ ] = result.id
40
+ else:
41
+ logger.warning(f"Could not resolve image ID for {image_source}")
47
42
 
48
43
  if "driver_info" in configuration["ironic_parameters"]:
49
44
  if "deploy_kernel" in configuration["ironic_parameters"]["driver_info"]:
50
45
  deploy_kernel = configuration["ironic_parameters"]["driver_info"][
51
46
  "deploy_kernel"
52
47
  ]
53
- if not is_uuid(deploy_kernel):
48
+ if not validators.uuid(deploy_kernel) and not validators.url(
49
+ deploy_kernel
50
+ ):
54
51
  result = openstack.image_get(deploy_kernel)
55
- configuration["ironic_parameters"]["driver_info"][
56
- "deploy_kernel"
57
- ] = result.id
52
+ if result:
53
+ configuration["ironic_parameters"]["driver_info"][
54
+ "deploy_kernel"
55
+ ] = result.id
56
+ else:
57
+ logger.warning(
58
+ f"Could not resolve image ID for {deploy_kernel}"
59
+ )
58
60
 
59
61
  if "deploy_ramdisk" in configuration["ironic_parameters"]["driver_info"]:
60
62
  deploy_ramdisk = configuration["ironic_parameters"]["driver_info"][
61
63
  "deploy_ramdisk"
62
64
  ]
63
- if not is_uuid(deploy_ramdisk):
65
+ if not validators.uuid(deploy_ramdisk) and not validators.url(
66
+ deploy_ramdisk
67
+ ):
64
68
  result = openstack.image_get(deploy_ramdisk)
65
- configuration["ironic_parameters"]["driver_info"][
66
- "deploy_ramdisk"
67
- ] = result.id
69
+ if result:
70
+ configuration["ironic_parameters"]["driver_info"][
71
+ "deploy_ramdisk"
72
+ ] = result.id
73
+ else:
74
+ logger.warning(
75
+ f"Could not resolve image ID for {deploy_ramdisk}"
76
+ )
68
77
 
69
78
  if "cleaning_network" in configuration["ironic_parameters"]["driver_info"]:
70
- result = openstack.network_get(
79
+ cleaning_network = configuration["ironic_parameters"]["driver_info"][
80
+ "cleaning_network"
81
+ ]
82
+ result = openstack.network_get(cleaning_network)
83
+ if result:
71
84
  configuration["ironic_parameters"]["driver_info"][
72
85
  "cleaning_network"
73
- ]
74
- )
75
- configuration["ironic_parameters"]["driver_info"][
76
- "cleaning_network"
77
- ] = result.id
86
+ ] = result.id
87
+ else:
88
+ logger.warning(
89
+ f"Could not resolve network ID for {cleaning_network}"
90
+ )
78
91
 
79
92
  if (
80
93
  "provisioning_network"
81
94
  in configuration["ironic_parameters"]["driver_info"]
82
95
  ):
83
- result = openstack.network_get(
96
+ provisioning_network = configuration["ironic_parameters"][
97
+ "driver_info"
98
+ ]["provisioning_network"]
99
+ result = openstack.network_get(provisioning_network)
100
+ if result:
84
101
  configuration["ironic_parameters"]["driver_info"][
85
102
  "provisioning_network"
86
- ]
87
- )
88
- configuration["ironic_parameters"]["driver_info"][
89
- "provisioning_network"
90
- ] = result.id
103
+ ] = result.id
104
+ else:
105
+ logger.warning(
106
+ f"Could not resolve network ID for {provisioning_network}"
107
+ )
91
108
 
92
109
  return configuration
@@ -3,7 +3,6 @@
3
3
  import json
4
4
 
5
5
  import jinja2
6
- from pottery import Redlock
7
6
 
8
7
  from osism import utils as osism_utils
9
8
  from osism.tasks import netbox, openstack
@@ -34,6 +33,100 @@ driver_params = {
34
33
  }
35
34
 
36
35
 
36
+ def _prepare_node_attributes(device, get_ironic_parameters):
37
+ node_attributes = get_ironic_parameters()
38
+ if (
39
+ "ironic_parameters" in device.custom_fields
40
+ and device.custom_fields["ironic_parameters"]
41
+ ):
42
+ deep_merge(node_attributes, device.custom_fields["ironic_parameters"])
43
+
44
+ vault = get_vault()
45
+ deep_decrypt(node_attributes, vault)
46
+
47
+ node_secrets = device.custom_fields.get("secrets", {})
48
+ if node_secrets is None:
49
+ node_secrets = {}
50
+ deep_decrypt(node_secrets, vault)
51
+
52
+ if (
53
+ "driver" in node_attributes
54
+ and node_attributes["driver"] in driver_params.keys()
55
+ ):
56
+ if "driver_info" in node_attributes:
57
+ unused_drivers = [
58
+ driver
59
+ for driver in driver_params.keys()
60
+ if driver != node_attributes["driver"]
61
+ ]
62
+ for key in list(node_attributes["driver_info"].keys()):
63
+ for driver in unused_drivers:
64
+ if key.startswith(driver + "_"):
65
+ node_attributes["driver_info"].pop(key, None)
66
+
67
+ username_key = driver_params[node_attributes["driver"]]["username"]
68
+ if username_key in node_attributes["driver_info"]:
69
+ node_attributes["driver_info"][username_key] = (
70
+ jinja2.Environment(loader=jinja2.BaseLoader())
71
+ .from_string(node_attributes["driver_info"][username_key])
72
+ .render(
73
+ remote_board_username=str(
74
+ node_secrets.get("remote_board_username", "admin")
75
+ )
76
+ )
77
+ )
78
+
79
+ password_key = driver_params[node_attributes["driver"]]["password"]
80
+ if password_key in node_attributes["driver_info"]:
81
+ node_attributes["driver_info"][password_key] = (
82
+ jinja2.Environment(loader=jinja2.BaseLoader())
83
+ .from_string(node_attributes["driver_info"][password_key])
84
+ .render(
85
+ remote_board_password=str(
86
+ node_secrets.get("remote_board_password", "password")
87
+ )
88
+ )
89
+ )
90
+
91
+ address_key = driver_params[node_attributes["driver"]]["address"]
92
+ if address_key in node_attributes["driver_info"]:
93
+ oob_ip_result = get_device_oob_ip(device)
94
+ if oob_ip_result:
95
+ oob_ip, _ = oob_ip_result
96
+ node_attributes["driver_info"][address_key] = (
97
+ jinja2.Environment(loader=jinja2.BaseLoader())
98
+ .from_string(node_attributes["driver_info"][address_key])
99
+ .render(remote_board_address=oob_ip)
100
+ )
101
+ node_attributes.update({"resource_class": device.name})
102
+ if "extra" not in node_attributes:
103
+ node_attributes["extra"] = {}
104
+ if "instance_info" in node_attributes and node_attributes["instance_info"]:
105
+ node_attributes["extra"].update(
106
+ {"instance_info": json.dumps(node_attributes["instance_info"])}
107
+ )
108
+ if (
109
+ "netplan_parameters" in device.custom_fields
110
+ and device.custom_fields["netplan_parameters"]
111
+ ):
112
+ node_attributes["extra"].update(
113
+ {
114
+ "netplan_parameters": json.dumps(
115
+ device.custom_fields["netplan_parameters"]
116
+ )
117
+ }
118
+ )
119
+ if (
120
+ "frr_parameters" in device.custom_fields
121
+ and device.custom_fields["frr_parameters"]
122
+ ):
123
+ node_attributes["extra"].update(
124
+ {"frr_parameters": json.dumps(device.custom_fields["frr_parameters"])}
125
+ )
126
+
127
+ return node_attributes
128
+
129
+
37
130
  def sync_ironic(request_id, get_ironic_parameters, force_update=False):
38
131
  osism_utils.push_task_output(
39
132
  request_id,
@@ -79,114 +172,15 @@ def sync_ironic(request_id, get_ironic_parameters, force_update=False):
79
172
 
80
173
  node_interfaces = list(netbox.get_interfaces_by_device(device.name))
81
174
 
82
- node_attributes = get_ironic_parameters()
83
- if (
84
- "ironic_parameters" in device.custom_fields
85
- and device.custom_fields["ironic_parameters"]
86
- ):
87
- # NOTE: Update node attributes with overrides from NetBox device
88
- deep_merge(node_attributes, device.custom_fields["ironic_parameters"])
89
-
90
- # NOTE: Decrypt ansible vaulted secrets
91
- vault = get_vault()
92
- deep_decrypt(node_attributes, vault)
93
-
94
- node_secrets = device.custom_fields.get("secrets", {})
95
- if node_secrets is None:
96
- node_secrets = {}
97
- deep_decrypt(node_secrets, vault)
98
-
99
- if (
100
- "driver" in node_attributes
101
- and node_attributes["driver"] in driver_params.keys()
102
- ):
103
- if "driver_info" in node_attributes:
104
- # NOTE: Remove all fields belonging to a different driver
105
- unused_drivers = [
106
- driver
107
- for driver in driver_params.keys()
108
- if driver != node_attributes["driver"]
109
- ]
110
- for key in list(node_attributes["driver_info"].keys()):
111
- for driver in unused_drivers:
112
- if key.startswith(driver + "_"):
113
- node_attributes["driver_info"].pop(key, None)
114
-
115
- # NOTE: Render driver username field
116
- username_key = driver_params[node_attributes["driver"]]["username"]
117
- if username_key in node_attributes["driver_info"]:
118
- node_attributes["driver_info"][username_key] = (
119
- jinja2.Environment(loader=jinja2.BaseLoader())
120
- .from_string(node_attributes["driver_info"][username_key])
121
- .render(
122
- remote_board_username=str(
123
- node_secrets.get("remote_board_username", "admin")
124
- )
125
- )
126
- )
127
-
128
- # NOTE: Render driver password field
129
- password_key = driver_params[node_attributes["driver"]]["password"]
130
- if password_key in node_attributes["driver_info"]:
131
- node_attributes["driver_info"][password_key] = (
132
- jinja2.Environment(loader=jinja2.BaseLoader())
133
- .from_string(node_attributes["driver_info"][password_key])
134
- .render(
135
- remote_board_password=str(
136
- node_secrets.get("remote_board_password", "password")
137
- )
138
- )
139
- )
140
-
141
- # NOTE: Render driver address field
142
- address_key = driver_params[node_attributes["driver"]]["address"]
143
- if address_key in node_attributes["driver_info"]:
144
- oob_ip_result = get_device_oob_ip(device)
145
- if oob_ip_result:
146
- oob_ip, _ = (
147
- oob_ip_result # Extract IP address, ignore prefix length
148
- )
149
- node_attributes["driver_info"][address_key] = (
150
- jinja2.Environment(loader=jinja2.BaseLoader())
151
- .from_string(node_attributes["driver_info"][address_key])
152
- .render(remote_board_address=oob_ip)
153
- )
154
- node_attributes.update({"resource_class": device.name})
155
- if "extra" not in node_attributes:
156
- node_attributes["extra"] = {}
157
- # NOTE: Copy instance_info into extra field. because ironic removes it on undeployment. This way it may be readded on undeploy without querying the netbox again
158
- if "instance_info" in node_attributes and node_attributes["instance_info"]:
159
- node_attributes["extra"].update(
160
- {"instance_info": json.dumps(node_attributes["instance_info"])}
161
- )
162
- # NOTE: Write metadata used for provisioning into 'extra' field, so that it is available during node deploy without querying the netbox again
163
- if (
164
- "netplan_parameters" in device.custom_fields
165
- and device.custom_fields["netplan_parameters"]
166
- ):
167
- node_attributes["extra"].update(
168
- {
169
- "netplan_parameters": json.dumps(
170
- device.custom_fields["netplan_parameters"]
171
- )
172
- }
173
- )
174
- if (
175
- "frr_parameters" in device.custom_fields
176
- and device.custom_fields["frr_parameters"]
177
- ):
178
- node_attributes["extra"].update(
179
- {"frr_parameters": json.dumps(device.custom_fields["frr_parameters"])}
180
- )
175
+ node_attributes = _prepare_node_attributes(device, get_ironic_parameters)
181
176
  ports_attributes = [
182
177
  dict(address=interface.mac_address)
183
178
  for interface in node_interfaces
184
179
  if interface.enabled and not interface.mgmt_only and interface.mac_address
185
180
  ]
186
181
 
187
- lock = Redlock(
182
+ lock = osism_utils.create_redlock(
188
183
  key=f"lock_osism_tasks_conductor_sync_ironic-{device.name}",
189
- masters={osism_utils.redis},
190
184
  auto_release_time=600,
191
185
  )
192
186
  if lock.acquire(timeout=120):