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

Make Labcontroller compatible with Python 2 and Python 3 #227

Closed
wants to merge 81 commits into from
Closed
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
557e290
feat: enable lab-controller build for Python 3
StykMartin Jan 19, 2024
6783017
fix(async): rename module to avoid use of reserved keyword
StykMartin Jan 19, 2024
046ec78
fix(test): use range instead of xrange
StykMartin Jan 19, 2024
b3b291c
fix(test): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
8bd61eb
fix(watchdog): use six to provide xmlrpc client
StykMartin Jan 19, 2024
59ca7ed
fix(watchdog): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
2d257ed
chore(watchdog): format code and sort imports
StykMartin Jan 19, 2024
d015ee2
chore(utils): format code and sort imports
StykMartin Jan 19, 2024
97f1633
fix(transfer): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
160f2d8
chore(transfer): format code and sort imports
StykMartin Jan 19, 2024
e182e16
fix(transfer): handle SSL error on Python 6
StykMartin Jan 19, 2024
fe53e5a
chore(test): format code and sort imports
StykMartin Jan 19, 2024
392ae39
fix(test): use proper octal format
StykMartin Jan 19, 2024
1babbbe
fix(pxemenu): use xmlrpc client and urllib from six
StykMartin Jan 19, 2024
b365a10
chore(pxemenu): format code and sort imports
StykMartin Jan 19, 2024
6a00b9a
fix(proxy): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
f8c3eea
fix(proxy): use xmlrpc move from six
StykMartin Jan 19, 2024
4698017
chore(proxy): format code and sort imports
StykMartin Jan 19, 2024
df27135
fix(provision): use xmlrpc move from six
StykMartin Jan 19, 2024
257de1c
fix(provision): use unicode from six
StykMartin Jan 19, 2024
97b3d7b
fix(provision): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
ceb0a39
fix(provision): handle iterators and dict with six
StykMartin Jan 19, 2024
cd9aecd
chore(provision): format code and sort imports
StykMartin Jan 19, 2024
f315c74
fix(netboot): import StringIO from six
StykMartin Jan 19, 2024
087709f
fix(netboot): use urllib from six
StykMartin Jan 19, 2024
08a8953
fix(netboot): use file mode compatible with six
StykMartin Jan 19, 2024
a078345
chore(netboot): format code and sort imports
StykMartin Jan 19, 2024
13505c8
fix(proxy-main): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
278181b
fix(proxy-main): make xmlrpc imports compatible
StykMartin Jan 19, 2024
8b842b6
chore(proxy-main): format code and sort imports
StykMartin Jan 19, 2024
96efb20
fix(log-storage): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
f9bdd1e
fix(log-storage): use proper octal format
StykMartin Jan 19, 2024
8842505
chore(log-storage): format code and sort imports
StykMartin Jan 19, 2024
40e6bb3
fix(pxemenu): use xmlrpc client and urllib from six
StykMartin Jan 19, 2024
2a9fbe2
chore(expire-distros): format code and sort imports
StykMartin Jan 19, 2024
283e3fd
fix(distro-import): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
ca713cd
fix(distro-import): call fn print
StykMartin Jan 19, 2024
313b0ee
fix(pxemenu): use xmlrpc client,urllib, and configparser from six
StykMartin Jan 19, 2024
9483957
fix(distro-import): use anonymous fn instead string::strip
StykMartin Jan 19, 2024
40a936e
chore(distro-import): format code and sort imports
StykMartin Jan 19, 2024
3919ba2
fix(distro-import): use is to compare None
StykMartin Jan 19, 2024
8857893
fix(distro-import): simplify comparison
StykMartin Jan 19, 2024
8df208e
chore(config): format code and sort imports
StykMartin Jan 19, 2024
8d649ec
fix(concurrency): update exception syntax for Python 3 compatibility
StykMartin Jan 19, 2024
1d2f31c
chore(concurrency): format code and sort imports
StykMartin Jan 19, 2024
daef1d6
chore(clear-netboot): format code and sort imports
StykMartin Jan 19, 2024
d1fce19
tests: use pytest w/ python3
StykMartin Jan 19, 2024
fa58bd9
fix(proxy): use absolute import to import utils
StykMartin Jan 19, 2024
5069073
ci: run unit tests during pull requests
StykMartin Jan 19, 2024
c14e540
fix(provision): use datetime fn instead on using custom
StykMartin Jan 19, 2024
75bef29
fix(common-helpers): always encode unicode to str
StykMartin Jan 19, 2024
497f87b
fix(spec): add werkzeug to builddeps
StykMartin Jan 19, 2024
964772c
fix(test): use file mode compatible with python six
StykMartin Jan 19, 2024
c0736c4
fix(test): always write bytes to test images
StykMartin Jan 19, 2024
9cc592f
fix(test): drop deprecated fn names
StykMartin Jan 19, 2024
12e8abc
fix(concurrency): wait till fileno is ready
StykMartin Jan 21, 2024
a06a82f
fix(concurrency): decode bytes
StykMartin Jan 21, 2024
b89bd27
test: extend test suite to validate concurrency
StykMartin Jan 21, 2024
3ac3e93
fix(proxy-main): use pywsgi instead of deprecated wsgi
StykMartin Jan 21, 2024
3fe496a
test: refactor _assert_process_group_is_removed to use psutil
StykMartin Jan 21, 2024
d83fbc9
ci: run init inside the container that forwards signals
StykMartin Jan 21, 2024
359066a
ci: define timeout for unit and integration tests
StykMartin Jan 21, 2024
4d08ee5
ci: run unit test on CentOS 9 Stream
StykMartin Jan 21, 2024
491b538
ci: show python environment
StykMartin Jan 21, 2024
af764fe
chore: remove init.d configuration
StykMartin Jan 21, 2024
0693013
Make source code cloneable on Windows
JohnVillalovos Dec 4, 2021
0d8c870
chore: remove unused check_output
StykMartin Jan 21, 2024
f8262f1
fix(proxy): explicitly convert ascii_control_chars to list for Py3 co…
StykMartin Jan 21, 2024
138a41d
fix(proxy): use raw string notation for ANSI escape code regex
StykMartin Jan 21, 2024
1932974
fix: use warning instead of deprecated warn in logger
StykMartin Jan 21, 2024
451e548
chore: noqa for broad exceptions in main loops
StykMartin Jan 21, 2024
11d6660
refactor(watchdog): remove shadowing for variable greenlet
StykMartin Jan 21, 2024
f6e8b00
fix(watchdog): handle missing 'Running' task in recipe abort process
StykMartin Jan 21, 2024
932c4aa
refactor(utils): remove unused code
StykMartin Jan 21, 2024
ab2bd02
refactor(test): rename 'id' to 'entity_id' to avoid shadowing built-i…
StykMartin Jan 21, 2024
3c38986
refactor(proxy): always close hub
StykMartin Jan 21, 2024
0ae7e70
docs: generate man page for beaker-import
StykMartin Jan 21, 2024
7062f60
ci: execute unit tests on fedora
StykMartin Jan 21, 2024
584f728
build: manage logrotate and log dir for py3 targets
StykMartin Jan 21, 2024
d84bcdd
refactor: use absolute import for utility bkr utility library
StykMartin Jan 21, 2024
20896e9
fix(provision): encode power env fields on python 2
StykMartin Jan 21, 2024
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
14 changes: 11 additions & 3 deletions LabController/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

BKR_PY3 ?= 0
COMMAND := python2

ifeq ($(BKR_PY3),1)
COMMAND := python3
endif


.PHONY: build
build:
python2 setup.py build
$(COMMAND) setup.py build

.PHONY: install
install: build
python2 setup.py install -O1 --skip-build --root $(DESTDIR)
$(COMMAND) setup.py install -O1 --skip-build --root $(DESTDIR)

.PHONY: clean
clean:
python2 setup.py clean
$(COMMAND) setup.py clean
rm -rf build

.PHONY: check
Expand Down
11 changes: 8 additions & 3 deletions LabController/setup.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
from setuptools import setup, find_packages
import commands
from glob import glob

try:
Copy link
Contributor

Choose a reason for hiding this comment

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

Martin: Further down in this file there exists a 'classifier' section which defines the following which perhaps you'd like to change?
'Programming Language :: Python :: 2.7',
I was looking at this file today and thought of you.

from subprocess import getstatusoutput
except ImportError:
from commands import getstatusoutput


def systemd_unit_dir():
status, output = commands.getstatusoutput('pkg-config --variable systemdsystemunitdir systemd')
status, output = getstatusoutput('pkg-config --variable systemdsystemunitdir systemd')
if status or not output:
return None # systemd not found
return output.strip()

def systemd_tmpfiles_dir():
# There doesn't seem to be a specific pkg-config variable for this
status, output = commands.getstatusoutput('pkg-config --variable prefix systemd')
status, output = getstatusoutput('pkg-config --variable prefix systemd')
if status or not output:
return None # systemd not found
return output.strip() + '/lib/tmpfiles.d'
Expand Down
22 changes: 11 additions & 11 deletions LabController/src/bkr/labcontroller/expire_distros.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
# (at your option) any later version.

import sys, os
import xmlrpclib
import urllib2
import urlparse

from six.moves import urllib
from six.moves import xmlrpc_client


def check_http(url):
try:
urllib2.urlopen(url, timeout=120)
urllib.request.urlopen(url, timeout=120)
return True
except urllib2.HTTPError as e:
except urllib.error.HTTPError as e:
if e.code in (404, 410):
return False
else:
Expand All @@ -23,9 +23,9 @@ def check_http(url):

def check_ftp(url):
try:
urllib2.urlopen(url, timeout=120)
urllib.request.urlopen(url, timeout=120)
return True
except urllib2.URLError as e:
except urllib.error.URLError as e:
if '550' in e.reason:
return False
else:
Expand All @@ -41,7 +41,7 @@ def check_nfs(tree):
Make sure the tree is accessible, check that the server is up first.
"""

_, nfs_server, nfs_path, _, _, _ = urlparse.urlparse(tree)
_, nfs_server, nfs_path, _, _, _ = urllib.parse.urlparse(tree)
# Beaker uses a non-standard syntax for NFS URLs, inherited from Cobbler:
# nfs://server:/path
# so we need to strip a trailing colon from the hostname portion.
Expand All @@ -64,7 +64,7 @@ def check_url(url):
Returns True if the given URL exists.
"""

scheme = urlparse.urlparse(url).scheme
scheme = urllib.parse.urlparse(url).scheme
if scheme == 'nfs' or scheme.startswith('nfs+'):
return check_nfs(url)
elif scheme == 'http' or scheme == 'https':
Expand All @@ -79,7 +79,7 @@ def check_all_trees(ignore_errors=False,
dry_run=False,
lab_controller='http://localhost:8000',
remove_all=False):
proxy = xmlrpclib.ServerProxy(lab_controller, allow_none=True)
proxy = xmlrpc_client.ServerProxy(lab_controller, allow_none=True)
rdistro_trees = []
distro_trees = proxy.get_distro_trees()
if not remove_all:
Expand All @@ -93,7 +93,7 @@ def check_all_trees(ignore_errors=False,
print('{0} is missing [Distro Tree ID {1}]'.format(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be " instead of ' in this section? Looks like this change wasn't done in this file.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's covered in another commit.

url,
distro_tree['distro_tree_id']))
except (urllib2.URLError, urllib2.HTTPError, NFSServerInaccessible) as e:
except (urllib.error.URLError, urllib.error.HTTPError, NFSServerInaccessible) as e:
if ignore_errors:
# suppress exception, assume the tree still exists
accessible = True
Expand Down
142 changes: 87 additions & 55 deletions LabController/src/bkr/labcontroller/log_storage.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# We are talking about job logs here, not logs produced by the daemons.

import os, os.path
import errno
import os
import os.path

from bkr.common.helpers import makedirs_ignore

class LogFile(object):

class LogFile(object):
def __init__(self, path, register_func, create=True):
self.path = path #: absolute path where the log will be stored
self.register_func = register_func #: called only if the file was created
self.create = create #: create the file if it doesn't exist
self.path = path #: absolute path where the log will be stored
self.register_func = register_func #: called only if the file was created
self.create = create #: create the file if it doesn't exist

def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.path)
return "%s(%r)" % (self.__class__.__name__, self.path)

def open_ro(self):
"""
If you just want to read the log, call this instead of entering the context manager.
"""
return open(self.path, 'r')
return open(self.path, "r")

def __enter__(self):
makedirs_ignore(os.path.dirname(self.path), 0755)
makedirs_ignore(os.path.dirname(self.path), 0o755)
created = False
if self.create:
try:
# stdio does not have any mode string which corresponds to this
# stdio does not have any mode string which corresponds to this
Copy link
Contributor

Choose a reason for hiding this comment

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

You must be running a script to produce these changes. They're too consistent to be humanly done.

# combination of flags, so we have to use raw os.open :-(
fd = os.open(self.path, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0644)
fd = os.open(self.path, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o644)
created = True
except (OSError, IOError), e:
except (OSError, IOError) as e:
if e.errno != errno.EEXIST:
raise
fd = os.open(self.path, os.O_RDWR)
else:
fd = os.open(self.path, os.O_RDWR)
try:
self.f = os.fdopen(fd, 'r+')
self.f = os.fdopen(fd, "r+")
except Exception:
os.close(fd)
raise
Expand All @@ -65,68 +66,99 @@ def truncate(self, size):

def update_chunk(self, data, offset):
if offset < 0:
raise ValueError('Offset cannot be negative')
raise ValueError("Offset cannot be negative")
self.f.seek(offset, os.SEEK_SET)
# XXX the original uploadFile acquires an exclusive lock while writing,
# XXX the original uploadFile acquires an exclusive lock while writing,
# for no reason that I can discern
self.f.write(data)
self.f.flush()


class LogStorage(object):

"""
Handles storage of job logs on the local filesystem.

The old XML-RPC API doesn't include the recipe ID with the task or result
upload calls. So for now, everything is stored flat. Eventually it would be
The old XML-RPC API doesn't include the recipe ID with the task or result
upload calls. So for now, everything is stored flat. Eventually it would be
nice to arrange things hierarchically with everything under recipe instead.
"""

def __init__(self, base_dir, base_url, hub):
self.base_dir = base_dir
if not base_url.endswith('/'):
base_url += '/' # really it is always a directory
if not base_url.endswith("/"):
base_url += "/" # really it is always a directory
self.base_url = base_url
self.hub = hub

def recipe(self, recipe_id, path, create=True):
path = os.path.normpath(path.lstrip('/'))
if path.startswith('../'):
raise ValueError('Upload path not allowed: %s' % path)
recipe_base_dir = os.path.join(self.base_dir, 'recipes',
(recipe_id[:-3] or '0') + '+', recipe_id, '')
recipe_base_url = '%srecipes/%s+/%s/' % (self.base_url,
recipe_id[:-3] or '0', recipe_id)
return LogFile(os.path.join(recipe_base_dir, path),
lambda: self.hub.recipes.register_file(recipe_base_url,
recipe_id, os.path.dirname(path), os.path.basename(path),
recipe_base_dir),
create=create)
path = os.path.normpath(path.lstrip("/"))
if path.startswith("../"):
raise ValueError("Upload path not allowed: %s" % path)
recipe_base_dir = os.path.join(
self.base_dir, "recipes", (recipe_id[:-3] or "0") + "+", recipe_id, ""
)
recipe_base_url = "%srecipes/%s+/%s/" % (
self.base_url,
recipe_id[:-3] or "0",
recipe_id,
)
return LogFile(
os.path.join(recipe_base_dir, path),
lambda: self.hub.recipes.register_file(
recipe_base_url,
recipe_id,
os.path.dirname(path),
os.path.basename(path),
recipe_base_dir,
),
create=create,
)

def task(self, task_id, path, create=True):
path = os.path.normpath(path.lstrip('/'))
if path.startswith('../'):
raise ValueError('Upload path not allowed: %s' % path)
task_base_dir = os.path.join(self.base_dir, 'tasks',
(task_id[:-3] or '0') + '+', task_id, '')
task_base_url = '%stasks/%s+/%s/' % (self.base_url,
task_id[:-3] or '0', task_id)
return LogFile(os.path.join(task_base_dir, path),
lambda: self.hub.recipes.tasks.register_file(task_base_url,
task_id, os.path.dirname(path), os.path.basename(path),
task_base_dir),
create=create)
path = os.path.normpath(path.lstrip("/"))
if path.startswith("../"):
raise ValueError("Upload path not allowed: %s" % path)
task_base_dir = os.path.join(
self.base_dir, "tasks", (task_id[:-3] or "0") + "+", task_id, ""
)
task_base_url = "%stasks/%s+/%s/" % (
self.base_url,
task_id[:-3] or "0",
task_id,
)
return LogFile(
os.path.join(task_base_dir, path),
lambda: self.hub.recipes.tasks.register_file(
task_base_url,
task_id,
os.path.dirname(path),
os.path.basename(path),
task_base_dir,
),
create=create,
)

def result(self, result_id, path, create=True):
path = os.path.normpath(path.lstrip('/'))
if path.startswith('../'):
raise ValueError('Upload path not allowed: %s' % path)
result_base_dir = os.path.join(self.base_dir, 'results',
(result_id[:-3] or '0') + '+', result_id, '')
result_base_url = '%sresults/%s+/%s/' % (self.base_url,
result_id[:-3] or '0', result_id)
return LogFile(os.path.join(result_base_dir, path),
lambda: self.hub.recipes.tasks.register_result_file(result_base_url,
result_id, os.path.dirname(path), os.path.basename(path),
result_base_dir),
create=create)
path = os.path.normpath(path.lstrip("/"))
if path.startswith("../"):
raise ValueError("Upload path not allowed: %s" % path)
result_base_dir = os.path.join(
self.base_dir, "results", (result_id[:-3] or "0") + "+", result_id, ""
)
result_base_url = "%sresults/%s+/%s/" % (
self.base_url,
result_id[:-3] or "0",
result_id,
)
return LogFile(
os.path.join(result_base_dir, path),
lambda: self.hub.recipes.tasks.register_result_file(
result_base_url,
result_id,
os.path.dirname(path),
os.path.basename(path),
result_base_dir,
),
create=create,
)
Loading