-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
set up scheduler to create tasks for idle workers
- Loading branch information
Showing
14 changed files
with
352 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from sqlalchemy import select | ||
from sqlalchemy.orm import Session as OrmSession | ||
|
||
from mirrors_qa_backend.db import models | ||
|
||
|
||
def get_countries_by_name(session: OrmSession, *countries: str) -> list[models.Country]: | ||
return list( | ||
session.scalars( | ||
select(models.Country).where(models.Country.name.in_(countries)) | ||
).all() | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,93 @@ | ||
# ruff: noqa: DTZ005, DTZ001 | ||
import datetime | ||
from pathlib import Path | ||
|
||
from sqlalchemy import select | ||
from sqlalchemy.orm import Session as OrmSession | ||
|
||
from mirrors_qa_backend.db import models | ||
from mirrors_qa_backend import cryptography | ||
from mirrors_qa_backend.db import country, models | ||
from mirrors_qa_backend.db.exceptions import DuplicatePrimaryKeyError | ||
|
||
|
||
def get_worker(session: OrmSession, worker_id: str) -> models.Worker | None: | ||
return session.scalars( | ||
select(models.Worker).where(models.Worker.id == worker_id) | ||
).one_or_none() | ||
|
||
|
||
def create_worker( | ||
session: OrmSession, | ||
worker_id: str, | ||
countries: list[str], | ||
private_key_filename: str | Path | None = None, | ||
) -> models.Worker: | ||
"""Creates a worker and writes private key contents to private_key_filename. | ||
If no private_key_filename is provided, defaults to {worker_id}.pem. | ||
""" | ||
if get_worker(session, worker_id) is not None: | ||
raise DuplicatePrimaryKeyError( | ||
f"A worker with id {worker_id!r} already exists." | ||
) | ||
|
||
if private_key_filename is None: | ||
private_key_filename = f"{worker_id}.pem" | ||
|
||
private_key = cryptography.generate_private_key() | ||
public_key = cryptography.generate_public_key(private_key) | ||
public_key_pkcs8 = cryptography.serialize_public_key(public_key).decode( | ||
encoding="ascii" | ||
) | ||
with open(private_key_filename, "wb") as fp: | ||
fp.write(cryptography.serialize_private_key(private_key)) | ||
|
||
worker = models.Worker( | ||
id=worker_id, | ||
pubkey_pkcs8=public_key_pkcs8, | ||
pubkey_fingerprint=cryptography.get_public_key_fingerprint(public_key), | ||
) | ||
session.add(worker) | ||
|
||
for db_country in country.get_countries_by_name(session, *countries): | ||
db_country.worker_id = worker_id | ||
session.add(db_country) | ||
|
||
return worker | ||
|
||
|
||
def get_workers_last_seen_in_range( | ||
session: OrmSession, begin: datetime.datetime, end: datetime.datetime | ||
) -> list[models.Worker]: | ||
"""Get workers whose last_seen_on falls between begin and end dates""" | ||
return list( | ||
session.scalars( | ||
select(models.Worker).where( | ||
models.Worker.last_seen_on.between(begin, end), | ||
) | ||
).all() | ||
) | ||
|
||
|
||
def get_idle_workers( | ||
session: OrmSession, interval: datetime.timedelta | ||
) -> list[models.Worker]: | ||
end = datetime.datetime.now() - interval | ||
begin = datetime.datetime(1970, 1, 1) | ||
return get_workers_last_seen_in_range(session, begin, end) | ||
|
||
|
||
def get_active_workers( | ||
session: OrmSession, interval: datetime.timedelta | ||
) -> list[models.Worker]: | ||
end = datetime.datetime.now() | ||
begin = end - interval | ||
return get_workers_last_seen_in_range(session, begin, end) | ||
|
||
|
||
def update_worker_last_seen( | ||
session: OrmSession, worker: models.Worker | ||
) -> models.Worker: | ||
worker.last_seen_on = datetime.datetime.now() | ||
session.add(worker) | ||
return worker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import datetime | ||
import time | ||
|
||
from mirrors_qa_backend import logger | ||
from mirrors_qa_backend.db import Session, tests, worker | ||
from mirrors_qa_backend.enums import StatusEnum | ||
from mirrors_qa_backend.settings import Settings | ||
|
||
|
||
def main(): | ||
while True: | ||
with Session.begin() as session: | ||
# expire tesst whose results have not been reported | ||
expired_tests = tests.expire_tests( | ||
session, | ||
interval=datetime.timedelta(hours=Settings.EXPIRE_TEST_INTERVAL), | ||
) | ||
for expired_test in expired_tests: | ||
logger.info( | ||
f"Expired test {expired_test.id}, " | ||
f"country: {expired_test.country}, " | ||
f"worker: {expired_test.worker_id}" | ||
) | ||
|
||
idle_workers = worker.get_idle_workers( | ||
session, | ||
interval=datetime.timedelta(hours=Settings.IDLE_WORKER_INTERVAL), | ||
) | ||
if not idle_workers: | ||
logger.info("No idle workers found.") | ||
|
||
# Create tests for the countries the worker is responsible for.. | ||
for idle_worker in idle_workers: | ||
if not idle_worker.countries: | ||
logger.info( | ||
f"No countries registered for idle worker {idle_worker.id}" | ||
) | ||
continue | ||
for country in idle_worker.countries: | ||
# While we have expired "unreported" tests, it is possible that | ||
# a test for a country might still be PENDING as the interval | ||
# for expiration and that of the scheduler might overlap. | ||
# In such scenarios, we skip creating a test for that country. | ||
pending_tests = tests.list_tests( | ||
session, | ||
worker_id=idle_worker.id, | ||
statuses=[StatusEnum.PENDING], | ||
country=country.name, | ||
) | ||
|
||
if pending_tests.nb_tests: | ||
logger.info( | ||
"Skipping creation of new test entries for " | ||
f"{idle_worker.id} as {pending_tests.nb_tests} " | ||
"tests are still pending." | ||
) | ||
continue | ||
|
||
new_test = tests.create_test( | ||
session=session, | ||
worker_id=idle_worker.id, | ||
country=country.name, | ||
status=StatusEnum.PENDING, | ||
) | ||
logger.info( | ||
f"Created new test {new_test.id} for worker " | ||
f"{idle_worker.id} in country {country.name}" | ||
) | ||
|
||
sleep_interval = datetime.timedelta( | ||
hours=Settings.SCHEDULER_SLEEP_INTERVAL | ||
).total_seconds() | ||
|
||
logger.info(f"Sleeping for {sleep_interval} seconds.") | ||
time.sleep(sleep_interval) |
Oops, something went wrong.