Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activate Red Hat subscription on demand #3426

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions backend/copr_backend/background_worker_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,25 @@ def _check_copr_builder(self):
raise BuildRetry("Minimum version for builder is {}"
.format(MIN_BUILDER_VERSION))

def _check_mock_config(self):
config = "/etc/mock/{}.cfg".format(self.job.chroot)
command = "/usr/bin/test -f " + config
if self.job.chroot == "srpm-builds":
return
if self.ssh.run(command):
raise BuildRetry("Chroot config {} not found".format(config))

def _check_vm(self):
"""
Check that the VM is OK to start the build
"""
self.log.info("Checking that builder machine is OK")
self._check_copr_builder()
self._check_mock_config()

# The output won't be live and will appear only after this command
# finishes. Making it live is nontrivial but we have a good code for
# doing so in `resallocserver.manager.run_command`. Praiskup plans to
# generalize it into a separate package that we could eventually use
# here.
cmd = "copr-builder-ready " + self.job.chroot
rc, stdout, stderr = self.ssh.run_expensive(
cmd, subprocess_timeout=660)
self.log.info(stdout)
if rc:
self.log.info(stderr)
raise BuildRetry("Builder wasn't ready, trying a new one")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a nit: separate method would be nice


def _fill_build_info_file(self):
"""
Expand Down
6 changes: 3 additions & 3 deletions backend/tests/test_background_worker_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,12 +695,12 @@ class _SideEffect:
def __call__(self):
self.counter += 1
if self.counter == 1:
return (1, "err stdout", "err stderr")
return (1, b"err stdout", "err stderr")
return (0, "", "")

config = f_build_rpm_case
ssh = config.ssh
ssh.set_command("/usr/bin/test -f /etc/mock/fedora-30-x86_64.cfg",
ssh.set_command("copr-builder-ready fedora-30-x86_64",
0, "", "", return_action=_SideEffect())
worker = config.bw
worker.process()
Expand Down Expand Up @@ -859,7 +859,7 @@ def test_failed_build_retry(f_build_rpm_case, caplog):
hosts[index].hostname = "1.2.3." + str(index)
rhf.return_value.get_host.side_effect = hosts
ssh = config.ssh
ssh.set_command("/usr/bin/test -f /etc/mock/fedora-30-x86_64.cfg",
ssh.set_command("copr-builder-ready fedora-30-x86_64",
1, "", "not found")

config.bw.process()
Expand Down
4 changes: 2 additions & 2 deletions backend/tests/testlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ def __init__(self, user=None, host=None, config_file=None, log=None):
self.commands = {}
self.set_command(COMMANDS["rpm_q_builder"],
0, "666\n", "")
self.set_command("/usr/bin/test -f /etc/mock/fedora-30-x86_64.cfg",
0, "", "")
self.set_command("copr-builder-ready fedora-30-x86_64", 0, "", "")
self.set_command("copr-builder-ready srpm-builds", 0, "", "")
self.set_command("copr-rpmbuild-log",
0, "build log stdout\n", "build log stderr\n")
self.resultdir = "fedora-30-x86_64/00848963-example"
Expand Down
94 changes: 94 additions & 0 deletions rpmbuild/bin/copr-builder-ready
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#! /usr/bin/python3

"""
Final checks that the builder machine is ready to be used

Everything printed to STDOUT will be redirected to the copr-backend logs,
STDERR will be ignored.
"""

import os
import sys
import time
from fnmatch import fnmatch
from copr_rpmbuild.config import Config


def check_mock_config(chroot):
"""
Does the mock config for this chroot exist?
"""
if chroot == "srpm-builds":
return

config = "/etc/mock/{}.cfg".format(chroot)
if os.path.isfile(config):
praiskup marked this conversation as resolved.
Show resolved Hide resolved
return

print("Chroot config {} not found".format(config))
sys.exit(1)


def subscription_required(chroot):
"""
Is subscription required for this task?
"""
config = Config()
config.load_config()

for pattern in config.rhsm:
if fnmatch(chroot, pattern):
return True
return False


def active_subscription():
"""
Is subscription active on this system?
"""
# There are standard-ish ways for checking whether the subscription is
# active. No silver bullet, all of them have trade-offs.
# - Checking the existence of `/etc/pki/consumer/cert.pem` file
# - Checking the existence of `/etc/pki/entitlement/*.pem` files
# - Exit code from `subscription-manager status`
# - Exit code from `subscription-manager identity`
# We don't want to rely on any of them. We use a custom daemon for
# registering the system on the background. Once it is done,
# it creates a file.
return os.path.exists("/run/copr-builder/rhsm-subscribed")


def wait_for_subscription(timeout=600):
"""
Wait until this system has an active subscription

Activating Red Hat subscription may take a lot of time and historically, the
subscription service used to be unreliable, so we should wait for the
subscription only when necessary.
"""
start = time.time()
attempt = 1
while True:
print("Checking Red Hat subscription (attempt #{0})".format(attempt))
if active_subscription():
print("Red Hat subscription active")
return
if time.time() > start + timeout:
print("Waiting for Red Hat subscription timeouted!")
sys.exit(1)
time.sleep(30)
attempt += 1


def main():
"""
The entrypoint for this script
"""
chroot = sys.argv[1]
check_mock_config(chroot)
if subscription_required(chroot):
wait_for_subscription()


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions rpmbuild/copr-rpmbuild.spec
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ EOF
%_bindir/copr-builder-cleanup
%_bindir/copr-builder-rhsm-subscribe
%_bindir/copr-builder-rhsm-subscribe-daemon
%_bindir/copr-builder-ready
%_sysconfdir/copr-builder
%dir %mock_config_overrides
%doc %mock_config_overrides/README
Expand Down
5 changes: 5 additions & 0 deletions rpmbuild/copr-rpmbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@
# cute
# multiline
# snippet
#
# Chroots that require active Red Hat subscription
# rhsm:
# - rhel-*
# - epel-*
3 changes: 3 additions & 0 deletions rpmbuild/copr_rpmbuild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ class Config:
"""
Configuration class for copr-rpmbuild
"""

def __init__(self):
self.tags_to_mock_snippet = []
self.rhsm = []

def load_config(self):
"""
Expand All @@ -27,3 +29,4 @@ def load_config(self):
pass

self.tags_to_mock_snippet = config_data.get("tags_to_mock_snippet", [])
self.rhsm = config_data.get("rhsm", [])