-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial support for a Beaker provider.
This change adds a very basic Beaker provider and its bind. For now, this relies on the beaker-client cli and config through that. Currently the only supported checkout/execute method is via job xml.
- Loading branch information
1 parent
e34979b
commit e7ac7be
Showing
5 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import json | ||
import subprocess | ||
from logzero import logger | ||
from pathlib import Path | ||
|
||
|
||
class BeakerBind: | ||
|
||
def __init__(self, hub_url, auth="krbv", **kwargs): | ||
self.hub_url = hub_url | ||
self._base_args = ["--insecure", f"--hub={self.hub_url}"] | ||
if auth == "basic": | ||
# If we're not using system kerberos auth, add in explicit basic auth | ||
self.username = kwargs.pop("username", None) | ||
self.password = kwargs.pop("password", None) | ||
self._base_args.extend([ | ||
f"--username {self.username}", | ||
f"--password {self.password}", | ||
]) | ||
self.__dict__.update(kwargs) | ||
|
||
def _exec_command(self, *cmd_args, **cmd_kwargs): | ||
exec_cmd = ["bkr"] | ||
exec_cmd.extend(cmd_args) | ||
exec_cmd.extend(self._base_args) | ||
exec_cmd.extend([ | ||
f"--{k.replace('_', '-')}={v}" | ||
for k, v in cmd_kwargs.items() | ||
]) | ||
logger.debug(f"Executing beaker command: {exec_cmd}") | ||
return subprocess.check_output(exec_cmd).decode() | ||
|
||
def job_submit(self, job_xml, wait=True): | ||
if not Path(job_xml).exists(): | ||
raise FileNotFoundError(f"Job XML file {job_xml} not found") | ||
result = self._exec_command("job-submit", job_xml, wait=wait) | ||
if not wait: | ||
# get the job id from the output | ||
# format is "Submitted: ['J:7849837'] where the number is the job id | ||
for line in result.splitlines(): | ||
if line.startswith("Submitted:"): | ||
return line.split("'")[1].replace("J:", "") | ||
|
||
def job_watch(self, job_id): | ||
job_id = f"J:{job_id}" | ||
return self._exec_command("job-watch", job_id) | ||
|
||
def job_results(self, job_id, format="beaker-results-xml", pretty=False): | ||
job_id = f"J:{job_id}" | ||
return self._exec_command("job-results", job_id, format=format, prettyxml=pretty) | ||
|
||
def job_clone(self, job_id, wait=True): | ||
job_id = f"J:{job_id}" | ||
return self._exec_command("job-clone", job_id, wait=wait) | ||
|
||
def job_cancel(self, job_id): | ||
job_id = f"J:{job_id}" | ||
return self._exec_command("job-cancel", job_id) | ||
|
||
def job_delete(self, job_id): | ||
job_id = f"J:{job_id}" | ||
return self._exec_command("job-delete", job_id) | ||
|
||
def system_release(self, system_id): | ||
return self._exec_command("system-release", system_id) | ||
|
||
def system_list(self, **kwargs): | ||
"""Due to the number of arguments, we will not validate before submitting | ||
Accepted arguments are: | ||
available available to be used by this user | ||
free available to this user and not currently being used | ||
removed which have been removed | ||
mine owned by this user | ||
type=TYPE of TYPE | ||
status=STATUS with STATUS | ||
pool=POOL in POOL | ||
arch=ARCH with ARCH | ||
dev-vendor-id=VENDOR-ID with a device that has VENDOR-ID | ||
dev-device-id=DEVICE-ID with a device that has DEVICE-ID | ||
dev-sub-vendor-id=SUBVENDOR-ID with a device that has SUBVENDOR-ID | ||
dev-sub-device-id=SUBDEVICE-ID with a device that has SUBDEVICE-ID | ||
dev-driver=DRIVER with a device that has DRIVER | ||
dev-description=DESCRIPTION with a device that has DESCRIPTION | ||
xml-filter=XML matching the given XML filter | ||
host-filter=NAME matching pre-defined host filter | ||
""" | ||
# convert the flags passed in kwargs to arguments | ||
args = [] | ||
for key in {"available", "free", "removed", "mine"}: | ||
if kwargs.pop(key, False): | ||
args.append(f"--{key}") | ||
return self._exec_command("system-list", *args, **kwargs) | ||
|
||
def user_systems(self): # to be used for inventory sync | ||
return self.system_list(mine=True).splitlines() | ||
|
||
def system_details(self, system_id, format="json"): | ||
return self._exec_command("system-details", system_id, format=format) | ||
|
||
def system_details_curated(self, system_id): | ||
full_details = json.loads(self.system_details(system_id)) | ||
return { | ||
"fqdn": full_details["fqdn"], | ||
"mac_address": full_details["mac_address"], | ||
"reserved_on": full_details["current_reservation"]["start_time"], | ||
"expires_on": full_details["current_reservation"]["finish_time"], | ||
"reserved_for": "{display_name} <{email_address}>".format( | ||
display_name=full_details["current_reservation"]["user"]["display_name"], | ||
email_address=full_details["current_reservation"]["user"]["email_address"], | ||
), | ||
"owner": "{display_name} <{email_address}>".format( | ||
display_name=full_details["owner"]["display_name"], | ||
email_address=full_details["owner"]["email_address"], | ||
), | ||
"id": full_details["id"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import click | ||
import inspect | ||
from dynaconf import Validator | ||
from logzero import logger | ||
from broker.providers import Provider | ||
from broker.binds.beaker import BeakerBind | ||
|
||
class Beaker(Provider): | ||
|
||
_validators = [ | ||
Validator("beaker.hub_url", must_exist=True), | ||
] | ||
_checkout_options = [ | ||
click.option( | ||
"--job-xml", | ||
type=str, | ||
help="Path to the job XML file to submit", | ||
), | ||
] | ||
_execute_options = [ | ||
click.option( | ||
"--job-xml", | ||
type=str, | ||
help="Path to the job XML file to submit", | ||
), | ||
] | ||
_extend_options = [] # TODO: find out the appropriate option | ||
|
||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
self.runtime = BeakerBind(self.hub_url, **kwargs) | ||
|
||
def _host_release(self): | ||
caller_host = inspect.stack()[1][0].f_locals["host"] | ||
broker_args = getattr(caller_host, "_broker_args", {}).get("_broker_args", {}) | ||
return self.runtime.system_release(broker_args["id"]) | ||
|
||
def _set_attributes(self, host_inst, broker_args=None): | ||
host_inst.__dict__.update( | ||
{ | ||
"_prov_inst": self, | ||
"_broker_provider": "Beaker", | ||
"_broker_provider_instance": self.instance, | ||
"_broker_args": broker_args, | ||
"release": self._host_release, | ||
} | ||
) | ||
|
||
def construct_host(self, provider_params, host_classes, **kwargs): | ||
"""Constructs broker host from a beaker system information | ||
:param provider_params: a beaker system information dictionary | ||
:param host_classes: host object | ||
:return: constructed broker host object | ||
""" | ||
logger.debug( | ||
f"constructing with {provider_params=}\n{host_classes=}\n{kwargs=}" | ||
) | ||
if not provider_params: | ||
host_inst = host_classes[kwargs.get("type", "host")](**kwargs) | ||
# cont_inst = self._cont_inst_by_name(host_inst.name) | ||
self._set_attributes(host_inst, broker_args=kwargs) | ||
return host_inst | ||
|
||
@Provider.register_action("job_xml") | ||
def submit_job(self, job_xml): | ||
job_id = self.runtime.job_submit(job_xml) | ||
logger.info(f"Submitted job {job_id}") | ||
# wait for the job to finish | ||
result = self.runtime.job_watch(job_id) | ||
|
||
def get_inventory(self, **kwargs): | ||
hosts = self.runtime.user_systems() | ||
with click.progressbar(hosts, label='Compiling host information') as hosts_bar: | ||
compiled_host_info = [self._compile_host_info(host) for host in hosts_bar] | ||
return compiled_host_info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters