|
| 1 | +import logging |
| 2 | +import shutil |
| 3 | +from pathlib import Path |
| 4 | +from subprocess import CalledProcessError, check_call, check_output |
| 5 | + |
| 6 | +logger = logging.getLogger(__name__) |
| 7 | + |
| 8 | +HOME = Path("~ubuntu").expanduser() |
| 9 | +REPO_LOCATION = HOME / "error-tracker" |
| 10 | + |
| 11 | + |
| 12 | +def setup_systemd_timer(unit_name, description, command, calendar): |
| 13 | + systemd_unit_location = Path("/") / "etc" / "systemd" / "system" |
| 14 | + systemd_unit_location.mkdir(parents=True, exist_ok=True) |
| 15 | + |
| 16 | + (systemd_unit_location / f"{unit_name}.service").write_text( |
| 17 | + f""" |
| 18 | +[Unit] |
| 19 | +Description={description} |
| 20 | +
|
| 21 | +[Service] |
| 22 | +Type=oneshot |
| 23 | +User=ubuntu |
| 24 | +Environment=PYTHONPATH={HOME}/config |
| 25 | +ExecStart={command} |
| 26 | +""" |
| 27 | + ) |
| 28 | + (systemd_unit_location / f"{unit_name}.timer").write_text( |
| 29 | + f""" |
| 30 | +[Unit] |
| 31 | +Description={description} |
| 32 | +
|
| 33 | +[Timer] |
| 34 | +OnCalendar={calendar} |
| 35 | +Persistent=true |
| 36 | +
|
| 37 | +[Install] |
| 38 | +WantedBy=timers.target |
| 39 | +""" |
| 40 | + ) |
| 41 | + |
| 42 | + check_call(["systemctl", "daemon-reload"]) |
| 43 | + check_call(["systemctl", "enable", "--now", f"{unit_name}.timer"]) |
| 44 | + |
| 45 | + |
| 46 | +class ErrorTracker: |
| 47 | + def __init__(self): |
| 48 | + self.enable_retracer = True |
| 49 | + self.enable_timers = True |
| 50 | + self.enable_daisy = True |
| 51 | + self.enable_web = True |
| 52 | + self.daisy_port = 8000 |
| 53 | + |
| 54 | + def install(self): |
| 55 | + self._install_deps() |
| 56 | + self._install_et() |
| 57 | + |
| 58 | + def _install_et(self): |
| 59 | + shutil.copytree(".", REPO_LOCATION) |
| 60 | + check_call(["chown", "-R", "ubuntu:ubuntu", str(REPO_LOCATION)]) |
| 61 | + |
| 62 | + def get_version(self): |
| 63 | + """Get the retracer version""" |
| 64 | + try: |
| 65 | + version = check_output( |
| 66 | + [ |
| 67 | + "sudo", |
| 68 | + "-u", |
| 69 | + "ubuntu", |
| 70 | + "python3", |
| 71 | + "-c", |
| 72 | + "import errortracker; print(errortracker.__version__)", |
| 73 | + ], |
| 74 | + cwd=REPO_LOCATION / "src", |
| 75 | + ) |
| 76 | + return version.decode() |
| 77 | + except CalledProcessError as e: |
| 78 | + logger.error("Unable to get version (%d, %s)", e.returncode, e.stderr) |
| 79 | + return "unknown" |
| 80 | + |
| 81 | + def _install_deps(self): |
| 82 | + try: |
| 83 | + check_call(["apt-get", "update", "-y"]) |
| 84 | + check_call( |
| 85 | + [ |
| 86 | + "apt-get", |
| 87 | + "install", |
| 88 | + "-y", |
| 89 | + "git", |
| 90 | + "python3-amqp", |
| 91 | + "python3-apport", |
| 92 | + "python3-apt", |
| 93 | + "python3-bson", |
| 94 | + "python3-cassandra", |
| 95 | + "python3-flask", |
| 96 | + "python3-swiftclient", |
| 97 | + ] |
| 98 | + ) |
| 99 | + except CalledProcessError as e: |
| 100 | + logger.debug("Package install failed with return code %d", e.returncode) |
| 101 | + return |
| 102 | + |
| 103 | + def configure(self, config: str): |
| 104 | + config_location = REPO_LOCATION / "src" |
| 105 | + (config_location / "local_config.py").write_text(config) |
| 106 | + |
| 107 | + def configure_daisy(self): |
| 108 | + logger.info("Configuring daisy") |
| 109 | + logger.info("Installing additional daisy dependencies") |
| 110 | + check_call(["apt-get", "install", "-y", "gunicorn"]) |
| 111 | + systemd_unit_location = Path("/") / "etc" / "systemd" / "system" |
| 112 | + systemd_unit_location.mkdir(parents=True, exist_ok=True) |
| 113 | + (systemd_unit_location / "daisy.service").write_text( |
| 114 | + f""" |
| 115 | +[Unit] |
| 116 | +Description=Daisy |
| 117 | +After=network.target |
| 118 | +
|
| 119 | +[Service] |
| 120 | +User=ubuntu |
| 121 | +Group=ubuntu |
| 122 | +WorkingDirectory={REPO_LOCATION}/src |
| 123 | +ExecStart=gunicorn -c {REPO_LOCATION}/src/daisy/gunicorn_config.py 'daisy.app:app' |
| 124 | +Restart=always |
| 125 | +
|
| 126 | +[Install] |
| 127 | +WantedBy=multi-user.target |
| 128 | +""" |
| 129 | + ) |
| 130 | + |
| 131 | + check_call(["systemctl", "daemon-reload"]) |
| 132 | + |
| 133 | + logger.info("enabling systemd units") |
| 134 | + check_call(["systemctl", "enable", "daisy"]) |
| 135 | + |
| 136 | + logger.info("restarting systemd units") |
| 137 | + check_call(["systemctl", "restart", "daisy"]) |
| 138 | + |
| 139 | + def configure_retracer(self, retracer_failed_queue: bool): |
| 140 | + logger.info("Configuring retracer") |
| 141 | + failed = "--failed" if retracer_failed_queue else "" |
| 142 | + # Work around https://bugs.launchpad.net/ubuntu/+source/gdb/+bug/1818918 |
| 143 | + # Apport will not be run as root, thus the included workaround here will hit ENOPERM |
| 144 | + (Path("/") / "usr" / "lib" / "debug" / ".dwz").mkdir(parents=True, exist_ok=True) |
| 145 | + logger.info("Installing additional retracer dependencies") |
| 146 | + check_call( |
| 147 | + [ |
| 148 | + "apt-get", |
| 149 | + "install", |
| 150 | + "-y", |
| 151 | + "apport-retrace", |
| 152 | + "ubuntu-dbgsym-keyring", |
| 153 | + ] |
| 154 | + ) |
| 155 | + |
| 156 | + logger.info("Configuring retracer systemd units") |
| 157 | + systemd_unit_location = Path("/") / "etc" / "systemd" / "system" |
| 158 | + systemd_unit_location.mkdir(parents=True, exist_ok=True) |
| 159 | + ( systemd_unit_location / "[email protected]"). write_text( |
| 160 | + f""" |
| 161 | +[Unit] |
| 162 | +Description=Retracer |
| 163 | +
|
| 164 | +[Service] |
| 165 | +User=ubuntu |
| 166 | +Group=ubuntu |
| 167 | +Environment=PYTHONPATH={REPO_LOCATION}/src |
| 168 | +ExecStart=python3 {REPO_LOCATION}/src/retracer.py --config-dir {REPO_LOCATION}/src/retracer/config --sandbox-dir {HOME}/cache --cleanup-debs --cleanup-sandbox --architecture %i --core-storage {HOME}/var --verbose {failed} |
| 169 | +Restart=on-failure |
| 170 | +
|
| 171 | +[Install] |
| 172 | +WantedBy=multi-user.target |
| 173 | +""" |
| 174 | + ) |
| 175 | + |
| 176 | + check_call(["systemctl", "daemon-reload"]) |
| 177 | + |
| 178 | + logger.info("enabling systemd units") |
| 179 | + check_call(["systemctl", "enable", "retracer@amd64"]) |
| 180 | + check_call(["systemctl", "enable", "retracer@arm64"]) |
| 181 | + check_call(["systemctl", "enable", "retracer@armhf"]) |
| 182 | + check_call(["systemctl", "enable", "retracer@i386"]) |
| 183 | + |
| 184 | + logger.info("restarting systemd units") |
| 185 | + check_call(["systemctl", "restart", "retracer@amd64"]) |
| 186 | + check_call(["systemctl", "restart", "retracer@arm64"]) |
| 187 | + check_call(["systemctl", "restart", "retracer@armhf"]) |
| 188 | + check_call(["systemctl", "restart", "retracer@i386"]) |
| 189 | + |
| 190 | + def configure_timers(self): |
| 191 | + logger.info("Configuring timers") |
| 192 | + setup_systemd_timer( |
| 193 | + "et-unique-users-daily-update", |
| 194 | + "Error Tracker - Unique users daily update", |
| 195 | + f"{REPO_LOCATION}/src/tools/unique_users_daily_update.py", |
| 196 | + "*-*-* 00:30:00", # every day at 00:30 |
| 197 | + ) |
| 198 | + setup_systemd_timer( |
| 199 | + "et-import-bugs", |
| 200 | + "Error Tracker - Import bugs", |
| 201 | + f"{REPO_LOCATION}/src/tools/import_bugs.py", |
| 202 | + "*-*-* 01,04,07,10,13,16,19,22:00:00", # every three hours |
| 203 | + ) |
| 204 | + setup_systemd_timer( |
| 205 | + "et-import-team-packages", |
| 206 | + "Error Tracker - Import team packages", |
| 207 | + f"{REPO_LOCATION}/src/tools/import_team_packages.py", |
| 208 | + "*-*-* 02:30:00", # every day at 02:30 |
| 209 | + ) |
| 210 | + setup_systemd_timer( |
| 211 | + "et-swift-corrupt-core-check", |
| 212 | + "Error Tracker - Swift - Check for corrupt cores", |
| 213 | + f"{REPO_LOCATION}/src/tools/swift_corrupt_core_check.py", |
| 214 | + "*-*-* 04:30:00", # every day at 04:30 |
| 215 | + ) |
| 216 | + setup_systemd_timer( |
| 217 | + "et-swift-handle-old-cores", |
| 218 | + "Error Tracker - Swift - Handle old cores", |
| 219 | + f"{REPO_LOCATION}/src/tools/swift_handle_old_cores.py", |
| 220 | + "*-*-* *:45:00", # every hour at minute 45 |
| 221 | + ) |
| 222 | + |
| 223 | + def configure_web(self): |
| 224 | + logger.info("Configuring web") |
0 commit comments