server_utils.py
277 lines
| 9.2 KiB
| text/x-python
|
PythonLexer
r2457 | ||||
r5088 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
r2457 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
import os | ||||
import time | ||||
import tempfile | ||||
import pytest | ||||
r4926 | import subprocess | |||
r4879 | import logging | |||
r5607 | import requests | |||
r4927 | import configparser | |||
r2457 | ||||
from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS | ||||
from rhodecode.tests.utils import is_url_reachable | ||||
r5607 | from rhodecode.tests import console_printer | |||
r2457 | ||||
r4879 | log = logging.getLogger(__name__) | |||
r2457 | ||||
def get_port(pyramid_config): | ||||
config = configparser.ConfigParser() | ||||
config.read(pyramid_config) | ||||
return config.get('server:main', 'port') | ||||
def get_host_url(pyramid_config): | ||||
"""Construct the host url using the port in the test configuration.""" | ||||
r4975 | port = get_port(pyramid_config) | |||
return f'127.0.0.1:{port}' | ||||
r2457 | ||||
def assert_no_running_instance(url): | ||||
if is_url_reachable(url): | ||||
r5607 | console_printer(f"Hint: Usually this means another instance of server " | |||
r4975 | f"is running in the background at {url}.") | |||
pytest.fail(f"Port is not free at {url}, cannot start server at") | ||||
r2457 | ||||
class ServerBase(object): | ||||
_args = [] | ||||
log_file_name = 'NOT_DEFINED.log' | ||||
r5087 | status_url_tmpl = 'http://{host}:{port}/_admin/ops/ping' | |||
r5607 | console_marker = " :warning: [green]pytest-setup[/green] " | |||
r2457 | ||||
r5607 | def __init__(self, config_file, log_file, env): | |||
r2457 | self.config_file = config_file | |||
r4975 | config = configparser.ConfigParser() | |||
config.read(config_file) | ||||
self._config = {k: v for k, v in config['server:main'].items()} | ||||
r2457 | ||||
self._args = [] | ||||
self.log_file = log_file or os.path.join( | ||||
tempfile.gettempdir(), self.log_file_name) | ||||
r5607 | self.env = env | |||
r2457 | self.process = None | |||
self.server_out = None | ||||
r5607 | log.info(f"Using the {self.__class__.__name__} configuration:{config_file}") | |||
r2457 | ||||
if not os.path.isfile(config_file): | ||||
r4975 | raise RuntimeError(f'Failed to get config at {config_file}') | |||
r2457 | ||||
@property | ||||
def command(self): | ||||
return ' '.join(self._args) | ||||
@property | ||||
r4976 | def bind_addr(self): | |||
return '{host}:{port}'.format(**self._config) | ||||
@property | ||||
r2457 | def http_url(self): | |||
template = 'http://{host}:{port}/' | ||||
return template.format(**self._config) | ||||
def host_url(self): | ||||
r4976 | host = get_host_url(self.config_file) | |||
return f'http://{host}' | ||||
r2457 | ||||
def get_rc_log(self): | ||||
with open(self.log_file) as f: | ||||
return f.read() | ||||
r5087 | def assert_message_in_server_logs(self, message): | |||
server_logs = self.get_rc_log() | ||||
assert message in server_logs | ||||
r2462 | def wait_until_ready(self, timeout=30): | |||
r2457 | host = self._config['host'] | |||
port = self._config['port'] | ||||
status_url = self.status_url_tmpl.format(host=host, port=port) | ||||
start = time.time() | ||||
while time.time() - start < timeout: | ||||
try: | ||||
r5607 | requests.get(status_url) | |||
r2457 | break | |||
r5607 | except requests.exceptions.ConnectionError: | |||
r2457 | time.sleep(0.2) | |||
else: | ||||
r2461 | pytest.fail( | |||
r5607 | f"Starting the {self.__class__.__name__} failed or took more than {timeout} seconds." | |||
f"cmd: `{self.command}`" | ||||
) | ||||
r2457 | ||||
r5607 | log.info(f'Server of {self.__class__.__name__} ready at url {status_url}') | |||
r2466 | ||||
r2457 | def shutdown(self): | |||
self.process.kill() | ||||
self.server_out.flush() | ||||
self.server_out.close() | ||||
def get_log_file_with_port(self): | ||||
log_file = list(self.log_file.partition('.log')) | ||||
r5607 | log_file.insert(1, f'-{get_port(self.config_file)}') | |||
r2457 | log_file = ''.join(log_file) | |||
return log_file | ||||
class RcVCSServer(ServerBase): | ||||
""" | ||||
Represents a running VCSServer instance. | ||||
""" | ||||
r5607 | log_file_name = 'rhodecode-vcsserver.log' | |||
r2457 | status_url_tmpl = 'http://{host}:{port}/status' | |||
r5607 | def __init__(self, config_file, log_file=None, workers='3', env=None, info_prefix=''): | |||
super(RcVCSServer, self).__init__(config_file, log_file, env) | ||||
self.info_prefix = info_prefix | ||||
r4976 | self._args = [ | |||
r5087 | 'gunicorn', | |||
'--bind', self.bind_addr, | ||||
r5468 | '--worker-class', 'sync', | |||
'--threads', '1', | ||||
'--backlog', '8', | ||||
r5087 | '--timeout', '300', | |||
'--workers', workers, | ||||
r4976 | '--paste', self.config_file] | |||
r2457 | ||||
def start(self): | ||||
env = os.environ.copy() | ||||
self.log_file = self.get_log_file_with_port() | ||||
self.server_out = open(self.log_file, 'w') | ||||
host_url = self.host_url() | ||||
assert_no_running_instance(host_url) | ||||
r5607 | console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-vcsserver starting at: {host_url}') | |||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-vcsserver command: {self.command}') | ||||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-vcsserver logfile: {self.log_file}') | ||||
console_printer() | ||||
r2457 | ||||
r4926 | self.process = subprocess.Popen( | |||
r2457 | self._args, bufsize=0, env=env, | |||
stdout=self.server_out, stderr=self.server_out) | ||||
class RcWebServer(ServerBase): | ||||
""" | ||||
Represents a running RCE web server used as a test fixture. | ||||
""" | ||||
r5607 | log_file_name = 'rhodecode-ce.log' | |||
r2457 | status_url_tmpl = 'http://{host}:{port}/_admin/ops/ping' | |||
r5607 | def __init__(self, config_file, log_file=None, workers='2', env=None, info_prefix=''): | |||
super(RcWebServer, self).__init__(config_file, log_file, env) | ||||
self.info_prefix = info_prefix | ||||
r2457 | self._args = [ | |||
r5087 | 'gunicorn', | |||
'--bind', self.bind_addr, | ||||
r5459 | '--worker-class', 'gthread', | |||
r5468 | '--threads', '4', | |||
'--backlog', '8', | ||||
r5087 | '--timeout', '300', | |||
'--workers', workers, | ||||
r4976 | '--paste', self.config_file] | |||
r2457 | ||||
def start(self): | ||||
env = os.environ.copy() | ||||
r5607 | if self.env: | |||
env.update(self.env) | ||||
r2457 | ||||
self.log_file = self.get_log_file_with_port() | ||||
self.server_out = open(self.log_file, 'w') | ||||
host_url = self.host_url() | ||||
assert_no_running_instance(host_url) | ||||
r5607 | console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-ce starting at: {host_url}') | |||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-ce command: {self.command}') | ||||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-ce logfile: {self.log_file}') | ||||
console_printer() | ||||
r2457 | ||||
r4926 | self.process = subprocess.Popen( | |||
r2457 | self._args, bufsize=0, env=env, | |||
stdout=self.server_out, stderr=self.server_out) | ||||
def repo_clone_url(self, repo_name, **kwargs): | ||||
params = { | ||||
'user': TEST_USER_ADMIN_LOGIN, | ||||
'passwd': TEST_USER_ADMIN_PASS, | ||||
'host': get_host_url(self.config_file), | ||||
'cloned_repo': repo_name, | ||||
} | ||||
params.update(**kwargs) | ||||
r5087 | _url = f"http://{params['user']}:{params['passwd']}@{params['host']}/{params['cloned_repo']}" | |||
r2457 | return _url | |||
r5459 | ||||
def repo_clone_credentials(self, **kwargs): | ||||
params = { | ||||
'user': TEST_USER_ADMIN_LOGIN, | ||||
'passwd': TEST_USER_ADMIN_PASS, | ||||
} | ||||
params.update(**kwargs) | ||||
return params['user'], params['passwd'] | ||||
r5607 | ||||
class CeleryServer(ServerBase): | ||||
log_file_name = 'rhodecode-celery.log' | ||||
status_url_tmpl = 'http://{host}:{port}/_admin/ops/ping' | ||||
def __init__(self, config_file, log_file=None, workers='2', env=None, info_prefix=''): | ||||
super(CeleryServer, self).__init__(config_file, log_file, env) | ||||
self.info_prefix = info_prefix | ||||
self._args = \ | ||||
['celery', | ||||
'--no-color', | ||||
'--app=rhodecode.lib.celerylib.loader', | ||||
'worker', | ||||
'--autoscale=4,2', | ||||
'--max-tasks-per-child=30', | ||||
'--task-events', | ||||
'--loglevel=DEBUG', | ||||
'--ini=' + self.config_file] | ||||
def start(self): | ||||
env = os.environ.copy() | ||||
env['RC_NO_TEST_ENV'] = '1' | ||||
self.log_file = self.get_log_file_with_port() | ||||
self.server_out = open(self.log_file, 'w') | ||||
host_url = "Celery" #self.host_url() | ||||
#assert_no_running_instance(host_url) | ||||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-celery starting at: {host_url}') | ||||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-celery command: {self.command}') | ||||
console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-celery logfile: {self.log_file}') | ||||
console_printer() | ||||
self.process = subprocess.Popen( | ||||
self._args, bufsize=0, env=env, | ||||
stdout=self.server_out, stderr=self.server_out) | ||||
def wait_until_ready(self, timeout=30): | ||||
time.sleep(2) | ||||