erase logs based on current package version

Old implementation has used process id instead, but it leads to log
removal in case of remote process trigger
This commit is contained in:
Evgenii Alekseev 2023-08-18 14:56:49 +03:00
parent 50775c3f0a
commit 479f0db572
23 changed files with 156 additions and 86 deletions

View File

@ -0,0 +1,36 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__all__ = ["steps"]
steps = [
"""
drop index logs_package_base_process_id
""",
"""
alter table logs drop column process_id
""",
"""
alter table logs add column version text not null default ''
""",
"""
create index logs_package_base_version on logs (package_base, version)
""",
]

View File

@ -66,13 +66,13 @@ class LogsOperations(Operations):
connection.execute( connection.execute(
""" """
insert into logs insert into logs
(package_base, process_id, created, record) (package_base, created, version, record)
values values
(:package_base, :process_id, :created, :record) (:package_base, :created, :version, :record)
""", """,
{ {
"package_base": log_record_id.package_base, "package_base": log_record_id.package_base,
"process_id": log_record_id.process_id, "version": log_record_id.version,
"created": created, "created": created,
"record": record, "record": record,
} }
@ -80,22 +80,22 @@ class LogsOperations(Operations):
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)
def logs_remove(self, package_base: str, current_process_id: int | None) -> None: def logs_remove(self, package_base: str, version: str | None) -> None:
""" """
remove log records for the specified package remove log records for the specified package
Args: Args:
package_base(str): package base to remove logs package_base(str): package base to remove logs
current_process_id(int | None): current process id. If set it will remove only logs belonging to another version(str): package version. If set it will remove only logs belonging to another
process version
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(
""" """
delete from logs delete from logs
where package_base = :package_base and (:process_id is null or process_id <> :process_id) where package_base = :package_base and (:version is null or version <> :version)
""", """,
{"package_base": package_base, "process_id": current_process_id} {"package_base": package_base, "version": version}
) )
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)

View File

@ -77,8 +77,8 @@ class HttpLogHandler(logging.Handler):
Args: Args:
record(logging.LogRecord): log record to log record(logging.LogRecord): log record to log
""" """
package_base = getattr(record, "package_base", None) log_record_id = getattr(record, "package_id", None)
if package_base is None: if log_record_id is None:
return # in case if no package base supplied we need just skip log message return # in case if no package base supplied we need just skip log message
self.reporter.package_logs(package_base, record) self.reporter.package_logs(log_record_id, record)

View File

@ -24,6 +24,8 @@ from collections.abc import Generator
from functools import cached_property from functools import cached_property
from typing import Any from typing import Any
from ahriman.models.log_record_id import LogRecordId
class LazyLogging: class LazyLogging:
""" """
@ -60,38 +62,40 @@ class LazyLogging:
logging.setLogRecordFactory(logging.LogRecord) logging.setLogRecordFactory(logging.LogRecord)
@staticmethod @staticmethod
def _package_logger_set(package_base: str) -> None: def _package_logger_set(package_base: str, version: str | None) -> None:
""" """
set package base as extra info to the logger set package base as extra info to the logger
Args: Args:
package_base(str): package base package_base(str): package base
version(str | None): package version if available
""" """
current_factory = logging.getLogRecordFactory() current_factory = logging.getLogRecordFactory()
def package_record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord: def package_record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
record = current_factory(*args, **kwargs) record = current_factory(*args, **kwargs)
record.package_base = package_base record.package_id = LogRecordId(package_base, version or "")
return record return record
logging.setLogRecordFactory(package_record_factory) logging.setLogRecordFactory(package_record_factory)
@contextlib.contextmanager @contextlib.contextmanager
def in_package_context(self, package_base: str) -> Generator[None, None, None]: def in_package_context(self, package_base: str, version: str | None) -> Generator[None, None, None]:
""" """
execute function while setting package context execute function while setting package context
Args: Args:
package_base(str): package base to set context in package_base(str): package base to set context in
version(str | None): package version if available
Examples: Examples:
This function is designed to be called as context manager with ``package_base`` argument, e.g.: This function is designed to be called as context manager with ``package_base`` argument, e.g.:
>>> with self.in_package_context(package.base): >>> with self.in_package_context(package.base, package.version):
>>> build_package(package) >>> build_package(package)
""" """
try: try:
self._package_logger_set(package_base) self._package_logger_set(package_base, version)
yield yield
finally: finally:
self._package_logger_reset() self._package_logger_reset()

View File

@ -93,7 +93,8 @@ class Executor(Cleaner):
result = Result() result = Result()
for single in updates: for single in updates:
with self.in_package_context(single.base), TemporaryDirectory(ignore_cleanup_errors=True) as dir_name: with self.in_package_context(single.base, local_versions.get(single.base)), \
TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
try: try:
packager = self.packager(packagers, single.base) packager = self.packager(packagers, single.base)
build_single(single, Path(dir_name), packager.packager_id) build_single(single, Path(dir_name), packager.packager_id)
@ -201,14 +202,16 @@ class Executor(Cleaner):
package_path = self.paths.repository / safe_filename(name) package_path = self.paths.repository / safe_filename(name)
self.repo.add(package_path) self.repo.add(package_path)
current_packages = self.packages() current_packages = {package.base: package for package in self.packages()}
local_versions = {package_base: package.version for package_base, package in current_packages.items()}
removed_packages: list[str] = [] # list of packages which have been removed from the base removed_packages: list[str] = [] # list of packages which have been removed from the base
updates = self.load_archives(packages) updates = self.load_archives(packages)
packagers = packagers or Packagers() packagers = packagers or Packagers()
result = Result() result = Result()
for local in updates: for local in updates:
with self.in_package_context(local.base): with self.in_package_context(local.base, local_versions.get(local.base)):
try: try:
packager = self.packager(packagers, local.base) packager = self.packager(packagers, local.base)
@ -218,12 +221,9 @@ class Executor(Cleaner):
self.reporter.set_success(local) self.reporter.set_success(local)
result.add_success(local) result.add_success(local)
current_package_archives = { current_package_archives: set[str] = set()
package if local.base in current_packages:
for current in current_packages current_package_archives = set(current_packages[local.base].packages.keys())
if current.base == local.base
for package in current.packages
}
removed_packages.extend(current_package_archives.difference(local.packages)) removed_packages.extend(current_package_archives.difference(local.packages))
except Exception: except Exception:
self.reporter.set_failed(local.base) self.reporter.set_failed(local.base)

View File

@ -66,10 +66,11 @@ class UpdateHandler(Cleaner):
continue continue
raise UnknownPackageError(package.base) raise UnknownPackageError(package.base)
result: list[Package] = [] local_versions = {package.base: package.version for package in self.packages()}
result: list[Package] = []
for local in self.packages(): for local in self.packages():
with self.in_package_context(local.base): with self.in_package_context(local.base, local_versions.get(local.base)):
if not local.remote.is_remote: if not local.remote.is_remote:
continue # avoid checking local packages continue # avoid checking local packages
if local.base in self.ignore_list: if local.base in self.ignore_list:
@ -102,11 +103,12 @@ class UpdateHandler(Cleaner):
Returns: Returns:
list[Package]: list of local packages which are out-of-dated list[Package]: list of local packages which are out-of-dated
""" """
result: list[Package] = []
packages = {local.base: local for local in self.packages()} packages = {local.base: local for local in self.packages()}
local_versions = {package_base: package.version for package_base, package in packages.items()}
result: list[Package] = []
for cache_dir in self.paths.cache.iterdir(): for cache_dir in self.paths.cache.iterdir():
with self.in_package_context(cache_dir.name): with self.in_package_context(cache_dir.name, local_versions.get(cache_dir.name)):
try: try:
source = RemoteSource( source = RemoteSource(
source=PackageSource.Local, source=PackageSource.Local,

View File

@ -24,6 +24,7 @@ import logging
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -82,12 +83,12 @@ class Client:
del package_base del package_base
return [] return []
def package_logs(self, package_base: str, record: logging.LogRecord) -> None: def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None:
""" """
post log record post log record
Args: Args:
package_base(str) package base log_record_id(LogRecordId): log record id
record(logging.LogRecord): log record to post to api record(logging.LogRecord): log record to post to api
""" """

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import os
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
@ -59,7 +57,7 @@ class Watcher(LazyLogging):
self.status = BuildStatus() self.status = BuildStatus()
# special variables for updating logs # special variables for updating logs
self._last_log_record_id = LogRecordId("", os.getpid()) self._last_log_record_id = LogRecordId("", "")
@property @property
def packages(self) -> list[tuple[Package, BuildStatus]]: def packages(self) -> list[tuple[Package, BuildStatus]]:
@ -99,15 +97,15 @@ class Watcher(LazyLogging):
""" """
return self.database.logs_get(package_base) return self.database.logs_get(package_base)
def logs_remove(self, package_base: str, current_process_id: int | None) -> None: def logs_remove(self, package_base: str, version: str | None) -> None:
""" """
remove package related logs remove package related logs
Args: Args:
package_base(str): package base package_base(str): package base
current_process_id(int | None): current process id version(str): package versio
""" """
self.database.logs_remove(package_base, current_process_id) self.database.logs_remove(package_base, version)
def logs_update(self, log_record_id: LogRecordId, created: float, record: str) -> None: def logs_update(self, log_record_id: LogRecordId, created: float, record: str) -> None:
""" """
@ -120,7 +118,7 @@ class Watcher(LazyLogging):
""" """
if self._last_log_record_id != log_record_id: if self._last_log_record_id != log_record_id:
# there is new log record, so we remove old ones # there is new log record, so we remove old ones
self.logs_remove(log_record_id.package_base, log_record_id.process_id) self.logs_remove(log_record_id.package_base, log_record_id.version)
self._last_log_record_id = log_record_id self._last_log_record_id = log_record_id
self.database.logs_insert(log_record_id, created, record) self.database.logs_insert(log_record_id, created, record)

View File

@ -33,6 +33,7 @@ from ahriman.core.status.client import Client
from ahriman.core.util import exception_response_text from ahriman.core.util import exception_response_text
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user import User from ahriman.models.user import User
@ -253,20 +254,20 @@ class WebClient(Client, LazyLogging):
for package in response_json for package in response_json
] ]
def package_logs(self, package_base: str, record: logging.LogRecord) -> None: def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None:
""" """
post log record post log record
Args: Args:
package_base(str) package base log_record_id(LogRecordId): log record id
record(logging.LogRecord): log record to post to api record(logging.LogRecord): log record to post to api
""" """
payload = { payload = {
"created": record.created, "created": record.created,
"message": record.getMessage(), "message": record.getMessage(),
"process_id": record.process, "version": log_record_id.version,
} }
self.make_request("POST", self._logs_url(package_base), json=payload) self.make_request("POST", self._logs_url(log_record_id.package_base), json=payload)
def package_remove(self, package_base: str) -> None: def package_remove(self, package_base: str) -> None:
""" """

View File

@ -27,8 +27,8 @@ class LogRecordId:
Attributes: Attributes:
package_base(str): package base for which log record belongs package_base(str): package base for which log record belongs
process_id(int): process id from which log record was emitted version(str): package version for which log record belongs
""" """
package_base: str package_base: str
process_id: int version: str

View File

@ -19,6 +19,8 @@
# #
from marshmallow import Schema, fields from marshmallow import Schema, fields
from ahriman import __version__
class LogSchema(Schema): class LogSchema(Schema):
""" """
@ -29,9 +31,9 @@ class LogSchema(Schema):
"description": "Log record timestamp", "description": "Log record timestamp",
"example": 1680537091.233495, "example": 1680537091.233495,
}) })
process_id = fields.Integer(required=True, metadata={ version = fields.Integer(required=True, metadata={
"description": "Current process id", "description": "Package version to tag",
"example": 42, "example": __version__,
}) })
message = fields.String(required=True, metadata={ message = fields.String(required=True, metadata={
"description": "Log message", "description": "Log message",

View File

@ -19,11 +19,11 @@
# #
import aiohttp_apispec # type: ignore[import] import aiohttp_apispec # type: ignore[import]
import shutil import shutil
import tempfile
from aiohttp import BodyPartReader from aiohttp import BodyPartReader
from aiohttp.web import HTTPBadRequest, HTTPCreated, HTTPNotFound from aiohttp.web import HTTPBadRequest, HTTPCreated, HTTPNotFound
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, FileSchema from ahriman.web.schemas import AuthSchema, ErrorSchema, FileSchema
@ -68,7 +68,7 @@ class UploadView(BaseView):
# in order to handle errors automatically we create temporary file for long operation (transfer) # in order to handle errors automatically we create temporary file for long operation (transfer)
# and then copy it to valid location # and then copy it to valid location
with tempfile.NamedTemporaryFile() as cache: with NamedTemporaryFile() as cache:
while True: while True:
chunk = await part.read_chunk() chunk = await part.read_chunk()
if not chunk: if not chunk:

View File

@ -137,10 +137,10 @@ class LogsView(BaseView):
try: try:
created = data["created"] created = data["created"]
record = data["message"] record = data["message"]
process_id = data["process_id"] version = data["version"]
except Exception as e: except Exception as e:
raise HTTPBadRequest(reason=str(e)) raise HTTPBadRequest(reason=str(e))
self.service.logs_update(LogRecordId(package_base, process_id), created, record) self.service.logs_update(LogRecordId(package_base, version), created, record)
raise HTTPNoContent() raise HTTPNoContent()

View File

@ -1,7 +1,7 @@
from ahriman.core.database.migrations.m009_local_source import steps from ahriman.core.database.migrations.m009_local_source import steps
def test_migration_packagers() -> None: def test_migration_local_source() -> None:
""" """
migration must not be empty migration must not be empty
""" """

View File

@ -0,0 +1,8 @@
from ahriman.core.database.migrations.m010_version_based_logs_removal import steps
def test_migration_version_based_logs_removal() -> None:
"""
migration must not be empty
"""
assert steps

View File

@ -8,11 +8,11 @@ def test_logs_insert_remove_process(database: SQLite, package_ahriman: Package,
""" """
must clear process specific package logs must clear process specific package logs
""" """
database.logs_insert(LogRecordId(package_ahriman.base, 1), 42.0, "message 1") database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1")
database.logs_insert(LogRecordId(package_ahriman.base, 2), 43.0, "message 2") database.logs_insert(LogRecordId(package_ahriman.base, "2"), 43.0, "message 2")
database.logs_insert(LogRecordId(package_python_schedule.base, 1), 42.0, "message 3") database.logs_insert(LogRecordId(package_python_schedule.base, "1"), 42.0, "message 3")
database.logs_remove(package_ahriman.base, 1) database.logs_remove(package_ahriman.base, "1")
assert database.logs_get(package_ahriman.base) == "[1970-01-01 00:00:42] message 1" assert database.logs_get(package_ahriman.base) == "[1970-01-01 00:00:42] message 1"
assert database.logs_get(package_python_schedule.base) == "[1970-01-01 00:00:42] message 3" assert database.logs_get(package_python_schedule.base) == "[1970-01-01 00:00:42] message 3"
@ -21,9 +21,9 @@ def test_logs_insert_remove_full(database: SQLite, package_ahriman: Package, pac
""" """
must clear full package logs must clear full package logs
""" """
database.logs_insert(LogRecordId(package_ahriman.base, 1), 42.0, "message 1") database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1")
database.logs_insert(LogRecordId(package_ahriman.base, 2), 43.0, "message 2") database.logs_insert(LogRecordId(package_ahriman.base, "2"), 43.0, "message 2")
database.logs_insert(LogRecordId(package_python_schedule.base, 1), 42.0, "message 3") database.logs_insert(LogRecordId(package_python_schedule.base, "1"), 42.0, "message 3")
database.logs_remove(package_ahriman.base, None) database.logs_remove(package_ahriman.base, None)
assert not database.logs_get(package_ahriman.base) assert not database.logs_get(package_ahriman.base)
@ -34,6 +34,6 @@ def test_logs_insert_get(database: SQLite, package_ahriman: Package) -> None:
""" """
must insert and get package logs must insert and get package logs
""" """
database.logs_insert(LogRecordId(package_ahriman.base, 1), 43.0, "message 2") database.logs_insert(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2")
database.logs_insert(LogRecordId(package_ahriman.base, 1), 42.0, "message 1") database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1")
assert database.logs_get(package_ahriman.base) == "[1970-01-01 00:00:42] message 1\n[1970-01-01 00:00:43] message 2" assert database.logs_get(package_ahriman.base) == "[1970-01-01 00:00:42] message 1\n[1970-01-01 00:00:43] message 2"

View File

@ -4,6 +4,7 @@ from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log.http_log_handler import HttpLogHandler from ahriman.core.log.http_log_handler import HttpLogHandler
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -39,13 +40,13 @@ def test_emit(configuration: Configuration, log_record: logging.LogRecord, packa
""" """
must emit log record to reporter must emit log record to reporter
""" """
log_record.package_base = package_ahriman.base log_record_id = log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs") log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
handler = HttpLogHandler(configuration, report=False) handler = HttpLogHandler(configuration, report=False)
handler.emit(log_record) handler.emit(log_record)
log_mock.assert_called_once_with(package_ahriman.base, log_record) log_mock.assert_called_once_with(log_record_id, log_record)
def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord, mocker: MockerFixture) -> None: def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord, mocker: MockerFixture) -> None:

View File

@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
from ahriman.core.alpm.repo import Repo from ahriman.core.alpm.repo import Repo
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -20,16 +21,16 @@ def test_package_logger_set_reset(database: SQLite) -> None:
""" """
must set and reset package base attribute must set and reset package base attribute
""" """
package_base = "package base" log_record_id = LogRecordId("base", "version")
database._package_logger_set(package_base) database._package_logger_set(log_record_id.package_base, log_record_id.version)
record = logging.makeLogRecord({}) record = logging.makeLogRecord({})
assert record.package_base == package_base assert record.package_id == log_record_id
database._package_logger_reset() database._package_logger_reset()
record = logging.makeLogRecord({}) record = logging.makeLogRecord({})
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
record.package_base record.package_id
def test_in_package_context(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None: def test_in_package_context(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -39,10 +40,24 @@ def test_in_package_context(database: SQLite, package_ahriman: Package, mocker:
set_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_set") set_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_set")
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset") reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")
with database.in_package_context(package_ahriman.base): with database.in_package_context(package_ahriman.base, package_ahriman.version):
pass pass
set_mock.assert_called_once_with(package_ahriman.base) set_mock.assert_called_once_with(package_ahriman.base, package_ahriman.version)
reset_mock.assert_called_once_with()
def test_in_package_context_empty_version(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set package log context
"""
set_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_set")
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")
with database.in_package_context(package_ahriman.base, None):
pass
set_mock.assert_called_once_with(package_ahriman.base, None)
reset_mock.assert_called_once_with() reset_mock.assert_called_once_with()
@ -54,7 +69,7 @@ def test_in_package_context_failed(database: SQLite, package_ahriman: Package, m
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset") reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")
with pytest.raises(Exception): with pytest.raises(Exception):
with database.in_package_context(package_ahriman.base): with database.in_package_context(package_ahriman.base, ""):
raise Exception() raise Exception()
reset_mock.assert_called_once_with() reset_mock.assert_called_once_with()

View File

@ -7,6 +7,7 @@ from ahriman.core.status.client import Client
from ahriman.core.status.web_client import WebClient from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -66,11 +67,11 @@ def test_package_get(client: Client, package_ahriman: Package) -> None:
assert client.package_get(None) == [] assert client.package_get(None) == []
def test_package_log(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None: def test_package_logs(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None:
""" """
must process log record without errors must process log record without errors
""" """
client.package_logs(package_ahriman.base, log_record) client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
def test_package_remove(client: Client, package_ahriman: Package) -> None: def test_package_remove(client: Client, package_ahriman: Package) -> None:

View File

@ -64,8 +64,8 @@ def test_logs_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerF
must remove package logs must remove package logs
""" """
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove") logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
watcher.logs_remove(package_ahriman.base, 42) watcher.logs_remove(package_ahriman.base, "42")
logs_mock.assert_called_once_with(package_ahriman.base, 42) logs_mock.assert_called_once_with(package_ahriman.base, "42")
def test_logs_update_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: def test_logs_update_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -75,11 +75,11 @@ def test_logs_update_new(watcher: Watcher, package_ahriman: Package, mocker: Moc
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove") delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove")
insert_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert") insert_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert")
log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.process_id) log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version)
assert watcher._last_log_record_id != log_record_id assert watcher._last_log_record_id != log_record_id
watcher.logs_update(log_record_id, 42.01, "log record") watcher.logs_update(log_record_id, 42.01, "log record")
delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.process_id) delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.version)
insert_mock.assert_called_once_with(log_record_id, 42.01, "log record") insert_mock.assert_called_once_with(log_record_id, 42.01, "log record")
assert watcher._last_log_record_id == log_record_id assert watcher._last_log_record_id == log_record_id
@ -92,7 +92,7 @@ def test_logs_update_update(watcher: Watcher, package_ahriman: Package, mocker:
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove") delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove")
insert_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert") insert_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert")
log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.process_id) log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version)
watcher._last_log_record_id = log_record_id watcher._last_log_record_id = log_record_id
watcher.logs_update(log_record_id, 42.01, "log record") watcher.logs_update(log_record_id, 42.01, "log record")

View File

@ -11,6 +11,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.status.web_client import WebClient from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user import User from ahriman.models.user import User
@ -280,10 +281,10 @@ def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, pack
payload = { payload = {
"created": log_record.created, "created": log_record.created,
"message": log_record.getMessage(), "message": log_record.getMessage(),
"process_id": log_record.process, "version": package_ahriman.version,
} }
web_client.package_logs(package_ahriman.base, log_record) web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
params=None, json=payload, files=None) params=None, json=payload, files=None)
@ -295,7 +296,7 @@ def test_package_logs_failed(web_client: WebClient, log_record: logging.LogRecor
""" """
mocker.patch("requests.Session.request", side_effect=Exception()) mocker.patch("requests.Session.request", side_effect=Exception())
log_record.package_base = package_ahriman.base log_record.package_base = package_ahriman.base
web_client.package_logs(package_ahriman.base, log_record) web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
def test_package_logs_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, def test_package_logs_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
@ -305,7 +306,7 @@ def test_package_logs_failed_http_error(web_client: WebClient, log_record: loggi
""" """
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError()) mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
log_record.package_base = package_ahriman.base log_record.package_base = package_ahriman.base
web_client.package_logs(package_ahriman.base, log_record) web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:

View File

@ -30,7 +30,7 @@ async def test_save_file(mocker: MockerFixture) -> None:
part_mock.filename = "filename" part_mock.filename = "filename"
part_mock.read_chunk = AsyncMock(side_effect=[b"content", None]) part_mock.read_chunk = AsyncMock(side_effect=[b"content", None])
tempfile_mock = mocker.patch("tempfile.NamedTemporaryFile") tempfile_mock = mocker.patch("ahriman.web.views.service.upload.NamedTemporaryFile")
file_mock = MagicMock() file_mock = MagicMock()
tempfile_mock.return_value.__enter__.return_value = file_mock tempfile_mock.return_value.__enter__.return_value = file_mock

View File

@ -30,9 +30,9 @@ async def test_delete(client: TestClient, package_ahriman: Package, package_pyth
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()}) json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", await client.post(f"/api/v1/packages/{package_ahriman.base}/logs",
json={"created": 42.0, "message": "message", "process_id": 42}) json={"created": 42.0, "message": "message", "version": "42"})
await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs", await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs",
json={"created": 42.0, "message": "message", "process_id": 42}) json={"created": 42.0, "message": "message", "version": "42"})
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}/logs") response = await client.delete(f"/api/v1/packages/{package_ahriman.base}/logs")
assert response.status == 204 assert response.status == 204
@ -53,7 +53,7 @@ async def test_get(client: TestClient, package_ahriman: Package) -> None:
await client.post(f"/api/v1/packages/{package_ahriman.base}", await client.post(f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", await client.post(f"/api/v1/packages/{package_ahriman.base}/logs",
json={"created": 42.0, "message": "message", "process_id": 42}) json={"created": 42.0, "message": "message", "version": "42"})
response_schema = pytest.helpers.schema_response(LogsView.get) response_schema = pytest.helpers.schema_response(LogsView.get)
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/logs") response = await client.get(f"/api/v1/packages/{package_ahriman.base}/logs")
@ -83,7 +83,7 @@ async def test_post(client: TestClient, package_ahriman: Package) -> None:
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
request_schema = pytest.helpers.schema_request(LogsView.post) request_schema = pytest.helpers.schema_request(LogsView.post)
payload = {"created": 42.0, "message": "message", "process_id": 42} payload = {"created": 42.0, "message": "message", "version": "42"}
assert not request_schema.validate(payload) assert not request_schema.validate(payload)
response = await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", json=payload) response = await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", json=payload)
assert response.status == 204 assert response.status == 204