Skip to content

Commit

Permalink
[server] Support configuration file
Browse files Browse the repository at this point in the history
  • Loading branch information
csordasmarton committed May 7, 2020
1 parent 6adf0e7 commit 132eb46
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 28 deletions.
16 changes: 14 additions & 2 deletions docs/web/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,13 @@ or via the `CodeChecker cmd` command-line client.
```
usage: CodeChecker server [-h] [-w WORKSPACE] [-f CONFIG_DIRECTORY]
[--host LISTEN_ADDRESS] [-v PORT] [--not-host-only]
[--skip-db-cleanup] [--config CONFIG_FILE]
[--sqlite SQLITE_FILE | --postgresql]
[--dbaddress DBADDRESS] [--dbport DBPORT]
[--dbusername DBUSERNAME] [--dbname DBNAME]
[--reset-root] [--force-authentication]
[-l | -s | --stop-all]
[-l | -r | -s | --stop-all]
[--db-status STATUS | --db-upgrade-schema PRODUCT_TO_UPGRADE | --db-force-upgrade]
[--verbose {info,debug,debug_analyzer}]
The CodeChecker Web server is used to handle the storage and navigation of
Expand Down Expand Up @@ -300,7 +302,17 @@ optional arguments:
over the Internet. (Equivalent to specifying '--host
""'.) (default: False)
--skip-db-cleanup Skip performing cleanup jobs on the database like
removing unused files.
removing unused files. (default: False)
--config CONFIG_FILE Allow the configuration from an explicit JSON based
configuration file. The values configured in the
config file will overwrite the values set in the
command line. The format of configuration file is:
{
"server": [
"--workspace=/home/<username>/workspace",
"--port=9090"
]
}. (default: None)
--verbose {info,debug,debug_analyzer}
Set verbosity level.
Expand Down
74 changes: 49 additions & 25 deletions web/server/codechecker_server/cmd/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@

from codechecker_api_shared.ttypes import DBStatus

from codechecker_common import logger
from codechecker_common import output_formatters
from codechecker_common import util
from codechecker_common import arg, logger, output_formatters, util

from codechecker_server import instance_manager, server
from codechecker_server.database import database
Expand All @@ -51,14 +49,13 @@ def get_argparser_ctor_args():

return {
'prog': 'CodeChecker server',
'formatter_class': argparse.ArgumentDefaultsHelpFormatter,
'formatter_class': arg.RawDescriptionDefaultHelpFormatter,

# Description is shown when the command's help is queried directly
'description': "The CodeChecker Web server is used to handle the "
"storage and navigation of analysis results. A "
"started server can be connected to via a Web "
"browser, or by using the 'CodeChecker cmd' "
"command-line client.",
'description': """
The CodeChecker Web server is used to handle the storage and navigation of
analysis results. A started server can be connected to via a Web browser, or
by using the 'CodeChecker cmd' command-line client.""",

# Help is shown when the "parent" CodeChecker command lists the
# individual subcommands.
Expand Down Expand Up @@ -134,6 +131,22 @@ def add_arguments_to_parser(parser):
help="Skip performing cleanup jobs on the database "
"like removing unused files.")

parser.add_argument('--config',
dest='config_file',
required=False,
help="R|Allow the configuration from an explicit JSON "
"based configuration file. The values configured "
"in the config file will overwrite the values "
"set in the command line. The format of "
"configuration file is: \n"
"{\n"
" \"server\": [\n"
" \"--workspace=/home/<username>/workspace\","
"\n"
" \"--port=9090\"\n"
" ]\n"
"}.")

dbmodes = parser.add_argument_group("configuration database arguments")

dbmodes = dbmodes.add_mutually_exclusive_group(required=False)
Expand Down Expand Up @@ -196,11 +209,11 @@ def add_arguments_to_parser(parser):

root_account = parser.add_argument_group(
"root account arguments",
"Servers automatically create a root user to access the server's "
"configuration via the clients. This user is created at first start "
"and saved in the CONFIG_DIRECTORY, and the credentials are printed "
"to the server's standard output. The plaintext credentials are "
"NEVER accessible again.")
"""
Servers automatically create a root user to access the server's configuration
via the clients. This user is created at first start and saved in the
CONFIG_DIRECTORY, and the credentials are printed to the server's standard
output. The plaintext credentials are NEVER accessible again.""")

root_account.add_argument('--reset-root',
dest="reset_root",
Expand Down Expand Up @@ -267,16 +280,17 @@ def add_arguments_to_parser(parser):

database_mgmnt = parser.add_argument_group(
"Database management arguments.",
"""WARNING these commands needs to be called with the same
workspace and configuration arguments as the server so the
configuration database will be found which is required for the
schema migration. Migration can be done without a running server
but pay attention to use the same arguments which will be used to
start the server.
NOTE:
Before migration it is advised to create a full a backup of
the product databases.
""")
"""
WARNING these commands needs to be called with the same workspace and
configuration arguments as the server so the configuration database will be
found which is required for the schema migration. Migration can be done
without a running server but pay attention to use the same arguments which
will be used to start the server.
NOTE:
Before migration it is advised to create a full a backup of the product
databases.
""")

database_mgmnt = database_mgmnt. \
add_mutually_exclusive_group(required=False)
Expand Down Expand Up @@ -414,7 +428,17 @@ def arg_match(options):
# If everything is fine, do call the handler for the subcommand.
main(args)

parser.set_defaults(func=__handle)
parser.set_defaults(func=__handle,
func_process_config_file=process_config_file)


def process_config_file(args):
"""
Handler to get config file options.
"""
if args.config_file and os.path.exists(args.config_file):
cfg = util.load_json_or_empty(args.config_file, default={})
return cfg.get('server', [])


def print_prod_status(prod_status):
Expand Down
51 changes: 51 additions & 0 deletions web/tests/functional/cli_config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# coding=utf-8
# -----------------------------------------------------------------------------
# The CodeChecker Infrastructure
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
# -----------------------------------------------------------------------------

""" Setup for the config test for the web commands. """


import os
import shutil

from libtest import env


# Test workspace should be initialized in this module.
TEST_WORKSPACE = None


def setup_package():
"""Setup the environment for the tests."""

global TEST_WORKSPACE
TEST_WORKSPACE = env.get_workspace('config')

# Set the TEST_WORKSPACE used by the tests.
os.environ['TEST_WORKSPACE'] = TEST_WORKSPACE

# Create a basic CodeChecker config for the tests, this should
# be imported by the tests and they should only depend on these
# configuration options.
codechecker_cfg = {
'workspace': TEST_WORKSPACE,
'check_env': env.test_env(TEST_WORKSPACE),
'viewer_host': 'localhost',
'viewer_product': 'db_cleanup'
}

env.export_test_cfg(TEST_WORKSPACE, {'codechecker_cfg': codechecker_cfg})


def teardown_package():
""" Delete the workspace associated with this test. """

# TODO: If environment variable is set keep the workspace
# and print out the path.
global TEST_WORKSPACE

print("Removing: " + TEST_WORKSPACE)
shutil.rmtree(TEST_WORKSPACE)
103 changes: 103 additions & 0 deletions web/tests/functional/cli_config/test_server_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#
# -----------------------------------------------------------------------------
# The CodeChecker Infrastructure
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
# -----------------------------------------------------------------------------

"""
Test server configuration file.
"""


import json
import multiprocessing
import os
import unittest

from libtest import codechecker
from libtest import env


class TestServerConfig(unittest.TestCase):
_ccClient = None

def setUp(self):

# TEST_WORKSPACE is automatically set by test package __init__.py .
self.test_workspace = os.environ['TEST_WORKSPACE']
self.codechecker_cfg = env.import_codechecker_cfg(self.test_workspace)

test_class = self.__class__.__name__
print('Running ' + test_class + ' tests in ' + self.test_workspace)

# Get the CodeChecker cmd if needed for the tests.
self._codechecker_cmd = env.codechecker_cmd()

self.config_file = os.path.join(self.test_workspace,
"codechecker.json")

def test_valid_config(self):
""" Start server with a valid configuration file. """
with open(self.config_file, 'w+',
encoding="utf-8", errors="ignore") as config_f:
json.dump({
'server': ['--skip-db-cleanup']}, config_f)

event = multiprocessing.Event()
event.clear()

self.codechecker_cfg['viewer_port'] = env.get_free_port()

server_access = \
codechecker.start_server(self.codechecker_cfg, event,
['--config', self.config_file])
event.set()
event.clear()
with open(server_access['server_output_file'], 'r',
encoding='utf-8', errors='ignore') as out:
content = out.read()
self.assertFalse('usage: CodeChecker' in content)

def test_invalid_config(self):
""" Start server with an invalid configuration file. """
with open(self.config_file, 'w+',
encoding="utf-8", errors="ignore") as config_f:
json.dump({
'server': ['--dummy-option']}, config_f)

event = multiprocessing.Event()
event.clear()

self.codechecker_cfg['viewer_port'] = env.get_free_port()

server_access = \
codechecker.start_server(self.codechecker_cfg, event,
['--config', self.config_file])
event.set()
event.clear()
with open(server_access['server_output_file'], 'r',
encoding='utf-8', errors='ignore') as out:
content = out.read()
self.assertTrue('usage: CodeChecker' in content)

def test_empty_config(self):
""" Start server with an empty configuration file. """
with open(self.config_file, 'w+',
encoding="utf-8", errors="ignore") as config_f:
config_f.write("")

event = multiprocessing.Event()
event.clear()

self.codechecker_cfg['viewer_port'] = env.get_free_port()

server_access = \
codechecker.start_server(self.codechecker_cfg, event,
['--config', self.config_file])
event.set()
event.clear()
with open(server_access['server_output_file'], 'r',
encoding='utf-8', errors='ignore') as out:
content = out.read()
self.assertFalse('usage: CodeChecker' in content)
9 changes: 8 additions & 1 deletion web/tests/libtest/codechecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,12 @@ def wait_for_server_start(stdoutfile):
out = f.read()
if "Server waiting for client requests" in out:
return

# Handle error case when the server failed to start and gave
# some error message.
if "usage: CodeChecker" in out:
return

time.sleep(1)
n += 1
print("Waiting for server to start for " + str(n) + " seconds...")
Expand Down Expand Up @@ -631,7 +637,8 @@ def start_server_proc(event, server_cmd, checking_env):

return {
'viewer_host': 'localhost',
'viewer_port': codechecker_cfg['viewer_port']
'viewer_port': codechecker_cfg['viewer_port'],
'server_output_file': server_output_file
}


Expand Down

0 comments on commit 132eb46

Please sign in to comment.