2018-11-07 02:13:37 +01:00
|
|
|
#!/usr/bin/env python3
|
2020-09-09 01:35:13 +02:00
|
|
|
import argparse
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
import filecmp
|
2018-11-07 02:13:37 +01:00
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import sys
|
2022-09-22 22:09:34 +02:00
|
|
|
from typing import Dict, List, Tuple, Union
|
2018-11-07 02:13:37 +01:00
|
|
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
sys.path.append(BASE_DIR)
|
|
|
|
from scripts.lib.setup_path import setup_path
|
|
|
|
|
|
|
|
setup_path()
|
|
|
|
|
2020-09-15 02:01:33 +02:00
|
|
|
from scripts.lib.zulip_tools import get_config_file, get_tornado_ports
|
2018-11-07 02:13:37 +01:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2022-09-01 00:46:08 +02:00
|
|
|
def nginx_quote(s: str) -> str:
|
|
|
|
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-11-07 02:13:37 +01:00
|
|
|
|
|
|
|
# Basic system to do Tornado sharding. Writes two output .tmp files that need
|
|
|
|
# to be renamed to the following files to finalize the changes:
|
2022-09-01 00:46:08 +02:00
|
|
|
# * /etc/zulip/nginx_sharding_map.conf; nginx needs to be reloaded after changing.
|
2018-11-07 02:13:37 +01:00
|
|
|
# * /etc/zulip/sharding.json; supervisor Django process needs to be reloaded
|
|
|
|
# after changing. TODO: We can probably make this live-reload by statting the file.
|
|
|
|
#
|
|
|
|
# TODO: Restructure this to automatically generate a sharding layout.
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
def write_updated_configs() -> None:
|
2018-11-07 02:13:37 +01:00
|
|
|
config_file = get_config_file()
|
2020-09-15 02:01:33 +02:00
|
|
|
ports = get_tornado_ports(config_file)
|
|
|
|
|
2022-09-01 01:11:27 +02:00
|
|
|
expected_ports = list(range(9800, ports[-1] + 1))
|
|
|
|
assert ports == expected_ports, f"ports ({ports}) must be contiguous, starting with 9800"
|
2020-09-15 02:01:33 +02:00
|
|
|
|
2022-09-01 00:46:08 +02:00
|
|
|
with open("/etc/zulip/nginx_sharding_map.conf.tmp", "w") as nginx_sharding_conf_f, open(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/etc/zulip/sharding.json.tmp", "w"
|
2021-02-12 08:19:30 +01:00
|
|
|
) as sharding_json_f:
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
if len(ports) == 1:
|
2022-09-01 00:46:08 +02:00
|
|
|
nginx_sharding_conf_f.write('map "" $tornado_server {\n')
|
|
|
|
nginx_sharding_conf_f.write(" default http://tornado;\n")
|
|
|
|
nginx_sharding_conf_f.write("}\n")
|
2021-02-12 08:20:45 +01:00
|
|
|
sharding_json_f.write("{}\n")
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
return
|
|
|
|
|
2023-07-25 07:31:50 +02:00
|
|
|
nginx_sharding_conf_f.write("map $host $tornado_server {\n")
|
2022-09-01 00:46:08 +02:00
|
|
|
nginx_sharding_conf_f.write(" default http://tornado9800;\n")
|
2022-09-22 22:09:34 +02:00
|
|
|
shard_map: Dict[str, Union[int, List[int]]] = {}
|
|
|
|
shard_regexes: List[Tuple[str, Union[int, List[int]]]] = []
|
2021-02-12 08:19:30 +01:00
|
|
|
external_host = subprocess.check_output(
|
2021-02-12 08:20:45 +01:00
|
|
|
[os.path.join(BASE_DIR, "scripts/get-django-setting"), "EXTERNAL_HOST"],
|
2022-01-22 07:52:54 +01:00
|
|
|
text=True,
|
2021-02-12 08:19:30 +01:00
|
|
|
).strip()
|
2022-09-01 01:11:27 +02:00
|
|
|
for key, shards in config_file["tornado_sharding"].items():
|
|
|
|
if key.endswith("_regex"):
|
2022-09-22 22:09:34 +02:00
|
|
|
ports = [int(port) for port in key[: -len("_regex")].split("_")]
|
|
|
|
shard_regexes.append((shards, ports[0] if len(ports) == 1 else ports))
|
2022-09-01 01:11:27 +02:00
|
|
|
nginx_sharding_conf_f.write(
|
2022-09-22 22:09:34 +02:00
|
|
|
f" {nginx_quote('~*' + shards)} http://tornado{'_'.join(map(str, ports))};\n"
|
2022-09-01 01:11:27 +02:00
|
|
|
)
|
|
|
|
else:
|
2022-09-22 22:09:34 +02:00
|
|
|
ports = [int(port) for port in key.split("_")]
|
2022-09-01 01:11:27 +02:00
|
|
|
for shard in shards.split():
|
2021-02-12 08:20:45 +01:00
|
|
|
if "." in shard:
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
host = shard
|
|
|
|
else:
|
|
|
|
host = f"{shard}.{external_host}"
|
|
|
|
assert host not in shard_map, f"host {host} duplicated"
|
2022-09-22 22:09:34 +02:00
|
|
|
shard_map[host] = ports[0] if len(ports) == 1 else ports
|
|
|
|
nginx_sharding_conf_f.write(
|
|
|
|
f" {nginx_quote(host)} http://tornado{'_'.join(map(str, ports))};\n"
|
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
nginx_sharding_conf_f.write("\n")
|
2022-09-01 00:46:08 +02:00
|
|
|
nginx_sharding_conf_f.write("}\n")
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
|
2022-09-01 01:11:27 +02:00
|
|
|
data = {"shard_map": shard_map, "shard_regexes": shard_regexes}
|
|
|
|
sharding_json_f.write(json.dumps(data) + "\n")
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description="Adjust Tornado sharding configuration",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
2021-02-12 08:19:30 +01:00
|
|
|
"--errors-ok",
|
|
|
|
action="store_true",
|
|
|
|
help="Exits 1 if there are no changes; if there are errors or changes, exits 0.",
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
)
|
|
|
|
options = parser.parse_args()
|
2018-11-07 02:13:37 +01:00
|
|
|
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
config_file_path = "/etc/zulip"
|
2022-09-01 00:46:08 +02:00
|
|
|
base_files = ["nginx_sharding_map.conf", "sharding.json"]
|
tornado: Remove fingerprinting, write out .tmp files always.
Fingerprinting the config is somewhat brittle -- it requires either
custom bootstrapping for old (fingerprint-less) configs, and may have
false-positives.
Since generating the config is lightweight, do so into the .tmp files,
and compare the output to the originals to determine if there are
changes to apply.
In order to both surface errors, as well as notify the user in case a
restart is necessary, we must run it twice. The `onlyif`
functionality cannot show configuration errors to the user, only
determine if the command runs or not. We thus run the command once,
judging errors as "interesting" enough to run the actual command,
whose failure will be verbose in Puppet and halt any steps that depend
on it.
Removing the `onlyif` would result in `stage_updated_sharding` showing
up in the output of every Puppet run, which obscures the important
messages it displays when an update to sharding is necessary.
Removing the `command` (e.g. making it an `echo`) would result in
removing the ability to report configuration errors. We thus have no
choice but to run it twice; this is thankfully low-overhead.
2020-09-24 00:58:30 +02:00
|
|
|
full_real_paths = [f"{config_file_path}/{filename}" for filename in base_files]
|
|
|
|
full_new_paths = [f"{filename}.tmp" for filename in full_real_paths]
|
|
|
|
try:
|
|
|
|
write_updated_configs()
|
|
|
|
for old, new in zip(full_real_paths, full_new_paths):
|
|
|
|
if not filecmp.cmp(old, new):
|
|
|
|
# There are changes; leave .tmp files and exit 0
|
|
|
|
if "SUPPRESS_SHARDING_NOTICE" not in os.environ:
|
|
|
|
print("===> Updated sharding; run scripts/refresh-sharding-and-restart")
|
|
|
|
sys.exit(0)
|
|
|
|
# No changes; clean up and exit 1
|
|
|
|
for filename in full_new_paths:
|
|
|
|
os.unlink(filename)
|
|
|
|
sys.exit(1)
|
|
|
|
except AssertionError as e:
|
|
|
|
# Clean up whichever files we made
|
|
|
|
for filename in full_new_paths:
|
|
|
|
if os.path.exists(filename):
|
|
|
|
os.unlink(filename)
|
|
|
|
if options.errors_ok:
|
|
|
|
sys.exit(0)
|
|
|
|
else:
|
|
|
|
print(e, file=sys.stderr)
|
|
|
|
sys.exit(2)
|