Skip to content

Commit 7f37f76

Browse files
scoopexMarc Schöchlin
andauthored
Better clouds yaml, get passwords interactively, bugfix quota (#31)
* improve clouds yaml generation * Improve cloud yaml generation * improve gitignore * Set quotas on every execution and bugfix the lookup * Add facility to ask passwords * add documentation --------- Signed-off-by: Marc Schöchlin <[email protected]> Co-authored-by: Marc Schöchlin <[email protected]>
1 parent 8cc512b commit 7f37f76

File tree

6 files changed

+117
-71
lines changed

6 files changed

+117
-71
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ venv
22
__pycache__
33
clouds*.yaml
44
*clouds.yaml
5-
5+
*.yaml_????-??-??_??-??-??

README.md

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,50 +26,70 @@ basis for later automation.
2626
# Usage
2727

2828
```
29-
$ ./openstack_workload_generator --help
30-
usage: Create workloads on openstack installations [-h] [--log_level loglevel] [--os_cloud OS_CLOUD]
31-
[--ansible_inventory [ANSIBLE_INVENTORY]]
32-
[--clouds_yaml [CLOUDS_YAML]] [--wait_for_machines]
33-
[--generate_clouds_yaml [GENERATE_CLOUDS_YAML]]
34-
[--config CONFIG]
35-
(--create_domains DOMAINNAME [DOMAINNAME ...] |
36-
--delete_domains DOMAINNAME [DOMAINNAME ...])
37-
[--create_projects PROJECTNAME [PROJECTNAME ...] |
38-
--delete_projects PROJECTNAME [PROJECTNAME ...]]
39-
[--create_machines SERVERNAME [SERVERNAME ...] |
40-
--delete_machines SERVERNAME [SERVERNAME ...]]
29+
$ openstack_workload_generator --help
30+
usage: Create workloads on openstack installations [-h] [--log_level loglevel] [--os_cloud OS_CLOUD] [--ansible_inventory [ANSIBLE_INVENTORY]]
31+
[--clouds_yaml [CLOUDS_YAML]] [--wait_for_machines] [--generate_clouds_yaml [GENERATE_CLOUDS_YAML]]
32+
[--config CONFIG] (--create_domains DOMAINNAME [DOMAINNAME ...] | --delete_domains DOMAINNAME [DOMAINNAME ...])
33+
[--create_projects PROJECTNAME [PROJECTNAME ...] | --delete_projects PROJECTNAME [PROJECTNAME ...]]
34+
[--create_machines SERVERNAME [SERVERNAME ...] | --delete_machines SERVERNAME [SERVERNAME ...]]
4135
4236
options:
4337
-h, --help show this help message and exit
4438
--log_level loglevel The loglevel
45-
--os_cloud OS_CLOUD The openstack config to use, defaults to the value of the OS_CLOUD environment variable or
46-
"admin" if the variable is not set
39+
--os_cloud OS_CLOUD The openstack config to use, defaults to the value of the OS_CLOUD environment variable or "admin" if the variable is not set
4740
--ansible_inventory [ANSIBLE_INVENTORY]
48-
Dump the created servers as an ansible inventory to the specified directory, adds a ssh
49-
proxy jump for the hosts without a floating ip
41+
Dump the created servers as an ansible inventory to the specified directory, adds a ssh proxy jump for the hosts without a floating ip
5042
--clouds_yaml [CLOUDS_YAML]
5143
Use a specific clouds.yaml file
52-
--wait_for_machines Wait for every machine to be created (normally the provisioning only waits for machines
53-
which use floating ips)
44+
--wait_for_machines Wait for every machine to be created (normally the provisioning only waits for machines which use floating ips)
5445
--generate_clouds_yaml [GENERATE_CLOUDS_YAML]
5546
Generate a openstack clouds.yaml file
56-
--config CONFIG The config file for environment creation, define a path to the yaml file or a subpath in
57-
the profiles folder
47+
--config CONFIG The config file for environment creation, define a path to the yaml file or a subpath in the profiles folder of the tool (you can overload
48+
the search path by setting the OPENSTACK_WORKLOAD_MANAGER_PROFILES environment variable)
5849
--create_domains DOMAINNAME [DOMAINNAME ...]
5950
A list of domains to be created
6051
--delete_domains DOMAINNAME [DOMAINNAME ...]
6152
A list of domains to be deleted, all child elements are recursively deleted
6253
--create_projects PROJECTNAME [PROJECTNAME ...]
6354
A list of projects to be created in the created domains
6455
--delete_projects PROJECTNAME [PROJECTNAME ...]
65-
A list of projects to be deleted in the created domains, all child elements are
66-
recursively deleted
56+
A list of projects to be deleted in the created domains, all child elements are recursively deleted
6757
--create_machines SERVERNAME [SERVERNAME ...]
6858
A list of vms to be created in the created domains
6959
--delete_machines SERVERNAME [SERVERNAME ...]
7060
A list of vms to be deleted in the created projects
7161
```
7262

63+
# Configuration
64+
65+
The following cnfigurations:
66+
67+
* `admin_domain_password`
68+
* the password for the domain users which are created (User `<domain-name>_admin`)
69+
* If you add "ASK_PASSWORD" as a value, the password will be asked in an interactive way
70+
* `admin_vm_password`:
71+
* the password for the operating system user (the username depends on the type of image you are using)
72+
* If you add "ASK_PASSWORD" as a value, the password will be asked in an interactive way
73+
* `vm_flavor`:
74+
* the name of the flavor used to create virtual machines
75+
* see `openstack flavor list`
76+
* `vm_image`:
77+
* the name of the image used to create virtual machines
78+
* see `openstack image list`
79+
* `vm_volume_size_gb`:
80+
* the size of the persistent root volume
81+
* `project_ipv4_subnet`:
82+
* the network cidr of the internal network
83+
* `*_quotas`:
84+
* the quotas for the created projects
85+
* execute the tool with `--log_level DEBUG` to see the configurable values
86+
* `public_network`:
87+
* The name of the public network which is used for floating ips
88+
* `admin_vm_ssh_key`:
89+
* A multiline string which ssh public keys
90+
91+
```
92+
7393
# Testing Scenarios
7494
7595
## Example usage: A minimal scenario

src/openstack_workload_generator/__main__.py

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,52 @@
2222
)
2323

2424

25+
def establish_connection():
26+
if args.clouds_yaml is None:
27+
config = loader.OpenStackConfig()
28+
else:
29+
LOGGER.info(f"Loading connection configuration from {args.clouds_yaml}")
30+
config = loader.OpenStackConfig(config_files=[args.clouds_yaml])
31+
cloud_config = config.get_one(args.os_cloud)
32+
return Connection(config=cloud_config)
33+
34+
35+
def generated_clouds_yaml():
36+
LOGGER.info(f"Creating a clouds yaml : {args.generate_clouds_yaml}")
37+
clouds_yaml_data_new = {"clouds": clouds_yaml_data}
38+
initial_entries = 0
39+
generated_entries = 0
40+
total_entries = 0
41+
if os.path.exists(args.generate_clouds_yaml):
42+
with open(args.generate_clouds_yaml, "r") as file:
43+
existing_data = yaml.safe_load(file)
44+
45+
initial_entries = len(existing_data.get("clouds", []))
46+
backup_file = f"{args.generate_clouds_yaml}_{iso_timestamp()}"
47+
logging.warning(
48+
f"File {args.generate_clouds_yaml}, making an backup to {backup_file} and adding the new values"
49+
)
50+
shutil.copy2(
51+
args.generate_clouds_yaml,
52+
f"{args.generate_clouds_yaml}_{iso_timestamp()}",
53+
)
54+
55+
generated_entries = len(clouds_yaml_data_new.get("clouds", []))
56+
clouds_yaml_data_new = deep_merge_dict(existing_data, clouds_yaml_data_new)
57+
total_entries = len(clouds_yaml_data_new.get("clouds", []))
58+
with open(args.generate_clouds_yaml, "w") as file:
59+
yaml.dump(
60+
clouds_yaml_data_new,
61+
file,
62+
default_flow_style=False,
63+
explicit_start=True,
64+
)
65+
LOGGER.info(
66+
f"Generated {generated_entries} entries, number of entries in "
67+
f"{args.generate_clouds_yaml} is now {total_entries} (old {initial_entries} entries)"
68+
)
69+
70+
2571
LOGGER = logging.getLogger()
2672

2773
parser = argparse.ArgumentParser(prog="Create workloads on openstack installations")
@@ -141,22 +187,12 @@
141187

142188
setup_logging(args.log_level)
143189

144-
145-
def establish_connection():
146-
if args.clouds_yaml is None:
147-
config = loader.OpenStackConfig()
148-
else:
149-
LOGGER.info(f"Loading connection configuration from {args.clouds_yaml}")
150-
config = loader.OpenStackConfig(config_files=[args.clouds_yaml])
151-
cloud_config = config.get_one(args.os_cloud)
152-
return Connection(config=cloud_config)
153-
154-
155190
time_start = time.time()
156191

157192
Config.load_config(args.config)
158193
Config.show_effective_config()
159194

195+
160196
if args.create_domains:
161197
conn = establish_connection()
162198
workload_domains: dict[str, WorkloadGeneratorDomain] = dict()
@@ -172,48 +208,27 @@ def establish_connection():
172208

173209
for workload_domain in workload_domains.values():
174210
for workload_project in workload_domain.get_projects(args.create_projects):
211+
if args.generate_clouds_yaml:
212+
clouds_yaml_data[
213+
f"{workload_domain.domain_name}-{workload_project.project_name}"
214+
] = workload_project.get_clouds_yaml_data()
215+
175216
if args.create_machines:
176217
workload_project.get_and_create_machines(
177218
args.create_machines, args.wait_for_machines
178219
)
179220
if args.ansible_inventory:
180221
workload_project.dump_inventory_hosts(args.ansible_inventory)
181-
if args.generate_clouds_yaml:
182-
clouds_yaml_data[
183-
f"{workload_domain.domain_name}-{workload_project.project_name}"
184-
] = workload_project.get_clouds_yaml_data()
222+
185223
elif args.delete_machines:
186224
for machine_obj in workload_project.get_machines(
187225
args.delete_machines
188226
):
189227
machine_obj.delete_machine()
190228

191229
if args.generate_clouds_yaml:
192-
LOGGER.info(f"Creating a clouds yaml : {args.generate_clouds_yaml}")
193-
clouds_yaml_data_new = {"clouds": clouds_yaml_data}
194-
195-
if os.path.exists(args.generate_clouds_yaml):
196-
with open(args.generate_clouds_yaml, "r") as file:
197-
existing_data = yaml.safe_load(file)
198-
backup_file = f"{args.generate_clouds_yaml}_{iso_timestamp()}"
199-
logging.warning(
200-
f"File {args.generate_clouds_yaml}, making an backup to {backup_file} and adding the new values"
201-
)
202-
shutil.copy2(
203-
args.generate_clouds_yaml,
204-
f"{args.generate_clouds_yaml}_{iso_timestamp()}",
205-
)
206-
clouds_yaml_data_new = deep_merge_dict(
207-
existing_data, clouds_yaml_data_new
208-
)
209-
210-
with open(args.generate_clouds_yaml, "w") as file:
211-
yaml.dump(
212-
clouds_yaml_data_new,
213-
file,
214-
default_flow_style=False,
215-
explicit_start=True,
216-
)
230+
generated_clouds_yaml()
231+
217232
sys.exit(0)
218233
elif args.delete_projects:
219234
conn = establish_connection()

src/openstack_workload_generator/entities/domain.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def create_and_get_projects(self, create_projects: list[str]):
9797

9898
for project_name in create_projects:
9999
if project_name in self.workload_projects:
100+
self.workload_projects[project_name].adapt_quota()
100101
continue
101102
project = WorkloadGeneratorProject(
102103
self.conn, project_name, self.obj, self.workload_user

src/openstack_workload_generator/entities/helpers.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import getpass
12
import inspect
23
import logging
34
import os
@@ -62,9 +63,14 @@ def get(key: str, regex: str = ".+", multi_line: bool = False) -> str:
6263

6364
@staticmethod
6465
def load_config(config_file: str):
65-
potential_profile_file = str(
66+
67+
profile_path = str(
6668
os.path.realpath(os.path.dirname(os.path.realpath(__file__)))
67-
+ f"/../../../profiles/{config_file}"
69+
+ "/../../../profiles/"
70+
)
71+
potential_profile_file = str(
72+
Path(os.getenv("OPENSTACK_WORKLOAD_MANAGER_PROFILES", profile_path))
73+
/ Path(config_file)
6874
)
6975

7076
if os.getenv("OPENSTACK_WORKLOAD_MANAGER_PROFILES", None):
@@ -135,7 +141,9 @@ def get_number_of_floating_ips_per_project() -> int:
135141

136142
@staticmethod
137143
def get_admin_vm_password() -> str:
138-
return Config.get("admin_vm_password")
144+
if Config.get("admin_vm_password").upper() == "ASK_PASSWORD":
145+
Config._config["admin_vm_password"] = getpass.getpass("Enter the wanted admin_vm_password: ")
146+
return Config.get("admin_vm_password", regex=r".{5,}")
139147

140148
@staticmethod
141149
def get_vm_flavor() -> str:
@@ -171,6 +179,8 @@ def get_admin_vm_ssh_key() -> str:
171179

172180
@staticmethod
173181
def get_admin_domain_password() -> str:
182+
if Config.get("admin_domain_password").upper() == "ASK_PASSWORD":
183+
Config._config["admin_domain_password"] = getpass.getpass("Enter the wanted admin_domain_password: ")
174184
return Config.get("admin_domain_password", regex=r".{5,}")
175185

176186
@staticmethod
@@ -192,7 +202,9 @@ def configured_quota_names(quota_category: str) -> list[str]:
192202
@staticmethod
193203
def quota(quota_name: str, quota_category: str, default_value: int) -> int:
194204
if quota_category in Config._config:
195-
value = Config._config.get(quota_name, default_value)
205+
value = Config._config[quota_category].get( # type: ignore
206+
quota_name, default_value
207+
)
196208
if isinstance(value, int):
197209
return value
198210
else:
@@ -246,7 +258,7 @@ def setup_logging(log_level: str) -> Tuple[logging.Logger, str]:
246258
)
247259
logger = logging.getLogger()
248260
log_file = "STDOUT"
249-
logging.basicConfig(format=log_format_string, level=log_level)
261+
logging.basicConfig(format=log_format_string, level=log_level.upper())
250262

251263
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"bold": True, "color": ""}
252264
coloredlogs.install(fmt=log_format_string, level=log_level.upper())

src/openstack_workload_generator/entities/project.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,6 @@ def _set_quota(self, quota_category: str):
169169
else:
170170
raise RuntimeError(f"Not implemented: {quota_category}")
171171

172-
# service_obj = getattr(self._admin_conn, api_area)
173-
# current_quota = service_obj.get_quota_set(self.obj.id)
174172
LOGGER.debug(f"current quotas for {quota_category} : {current_quota}")
175173

176174
new_quota = {}
@@ -193,7 +191,7 @@ def _set_quota(self, quota_category: str):
193191
)
194192
new_quota[key_name] = new_value
195193

196-
if len(new_quota):
194+
if len(new_quota.keys()) > 0:
197195
set_quota_method = getattr(self._admin_conn, f"set_{api_area}_quotas")
198196
set_quota_method(self.obj.id, **new_quota)
199197
LOGGER.info(

0 commit comments

Comments
 (0)