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
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
|
-
|
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 =
|
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 =
|
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",
|
osism/tasks/conductor/config.py
CHANGED
@@ -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
|
32
|
+
if not validators.uuid(image_source) and not validators.url(
|
33
|
+
image_source
|
34
|
+
):
|
43
35
|
result = openstack.image_get(image_source)
|
44
|
-
|
45
|
-
"
|
46
|
-
|
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
|
48
|
+
if not validators.uuid(deploy_kernel) and not validators.url(
|
49
|
+
deploy_kernel
|
50
|
+
):
|
54
51
|
result = openstack.image_get(deploy_kernel)
|
55
|
-
|
56
|
-
"
|
57
|
-
|
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
|
65
|
+
if not validators.uuid(deploy_ramdisk) and not validators.url(
|
66
|
+
deploy_ramdisk
|
67
|
+
):
|
64
68
|
result = openstack.image_get(deploy_ramdisk)
|
65
|
-
|
66
|
-
"
|
67
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
osism/tasks/conductor/ironic.py
CHANGED
@@ -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 =
|
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):
|