osism 0.20251003.0__py3-none-any.whl → 0.20251012.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/commands/apply.py +65 -34
- osism/commands/manage.py +725 -1
- osism/commands/server.py +97 -2
- osism/commands/stress.py +213 -0
- osism/data/__init__.py +34 -0
- osism/data/enums.py +54 -4
- osism/tasks/__init__.py +23 -4
- osism/tasks/openstack.py +80 -0
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/METADATA +2 -2
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/RECORD +16 -15
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/entry_points.txt +4 -0
- osism-0.20251012.0.dist-info/pbr.json +1 -0
- osism-0.20251003.0.dist-info/pbr.json +0 -1
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/WHEEL +0 -0
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/licenses/AUTHORS +0 -0
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/licenses/LICENSE +0 -0
- {osism-0.20251003.0.dist-info → osism-0.20251012.0.dist-info}/top_level.txt +0 -0
osism/commands/server.py
CHANGED
@@ -108,6 +108,18 @@ class ServerList(Command):
|
|
108
108
|
parser.add_argument(
|
109
109
|
"--project-domain", help="Domain of the project", type=str, default=None
|
110
110
|
)
|
111
|
+
parser.add_argument(
|
112
|
+
"--user",
|
113
|
+
default=None,
|
114
|
+
type=str,
|
115
|
+
help="Only list servers for the given user (name or ID)",
|
116
|
+
)
|
117
|
+
parser.add_argument(
|
118
|
+
"--user-domain",
|
119
|
+
default=None,
|
120
|
+
type=str,
|
121
|
+
help="Domain the user belongs to (name or ID)",
|
122
|
+
)
|
111
123
|
return parser
|
112
124
|
|
113
125
|
def take_action(self, parsed_args):
|
@@ -115,8 +127,31 @@ class ServerList(Command):
|
|
115
127
|
domain = parsed_args.domain
|
116
128
|
project = parsed_args.project
|
117
129
|
project_domain = parsed_args.project_domain
|
130
|
+
user = parsed_args.user
|
131
|
+
user_domain = parsed_args.user_domain
|
118
132
|
|
119
133
|
result = []
|
134
|
+
|
135
|
+
# Handle user lookup if --user is specified
|
136
|
+
user_id = None
|
137
|
+
if user:
|
138
|
+
user_query = {}
|
139
|
+
|
140
|
+
if user_domain:
|
141
|
+
u_d = conn.identity.find_domain(user_domain, ignore_missing=True)
|
142
|
+
if u_d and "id" in u_d:
|
143
|
+
user_query = dict(domain_id=u_d.id)
|
144
|
+
else:
|
145
|
+
logger.error(f"No domain found for {user_domain}")
|
146
|
+
return
|
147
|
+
|
148
|
+
u = conn.identity.find_user(user, ignore_missing=True, **user_query)
|
149
|
+
if u and "id" in u:
|
150
|
+
user_id = u.id
|
151
|
+
else:
|
152
|
+
logger.error(f"No user found for {user}")
|
153
|
+
return
|
154
|
+
|
120
155
|
if domain:
|
121
156
|
_domain = conn.identity.find_domain(domain)
|
122
157
|
if not _domain:
|
@@ -131,6 +166,7 @@ class ServerList(Command):
|
|
131
166
|
[
|
132
167
|
project.name,
|
133
168
|
project.id,
|
169
|
+
server.user_id if hasattr(server, "user_id") else None,
|
134
170
|
server.id,
|
135
171
|
server.name,
|
136
172
|
server.flavor["original_name"],
|
@@ -141,7 +177,15 @@ class ServerList(Command):
|
|
141
177
|
print(
|
142
178
|
tabulate(
|
143
179
|
result,
|
144
|
-
headers=[
|
180
|
+
headers=[
|
181
|
+
"Project",
|
182
|
+
"Project ID",
|
183
|
+
"User ID",
|
184
|
+
"ID",
|
185
|
+
"Name",
|
186
|
+
"Flavor",
|
187
|
+
"Status",
|
188
|
+
],
|
145
189
|
tablefmt="psql",
|
146
190
|
)
|
147
191
|
)
|
@@ -161,9 +205,20 @@ class ServerList(Command):
|
|
161
205
|
return
|
162
206
|
query = {"project_id": _project.id}
|
163
207
|
|
208
|
+
# Get domain name from project
|
209
|
+
domain_name = None
|
210
|
+
if hasattr(_project, "domain_id") and _project.domain_id:
|
211
|
+
try:
|
212
|
+
_domain = conn.identity.get_domain(_project.domain_id)
|
213
|
+
domain_name = _domain.name if _domain else _project.domain_id
|
214
|
+
except Exception:
|
215
|
+
domain_name = _project.domain_id
|
216
|
+
|
164
217
|
for server in conn.compute.servers(all_projects=True, **query):
|
165
218
|
result.append(
|
166
219
|
[
|
220
|
+
domain_name,
|
221
|
+
server.user_id if hasattr(server, "user_id") else None,
|
167
222
|
server.id,
|
168
223
|
server.name,
|
169
224
|
server.flavor["original_name"],
|
@@ -174,7 +229,47 @@ class ServerList(Command):
|
|
174
229
|
print(
|
175
230
|
tabulate(
|
176
231
|
result,
|
177
|
-
headers=["ID", "Name", "Flavor", "Status"],
|
232
|
+
headers=["Domain", "User ID", "ID", "Name", "Flavor", "Status"],
|
233
|
+
tablefmt="psql",
|
234
|
+
)
|
235
|
+
)
|
236
|
+
|
237
|
+
elif user_id:
|
238
|
+
query = {"user_id": user_id}
|
239
|
+
|
240
|
+
for server in conn.compute.servers(all_projects=True, **query):
|
241
|
+
# Get domain name from project
|
242
|
+
domain_name = None
|
243
|
+
if hasattr(server, "project_id") and server.project_id:
|
244
|
+
try:
|
245
|
+
_project = conn.identity.get_project(server.project_id)
|
246
|
+
if (
|
247
|
+
_project
|
248
|
+
and hasattr(_project, "domain_id")
|
249
|
+
and _project.domain_id
|
250
|
+
):
|
251
|
+
_domain = conn.identity.get_domain(_project.domain_id)
|
252
|
+
domain_name = (
|
253
|
+
_domain.name if _domain else _project.domain_id
|
254
|
+
)
|
255
|
+
except Exception:
|
256
|
+
domain_name = None
|
257
|
+
|
258
|
+
result.append(
|
259
|
+
[
|
260
|
+
domain_name,
|
261
|
+
server.project_id if hasattr(server, "project_id") else None,
|
262
|
+
server.id,
|
263
|
+
server.name,
|
264
|
+
server.flavor["original_name"],
|
265
|
+
server.status,
|
266
|
+
]
|
267
|
+
)
|
268
|
+
|
269
|
+
print(
|
270
|
+
tabulate(
|
271
|
+
result,
|
272
|
+
headers=["Domain", "Project ID", "ID", "Name", "Flavor", "Status"],
|
178
273
|
tablefmt="psql",
|
179
274
|
)
|
180
275
|
)
|
osism/commands/stress.py
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
|
3
|
+
import subprocess
|
4
|
+
|
5
|
+
from cliff.command import Command
|
6
|
+
from loguru import logger
|
7
|
+
|
8
|
+
|
9
|
+
class OpenStackStress(Command):
|
10
|
+
"""Run OpenStack stress testing tool"""
|
11
|
+
|
12
|
+
def get_parser(self, prog_name):
|
13
|
+
parser = super(OpenStackStress, self).get_parser(prog_name)
|
14
|
+
|
15
|
+
# Boolean flags
|
16
|
+
parser.add_argument(
|
17
|
+
"--no-cleanup",
|
18
|
+
action="store_true",
|
19
|
+
help="Do not clean up resources after test",
|
20
|
+
)
|
21
|
+
parser.add_argument(
|
22
|
+
"--debug",
|
23
|
+
action="store_true",
|
24
|
+
help="Enable debug mode",
|
25
|
+
)
|
26
|
+
parser.add_argument(
|
27
|
+
"--no-delete",
|
28
|
+
action="store_true",
|
29
|
+
help="Do not delete resources",
|
30
|
+
)
|
31
|
+
parser.add_argument(
|
32
|
+
"--no-volume",
|
33
|
+
action="store_true",
|
34
|
+
help="Do not create volumes",
|
35
|
+
)
|
36
|
+
parser.add_argument(
|
37
|
+
"--no-boot-volume",
|
38
|
+
action="store_true",
|
39
|
+
help="Do not use boot volumes",
|
40
|
+
)
|
41
|
+
parser.add_argument(
|
42
|
+
"--no-wait",
|
43
|
+
action="store_true",
|
44
|
+
help="Do not wait for resources",
|
45
|
+
)
|
46
|
+
|
47
|
+
# Integer parameters with defaults
|
48
|
+
parser.add_argument(
|
49
|
+
"--interval",
|
50
|
+
type=int,
|
51
|
+
default=10,
|
52
|
+
help="Interval in seconds (default: %(default)s)",
|
53
|
+
)
|
54
|
+
parser.add_argument(
|
55
|
+
"--number",
|
56
|
+
type=int,
|
57
|
+
default=1,
|
58
|
+
help="Number of instances (default: %(default)s)",
|
59
|
+
)
|
60
|
+
parser.add_argument(
|
61
|
+
"--parallel",
|
62
|
+
type=int,
|
63
|
+
default=1,
|
64
|
+
help="Parallel operations (default: %(default)s)",
|
65
|
+
)
|
66
|
+
parser.add_argument(
|
67
|
+
"--timeout",
|
68
|
+
type=int,
|
69
|
+
default=600,
|
70
|
+
help="Timeout in seconds (default: %(default)s)",
|
71
|
+
)
|
72
|
+
parser.add_argument(
|
73
|
+
"--volume-number",
|
74
|
+
type=int,
|
75
|
+
default=1,
|
76
|
+
help="Number of volumes per instance (default: %(default)s)",
|
77
|
+
)
|
78
|
+
parser.add_argument(
|
79
|
+
"--volume-size",
|
80
|
+
type=int,
|
81
|
+
default=1,
|
82
|
+
help="Volume size in GB (default: %(default)s)",
|
83
|
+
)
|
84
|
+
parser.add_argument(
|
85
|
+
"--boot-volume-size",
|
86
|
+
type=int,
|
87
|
+
default=20,
|
88
|
+
help="Boot volume size in GB (default: %(default)s)",
|
89
|
+
)
|
90
|
+
|
91
|
+
# String parameters with defaults
|
92
|
+
parser.add_argument(
|
93
|
+
"--cloud",
|
94
|
+
type=str,
|
95
|
+
default="simple-stress",
|
96
|
+
help="Cloud name in clouds.yaml (default: %(default)s)",
|
97
|
+
)
|
98
|
+
parser.add_argument(
|
99
|
+
"--flavor",
|
100
|
+
type=str,
|
101
|
+
default="SCS-1V-2",
|
102
|
+
help="Flavor name (default: %(default)s)",
|
103
|
+
)
|
104
|
+
parser.add_argument(
|
105
|
+
"--image",
|
106
|
+
type=str,
|
107
|
+
default="Ubuntu 24.04",
|
108
|
+
help="Image name (default: %(default)s)",
|
109
|
+
)
|
110
|
+
parser.add_argument(
|
111
|
+
"--subnet-cidr",
|
112
|
+
type=str,
|
113
|
+
default="10.100.0.0/16",
|
114
|
+
help="Subnet CIDR (default: %(default)s)",
|
115
|
+
)
|
116
|
+
parser.add_argument(
|
117
|
+
"--prefix",
|
118
|
+
type=str,
|
119
|
+
default="simple-stress",
|
120
|
+
help="Resource name prefix (default: %(default)s)",
|
121
|
+
)
|
122
|
+
parser.add_argument(
|
123
|
+
"--compute-zone",
|
124
|
+
type=str,
|
125
|
+
default="nova",
|
126
|
+
help="Compute availability zone (default: %(default)s)",
|
127
|
+
)
|
128
|
+
parser.add_argument(
|
129
|
+
"--storage-zone",
|
130
|
+
type=str,
|
131
|
+
default="nova",
|
132
|
+
help="Storage availability zone (default: %(default)s)",
|
133
|
+
)
|
134
|
+
parser.add_argument(
|
135
|
+
"--affinity",
|
136
|
+
type=str,
|
137
|
+
default="soft-anti-affinity",
|
138
|
+
choices=[
|
139
|
+
"soft-affinity",
|
140
|
+
"soft-anti-affinity",
|
141
|
+
"affinity",
|
142
|
+
"anti-affinity",
|
143
|
+
],
|
144
|
+
help="Server group policy (default: %(default)s)",
|
145
|
+
)
|
146
|
+
parser.add_argument(
|
147
|
+
"--volume-type",
|
148
|
+
type=str,
|
149
|
+
default="__DEFAULT__",
|
150
|
+
help="Volume type (default: %(default)s)",
|
151
|
+
)
|
152
|
+
|
153
|
+
return parser
|
154
|
+
|
155
|
+
def take_action(self, parsed_args):
|
156
|
+
"""Execute the OpenStack stress testing tool"""
|
157
|
+
|
158
|
+
# Build the command
|
159
|
+
command = [
|
160
|
+
"python3",
|
161
|
+
"/openstack-simple-stress/openstack_simple_stress/main.py",
|
162
|
+
]
|
163
|
+
|
164
|
+
# Add boolean flags
|
165
|
+
if parsed_args.no_cleanup:
|
166
|
+
command.append("--no-cleanup")
|
167
|
+
if parsed_args.debug:
|
168
|
+
command.append("--debug")
|
169
|
+
if parsed_args.no_delete:
|
170
|
+
command.append("--no-delete")
|
171
|
+
if parsed_args.no_volume:
|
172
|
+
command.append("--no-volume")
|
173
|
+
if parsed_args.no_boot_volume:
|
174
|
+
command.append("--no-boot-volume")
|
175
|
+
if parsed_args.no_wait:
|
176
|
+
command.append("--no-wait")
|
177
|
+
|
178
|
+
# Add integer parameters
|
179
|
+
command.extend(["--interval", str(parsed_args.interval)])
|
180
|
+
command.extend(["--number", str(parsed_args.number)])
|
181
|
+
command.extend(["--parallel", str(parsed_args.parallel)])
|
182
|
+
command.extend(["--timeout", str(parsed_args.timeout)])
|
183
|
+
command.extend(["--volume-number", str(parsed_args.volume_number)])
|
184
|
+
command.extend(["--volume-size", str(parsed_args.volume_size)])
|
185
|
+
command.extend(["--boot-volume-size", str(parsed_args.boot_volume_size)])
|
186
|
+
|
187
|
+
# Add string parameters
|
188
|
+
command.extend(["--cloud", parsed_args.cloud])
|
189
|
+
command.extend(["--flavor", parsed_args.flavor])
|
190
|
+
command.extend(["--image", parsed_args.image])
|
191
|
+
command.extend(["--subnet-cidr", parsed_args.subnet_cidr])
|
192
|
+
command.extend(["--prefix", parsed_args.prefix])
|
193
|
+
command.extend(["--compute-zone", parsed_args.compute_zone])
|
194
|
+
command.extend(["--storage-zone", parsed_args.storage_zone])
|
195
|
+
command.extend(["--affinity", parsed_args.affinity])
|
196
|
+
command.extend(["--volume-type", parsed_args.volume_type])
|
197
|
+
|
198
|
+
logger.debug(
|
199
|
+
f"Executing OpenStack stress test with command: {' '.join(command)}"
|
200
|
+
)
|
201
|
+
|
202
|
+
# Execute the stress tool
|
203
|
+
try:
|
204
|
+
result = subprocess.run(command, check=False)
|
205
|
+
return result.returncode
|
206
|
+
except FileNotFoundError:
|
207
|
+
logger.error(
|
208
|
+
"OpenStack stress tool not found at /openstack-simple-stress/openstack_simple_stress/main.py"
|
209
|
+
)
|
210
|
+
return 1
|
211
|
+
except Exception as e:
|
212
|
+
logger.error(f"Error executing OpenStack stress tool: {e}")
|
213
|
+
return 1
|
osism/data/__init__.py
CHANGED
@@ -101,3 +101,37 @@ images:
|
|
101
101
|
build_date: {{ image_builddate }}
|
102
102
|
|
103
103
|
"""
|
104
|
+
|
105
|
+
TEMPLATE_IMAGE_CLUSTERAPI_GARDENER = """---
|
106
|
+
images:
|
107
|
+
- name: ubuntu-capi-image-gardener
|
108
|
+
enable: true
|
109
|
+
keep: true
|
110
|
+
separator: "-"
|
111
|
+
format: qcow2
|
112
|
+
login: ubuntu
|
113
|
+
min_disk: 20
|
114
|
+
min_ram: 512
|
115
|
+
status: active
|
116
|
+
visibility: public
|
117
|
+
multi: false
|
118
|
+
meta:
|
119
|
+
architecture: x86_64
|
120
|
+
hw_disk_bus: scsi
|
121
|
+
hw_rng_model: virtio
|
122
|
+
hw_scsi_model: virtio-scsi
|
123
|
+
hw_watchdog_action: reset
|
124
|
+
hypervisor_type: qemu
|
125
|
+
os_distro: ubuntu
|
126
|
+
replace_frequency: never
|
127
|
+
uuid_validity: none
|
128
|
+
provided_until: none
|
129
|
+
os_purpose: k8snode
|
130
|
+
tags: []
|
131
|
+
versions:
|
132
|
+
- version: "v{{ image_version }}"
|
133
|
+
url: "{{ image_url }}"
|
134
|
+
checksum: "{{ image_checksum }}"
|
135
|
+
build_date: {{ image_builddate }}
|
136
|
+
|
137
|
+
"""
|
osism/data/enums.py
CHANGED
@@ -132,7 +132,7 @@ MAP_ROLE2ROLE = {
|
|
132
132
|
[
|
133
133
|
"keystone",
|
134
134
|
[
|
135
|
-
["neutron", ["wait-for-nova", ["octavia"]]],
|
135
|
+
["neutron", [["wait-for-nova", ["octavia"]]]],
|
136
136
|
"barbican",
|
137
137
|
"designate",
|
138
138
|
"ironic",
|
@@ -207,7 +207,7 @@ MAP_ROLE2ROLE = {
|
|
207
207
|
[
|
208
208
|
"glance",
|
209
209
|
"cinder",
|
210
|
-
["neutron", ["octavia"]],
|
210
|
+
["neutron", [["wait-for-nova", ["octavia"]]]],
|
211
211
|
"designate",
|
212
212
|
["placement", ["nova"]],
|
213
213
|
],
|
@@ -220,10 +220,9 @@ MAP_ROLE2ROLE = {
|
|
220
220
|
[
|
221
221
|
"glance",
|
222
222
|
"cinder",
|
223
|
-
"neutron",
|
224
223
|
"barbican",
|
225
224
|
"designate",
|
226
|
-
"octavia",
|
225
|
+
["neutron", [["wait-for-nova", ["octavia"]]]],
|
227
226
|
"ironic",
|
228
227
|
"kolla-ceph-rgw",
|
229
228
|
"magnum",
|
@@ -300,4 +299,55 @@ MAP_ROLE2ROLE = {
|
|
300
299
|
],
|
301
300
|
],
|
302
301
|
],
|
302
|
+
"cloudpod-infrastructure": [
|
303
|
+
"openstackclient",
|
304
|
+
"phpmyadmin",
|
305
|
+
[
|
306
|
+
"common",
|
307
|
+
[
|
308
|
+
["loadbalancer", ["letsencrypt", "opensearch", "mariadb-ng"]],
|
309
|
+
["openvswitch", ["ovn"]],
|
310
|
+
"memcached",
|
311
|
+
"redis",
|
312
|
+
"rabbitmq-ng",
|
313
|
+
],
|
314
|
+
],
|
315
|
+
],
|
316
|
+
"cloudpod-openstack": [
|
317
|
+
"horizon",
|
318
|
+
[
|
319
|
+
"keystone",
|
320
|
+
[
|
321
|
+
"glance",
|
322
|
+
"cinder",
|
323
|
+
["neutron", [["wait-for-nova", ["octavia"]]]],
|
324
|
+
["placement", ["nova"]],
|
325
|
+
"designate",
|
326
|
+
"skyline",
|
327
|
+
"kolla-ceph-rgw",
|
328
|
+
],
|
329
|
+
],
|
330
|
+
],
|
331
|
+
"cloudpod-ceph": [
|
332
|
+
[
|
333
|
+
"ceph-create-lvm-devices",
|
334
|
+
[
|
335
|
+
"facts",
|
336
|
+
[
|
337
|
+
"ceph",
|
338
|
+
[
|
339
|
+
[
|
340
|
+
"ceph-pools",
|
341
|
+
[
|
342
|
+
[
|
343
|
+
"copy-ceph-keys",
|
344
|
+
[["cephclient", ["ceph-bootstrap-dashboard"]]],
|
345
|
+
]
|
346
|
+
],
|
347
|
+
],
|
348
|
+
],
|
349
|
+
],
|
350
|
+
],
|
351
|
+
],
|
352
|
+
],
|
303
353
|
}
|
osism/tasks/__init__.py
CHANGED
@@ -13,6 +13,9 @@ from loguru import logger
|
|
13
13
|
|
14
14
|
from osism import utils
|
15
15
|
|
16
|
+
# Regex pattern for extracting hosts from Ansible output
|
17
|
+
HOST_PATTERN = re.compile(r"^(ok|changed|failed|skipping|unreachable):\s+\[([^\]]+)\]")
|
18
|
+
|
16
19
|
|
17
20
|
class Config:
|
18
21
|
broker_connection_retry_on_startup = True
|
@@ -88,7 +91,7 @@ def get_container_version(worker):
|
|
88
91
|
|
89
92
|
|
90
93
|
def log_play_execution(
|
91
|
-
request_id, worker, environment, role, hosts=None, result="started"
|
94
|
+
request_id, worker, environment, role, hosts=None, arguments=None, result="started"
|
92
95
|
):
|
93
96
|
"""Log Ansible play execution to central tracking file.
|
94
97
|
|
@@ -98,6 +101,7 @@ def log_play_execution(
|
|
98
101
|
environment: The environment parameter
|
99
102
|
role: The playbook/role that was executed
|
100
103
|
hosts: List of hosts the play was executed against (default: empty list)
|
104
|
+
arguments: Command-line arguments passed to ansible-playbook (default: None)
|
101
105
|
result: Execution result - "started", "success", or "failure"
|
102
106
|
"""
|
103
107
|
log_file = Path("/share/ansible-execution-history.json")
|
@@ -105,6 +109,10 @@ def log_play_execution(
|
|
105
109
|
# Get runtime version from YAML version file
|
106
110
|
runtime_version = get_container_version(worker)
|
107
111
|
|
112
|
+
# Use provided hosts or empty list
|
113
|
+
if hosts is None:
|
114
|
+
hosts = []
|
115
|
+
|
108
116
|
execution_record = {
|
109
117
|
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
110
118
|
"request_id": request_id,
|
@@ -112,7 +120,8 @@ def log_play_execution(
|
|
112
120
|
"worker_version": runtime_version,
|
113
121
|
"environment": environment,
|
114
122
|
"role": role,
|
115
|
-
"hosts": hosts
|
123
|
+
"hosts": hosts,
|
124
|
+
"arguments": arguments if arguments else "",
|
116
125
|
"result": result,
|
117
126
|
}
|
118
127
|
|
@@ -143,6 +152,7 @@ def run_ansible_in_environment(
|
|
143
152
|
auto_release_time=3600,
|
144
153
|
):
|
145
154
|
result = ""
|
155
|
+
extracted_hosts = set() # Local set for host deduplication
|
146
156
|
|
147
157
|
if type(arguments) == list:
|
148
158
|
joined_arguments = " ".join(arguments)
|
@@ -181,7 +191,8 @@ def run_ansible_in_environment(
|
|
181
191
|
worker=worker,
|
182
192
|
environment=environment,
|
183
193
|
role=role,
|
184
|
-
hosts=None, #
|
194
|
+
hosts=None, # Hosts will be empty at start, filled at completion
|
195
|
+
arguments=joined_arguments,
|
185
196
|
result="started",
|
186
197
|
)
|
187
198
|
|
@@ -269,6 +280,13 @@ def run_ansible_in_environment(
|
|
269
280
|
|
270
281
|
while p.poll() is None:
|
271
282
|
line = p.stdout.readline().decode("utf-8")
|
283
|
+
|
284
|
+
# Extract hosts from Ansible output
|
285
|
+
match = HOST_PATTERN.match(line.strip())
|
286
|
+
if match:
|
287
|
+
hostname = match.group(2)
|
288
|
+
extracted_hosts.add(hostname) # Local set (automatic deduplication)
|
289
|
+
|
272
290
|
if publish:
|
273
291
|
utils.push_task_output(request_id, line)
|
274
292
|
result += line
|
@@ -281,7 +299,8 @@ def run_ansible_in_environment(
|
|
281
299
|
worker=worker,
|
282
300
|
environment=environment,
|
283
301
|
role=role,
|
284
|
-
hosts=
|
302
|
+
hosts=sorted(list(extracted_hosts)), # Direct pass of extracted hosts
|
303
|
+
arguments=joined_arguments,
|
285
304
|
result="success" if rc == 0 else "failure",
|
286
305
|
)
|
287
306
|
|
osism/tasks/openstack.py
CHANGED
@@ -452,3 +452,83 @@ def flavor_manager(
|
|
452
452
|
locking=locking,
|
453
453
|
auto_release_time=auto_release_time,
|
454
454
|
)
|
455
|
+
|
456
|
+
|
457
|
+
@app.task(bind=True, name="osism.tasks.openstack.project_manager")
|
458
|
+
def project_manager(
|
459
|
+
self,
|
460
|
+
*arguments,
|
461
|
+
publish=True,
|
462
|
+
locking=False,
|
463
|
+
auto_release_time=3600,
|
464
|
+
cloud=None,
|
465
|
+
):
|
466
|
+
# Check if tasks are locked before execution
|
467
|
+
utils.check_task_lock_and_exit()
|
468
|
+
|
469
|
+
command = "/usr/local/bin/python3"
|
470
|
+
script_path = "/openstack-project-manager/openstack_project_manager/create.py"
|
471
|
+
# Prepend script path to arguments
|
472
|
+
full_arguments = [script_path] + list(arguments)
|
473
|
+
|
474
|
+
# Setup cloud environment (changes to /tmp)
|
475
|
+
temp_files_to_cleanup, original_cwd, cloud_setup_success = setup_cloud_environment(
|
476
|
+
cloud
|
477
|
+
)
|
478
|
+
|
479
|
+
try:
|
480
|
+
# Change to working directory required by openstack-project-manager
|
481
|
+
os.chdir("/openstack-project-manager")
|
482
|
+
|
483
|
+
return run_command(
|
484
|
+
self.request.id,
|
485
|
+
command,
|
486
|
+
{},
|
487
|
+
*full_arguments,
|
488
|
+
publish=publish,
|
489
|
+
locking=locking,
|
490
|
+
auto_release_time=auto_release_time,
|
491
|
+
ignore_env=True,
|
492
|
+
)
|
493
|
+
finally:
|
494
|
+
cleanup_cloud_environment(temp_files_to_cleanup, original_cwd)
|
495
|
+
|
496
|
+
|
497
|
+
@app.task(bind=True, name="osism.tasks.openstack.project_manager_sync")
|
498
|
+
def project_manager_sync(
|
499
|
+
self,
|
500
|
+
*arguments,
|
501
|
+
publish=True,
|
502
|
+
locking=False,
|
503
|
+
auto_release_time=3600,
|
504
|
+
cloud=None,
|
505
|
+
):
|
506
|
+
# Check if tasks are locked before execution
|
507
|
+
utils.check_task_lock_and_exit()
|
508
|
+
|
509
|
+
command = "/usr/local/bin/python3"
|
510
|
+
script_path = "/openstack-project-manager/openstack_project_manager/manage.py"
|
511
|
+
# Prepend script path to arguments
|
512
|
+
full_arguments = [script_path] + list(arguments)
|
513
|
+
|
514
|
+
# Setup cloud environment (changes to /tmp)
|
515
|
+
temp_files_to_cleanup, original_cwd, cloud_setup_success = setup_cloud_environment(
|
516
|
+
cloud
|
517
|
+
)
|
518
|
+
|
519
|
+
try:
|
520
|
+
# Change to working directory required by openstack-project-manager
|
521
|
+
os.chdir("/openstack-project-manager")
|
522
|
+
|
523
|
+
return run_command(
|
524
|
+
self.request.id,
|
525
|
+
command,
|
526
|
+
{},
|
527
|
+
*full_arguments,
|
528
|
+
publish=publish,
|
529
|
+
locking=locking,
|
530
|
+
auto_release_time=auto_release_time,
|
531
|
+
ignore_env=True,
|
532
|
+
)
|
533
|
+
finally:
|
534
|
+
cleanup_cloud_environment(temp_files_to_cleanup, original_cwd)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: osism
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20251012.0
|
4
4
|
Summary: OSISM manager interface
|
5
5
|
Home-page: https://github.com/osism/python-osism
|
6
6
|
Author: OSISM GmbH
|
@@ -59,7 +59,7 @@ Requires-Dist: watchdog==6.0.0
|
|
59
59
|
Requires-Dist: websockets==15.0.1
|
60
60
|
Provides-Extra: ansible
|
61
61
|
Requires-Dist: ansible-runner==2.4.1; extra == "ansible"
|
62
|
-
Requires-Dist: ansible-core==2.19.
|
62
|
+
Requires-Dist: ansible-core==2.19.3; extra == "ansible"
|
63
63
|
Provides-Extra: openstack-image-manager
|
64
64
|
Requires-Dist: openstack-image-manager==0.20250912.0; extra == "openstack-image-manager"
|
65
65
|
Dynamic: author
|