Skip to content

Commit

Permalink
Add new settings command (#114)
Browse files Browse the repository at this point in the history
When called without further argument, print the current settings.

When called with argument `key=value`, set the runtime parameter `key` to `value`, and print the new settings.
  • Loading branch information
hannahbast authored Jan 14, 2025
1 parent ff5b96a commit dd95a05
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 70 deletions.
109 changes: 109 additions & 0 deletions src/qlever/commands/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from __future__ import annotations

import json

from termcolor import colored

from qlever.command import QleverCommand
from qlever.log import log
from qlever.util import run_command


class SettingsCommand(QleverCommand):
"""
Class for executing the `settings` command.
"""

def __init__(self):
pass

def description(self) -> str:
return "Show or set server settings (after `qlever start`)"

def should_have_qleverfile(self) -> bool:
return True

def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
return {"server": ["port", "host_name", "access_token"]}

def additional_arguments(self, subparser) -> None:
all_keys = [
"cache-max-num-entries",
"cache-max-size",
"cache-max-size-single-entry",
"default-query-timeout",
"group-by-disable-index-scan-optimizations",
"group-by-hash-map-enabled",
"lazy-index-scan-max-size-materialization",
"lazy-index-scan-num-threads",
"lazy-index-scan-queue-size",
"lazy-result-max-cache-size",
"query-planning-budget",
"service-max-value-rows",
"sort-estimate-cancellation-factor",
"throw-on-unbound-variables",
"use-binsearch-transitive-path",
]
subparser.add_argument(
"runtime_parameter",
nargs="?",
help="Set the given runtime parameter (key=value)"
"; if no argument is given, show all settings",
).completer = lambda **kwargs: [f"{key}=" for key in all_keys]
subparser.add_argument(
"--endpoint_url",
type=str,
help="An arbitrary endpoint URL "
"(overriding the one in the Qleverfile)",
)

def execute(self, args) -> bool:
# Get endpoint URL from command line or Qleverfile.
if args.endpoint_url:
endpoint_url = args.endpoint_url
else:
endpoint_url = f"http://{args.host_name}:{args.port}"

# Construct the `curl` command for getting or setting.
if args.runtime_parameter:
try:
parameter_key, parameter_value = args.runtime_parameter.split(
"="
)
except ValueError:
log.error("Runtime parameter must be given as `key=value`")
return False

curl_cmd = (
f"curl -s {endpoint_url}"
f' --data-urlencode "{parameter_key}={parameter_value}"'
f' --data-urlencode "access-token={args.access_token}"'
)
else:
curl_cmd = (
f"curl -s {endpoint_url}" f" --data-urlencode cmd=get-settings"
)
parameter_key, parameter_value = None, None
self.show(curl_cmd, only_show=args.show)
if args.show:
return True

# Execute the `curl` command. Note that the `get-settings` command
# returns all settings in both scencarios (that is, also when setting a
# parameter).
try:
settings_json = run_command(curl_cmd, return_output=True)
settings_dict = json.loads(settings_json)
except Exception as e:
log.error(f"setting command failed: {e}")
return False
for key, value in settings_dict.items():
print(
colored(
f"{key:<45}: {value}",
"blue"
if parameter_key and key == parameter_key
else None,
)
)
return True
157 changes: 97 additions & 60 deletions src/qlever/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@

# Construct the command line based on the config file.
def construct_command_line(args) -> str:
start_cmd = (f"{args.server_binary}"
f" -i {args.name}"
f" -j {args.num_threads}"
f" -p {args.port}"
f" -m {args.memory_for_queries}"
f" -c {args.cache_max_size}"
f" -e {args.cache_max_size_single_entry}"
f" -k {args.cache_max_num_entries}")
start_cmd = (
f"{args.server_binary}"
f" -i {args.name}"
f" -j {args.num_threads}"
f" -p {args.port}"
f" -m {args.memory_for_queries}"
f" -c {args.cache_max_size}"
f" -e {args.cache_max_size_single_entry}"
f" -k {args.cache_max_num_entries}"
)

if args.timeout:
start_cmd += f" -s {args.timeout}"
Expand All @@ -49,18 +51,21 @@ def kill_existing_server(args) -> bool:
log.info("")
return True


# Run the command in a container
def run_command_in_container(args, start_cmd) -> str:
if not args.server_container:
args.server_container = f"qlever.server.{args.name}"
start_cmd = Containerize().containerize_command(
start_cmd,
args.system, "run -d --restart=unless-stopped",
args.system,
"run -d --restart=unless-stopped",
args.image,
args.server_container,
volumes=[("$(pwd)", "/index")],
ports=[(args.port, args.port)],
working_directory="/index")
working_directory="/index",
)
return start_cmd


Expand All @@ -70,19 +75,23 @@ def check_binary(binary) -> bool:
run_command(f"{binary} --help")
return True
except Exception as e:
log.error(f"Running \"{binary}\" failed, "
f"set `--server-binary` to a different binary or "
f"set `--system to a container system`")
log.error(
f'Running "{binary}" failed, '
f"set `--server-binary` to a different binary or "
f"set `--system to a container system`"
)
log.info("")
log.info(f"The error message was: {e}")
return False


# Set the access token if specified. Try to set the index description
def setting_index_description(access_arg, port, desc) -> bool:
curl_cmd = (f"curl -Gs http://localhost:{port}/api"
f" --data-urlencode \"index-description={desc}\""
f" {access_arg} > /dev/null")
curl_cmd = (
f"curl -Gs http://localhost:{port}/api"
f' --data-urlencode "index-description={desc}"'
f" {access_arg} > /dev/null"
)
log.debug(curl_cmd)
try:
run_command(curl_cmd)
Expand All @@ -94,9 +103,11 @@ def setting_index_description(access_arg, port, desc) -> bool:

# Set the access token if specified. Try to set the text description
def setting_text_description(access_arg, port, text_desc) -> bool:
curl_cmd = (f"curl -Gs http://localhost:{port}/api"
f" --data-urlencode \"text-description={text_desc}\""
f" {access_arg} > /dev/null")
curl_cmd = (
f"curl -Gs http://localhost:{port}/api"
f' --data-urlencode "text-description={text_desc}"'
f" {access_arg} > /dev/null"
)
log.debug(curl_cmd)
try:
run_command(curl_cmd)
Expand All @@ -115,22 +126,35 @@ def __init__(self):
pass

def description(self) -> str:
return ("Start the QLever server (requires that you have built "
"an index with `qlever index` before)")
return (
"Start the QLever server (requires that you have built "
"an index with `qlever index` before)"
)

def should_have_qleverfile(self) -> bool:
return True

def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
return {"data": ["name", "description", "text_description"],
"server": ["server_binary", "host_name", "port",
"access_token", "memory_for_queries",
"cache_max_size", "cache_max_size_single_entry",
"cache_max_num_entries", "num_threads",
"timeout", "only_pso_and_pos_permutations",
"use_patterns", "use_text_index",
"warmup_cmd"],
"runtime": ["system", "image", "server_container"]}
def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
return {
"data": ["name", "description", "text_description"],
"server": [
"server_binary",
"host_name",
"port",
"access_token",
"memory_for_queries",
"cache_max_size",
"cache_max_size_single_entry",
"cache_max_num_entries",
"num_threads",
"timeout",
"only_pso_and_pos_permutations",
"use_patterns",
"use_text_index",
"warmup_cmd",
],
"runtime": ["system", "image", "server_container"],
}

def additional_arguments(self, subparser) -> None:
# subparser.add_argument("--kill-existing-with-same-name",
Expand All @@ -139,16 +163,20 @@ def additional_arguments(self, subparser) -> None:
# help="If a QLever server is already running "
# "with the same name, kill it before "
# "starting a new server")
subparser.add_argument("--kill-existing-with-same-port",
action="store_true",
default=False,
help="If a QLever server is already running "
"on the same port, kill it before "
"starting a new server")
subparser.add_argument("--no-warmup",
action="store_true",
default=False,
help="Do not execute the warmup command")
subparser.add_argument(
"--kill-existing-with-same-port",
action="store_true",
default=False,
help="If a QLever server is already running "
"on the same port, kill it before "
"starting a new server",
)
subparser.add_argument(
"--no-warmup",
action="store_true",
default=False,
help="Do not execute the warmup command",
)

def execute(self, args) -> bool:
# Kill existing server with the same name if so desired.
Expand All @@ -163,8 +191,9 @@ def execute(self, args) -> bool:

# Kill existing server on the same port if so desired.
if args.kill_existing_with_same_port:
if (args.kill_existing_with_same_port and
not kill_existing_server(args)):
if args.kill_existing_with_same_port and not kill_existing_server(
args
):
return False

# Construct the command line based on the config file.
Expand All @@ -188,23 +217,27 @@ def execute(self, args) -> bool:
return False

# Check if a QLever server is already running on this port.
port = args.port
if is_qlever_server_alive(port):
log.error(f"QLever server already running on port {port}")
endpoint_url = f"http://localhost:{args.port}"
if is_qlever_server_alive(endpoint_url):
log.error(f"QLever server already running on {endpoint_url}")
log.info("")
log.info("To kill the existing server, use `qlever stop` "
"or `qlever start` with option "
"--kill-existing-with-same-port`")
log.info(
"To kill the existing server, use `qlever stop` "
"or `qlever start` with option "
"--kill-existing-with-same-port`"
)

# Show output of status command.
args.cmdline_regex = f"^ServerMain.* -p *{port}"
args.cmdline_regex = f"^ServerMain.* -p *{args.port}"
log.info("")
StatusCommand().execute(args)
return False

# Remove already existing container.
if args.system in Containerize.supported_systems() \
and args.kill_existing_with_same_port:
if (
args.system in Containerize.supported_systems()
and args.kill_existing_with_same_port
):
try:
run_command(f"{args.system} rm -f {args.server_container}")
except Exception as e:
Expand All @@ -229,22 +262,26 @@ def execute(self, args) -> bool:
# Tail the server log until the server is ready (note that the `exec`
# is important to make sure that the tail process is killed and not
# just the bash process).
log.info(f"Follow {args.name}.server-log.txt until the server is ready"
f" (Ctrl-C stops following the log, but not the server)")
log.info(
f"Follow {args.name}.server-log.txt until the server is ready"
f" (Ctrl-C stops following the log, but not the server)"
)
log.info("")
tail_cmd = f"exec tail -f {args.name}.server-log.txt"
tail_proc = subprocess.Popen(tail_cmd, shell=True)
while not is_qlever_server_alive(port):
while not is_qlever_server_alive(endpoint_url):
time.sleep(1)

# Set the access token if specified.
access_arg = f"--data-urlencode \"access-token={args.access_token}\""
if (args.description
and not setting_index_description(access_arg, port, args.description)):
access_arg = f'--data-urlencode "access-token={args.access_token}"'
if args.description and not setting_index_description(
access_arg, args.port, args.description
):
return False

if (args.text_description
and not setting_text_description(access_arg, port, args.text_description)):
if args.text_description and not setting_text_description(
access_arg, args.port, args.text_description
):
return False

# Kill the tail process. NOTE: `tail_proc.kill()` does not work.
Expand Down
Loading

0 comments on commit dd95a05

Please sign in to comment.