handle process id during removal

During one process we can write logs from different packages in
different times (e.g. check and update laster) and we would like
to store all logs belong to the same process
This commit is contained in:
Evgenii Alekseev 2022-11-20 12:37:51 +02:00
parent 12f6bb0aaf
commit e22e57bf08
6 changed files with 71 additions and 43 deletions

View File

@ -24,11 +24,12 @@ steps = [
""" """
create table logs ( create table logs (
package_base text not null, package_base text not null,
process_id integer not null,
created real not null, created real not null,
record text record text
) )
""", """,
""" """
create index logs_package_base on logs (package_base) create index logs_package_base_process_id on logs (package_base, process_id)
""", """,
] ]

View File

@ -18,9 +18,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from sqlite3 import Connection from sqlite3 import Connection
from typing import List from typing import List, Optional
from ahriman.core.database.operations import Operations from ahriman.core.database.operations import Operations
from ahriman.models.log_record_id import LogRecordId
class LogsOperations(Operations): class LogsOperations(Operations):
@ -28,21 +29,6 @@ class LogsOperations(Operations):
logs operations logs operations
""" """
def logs_delete(self, package_base: str) -> None:
"""
delete log records for the specified package
Args:
package_base(str): package base to remove logs
"""
def run(connection: Connection) -> None:
connection.execute(
"""delete from logs where package_base = :package_base""",
{"package_base": package_base}
)
return self.with_connection(run, commit=True)
def logs_get(self, package_base: str) -> str: def logs_get(self, package_base: str) -> str:
""" """
extract logs for specified package base extract logs for specified package base
@ -67,12 +53,12 @@ class LogsOperations(Operations):
records = self.with_connection(run) records = self.with_connection(run)
return "\n".join(records) return "\n".join(records)
def logs_insert(self, package_base: str, created: float, record: str) -> None: def logs_insert(self, log_record_id: LogRecordId, created: float, record: str) -> None:
""" """
write new log record to database write new log record to database
Args: Args:
package_base(str): package base log_record_id(LogRecordId): current log record id
created(float): log created timestamp from log record attribute created(float): log created timestamp from log record attribute
record(str): log record record(str): log record
""" """
@ -80,15 +66,36 @@ class LogsOperations(Operations):
connection.execute( connection.execute(
""" """
insert into logs insert into logs
(package_base, created, record) (package_base, process_id, created, record)
values values
(:package_base, :created, :record) (:package_base, :process_id, :created, :record)
""", """,
dict( dict(
package_base=package_base, package_base=log_record_id.package_base,
process_id=log_record_id.process_id,
created=created, created=created,
record=record record=record
) )
) )
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)
def logs_remove(self, package_base: str, current_process_id: Optional[int]) -> None:
"""
remove log records for the specified package
Args:
package_base(str): package base to remove logs
current_process_id(Optional[int]): current process id. If set it will remove only logs belonging to another
process
"""
def run(connection: Connection) -> None:
connection.execute(
"""
delete from logs
where package_base = :package_base and (:process_id is null or process_id <> :process_id)
""",
{"package_base": package_base, "process_id": current_process_id}
)
return self.with_connection(run, commit=True)

View File

@ -128,15 +128,17 @@ class Watcher(LazyLogging):
""" """
self.known.pop(package_base, None) self.known.pop(package_base, None)
self.database.package_remove(package_base) self.database.package_remove(package_base)
self.remove_logs(package_base, None)
def remove_logs(self, package_base: str) -> None: def remove_logs(self, package_base: str, current_process_id: Optional[int]) -> 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): current process id
""" """
self.database.logs_delete(package_base) self.database.logs_remove(package_base, current_process_id)
def update(self, package_base: str, status: BuildStatusEnum, package: Optional[Package]) -> None: def update(self, package_base: str, status: BuildStatusEnum, package: Optional[Package]) -> None:
""" """
@ -170,9 +172,9 @@ 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.database.logs_delete(log_record_id.package_base) self.remove_logs(log_record_id.package_base, log_record_id.process_id)
self._last_log_record_id = log_record_id self._last_log_record_id = log_record_id
self.database.logs_insert(log_record_id.package_base, created, record) self.database.logs_insert(log_record_id, created, record)
def update_self(self, status: BuildStatusEnum) -> None: def update_self(self, status: BuildStatusEnum) -> None:
""" """

View File

@ -46,7 +46,7 @@ class LogsView(BaseView):
HTTPNoContent: on success response HTTPNoContent: on success response
""" """
package_base = self.request.match_info["package"] package_base = self.request.match_info["package"]
self.service.remove_logs(package_base) self.service.remove_logs(package_base, None)
raise HTTPNoContent() raise HTTPNoContent()

View File

@ -1,23 +1,39 @@
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
def test_logs_insert_delete(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None: def test_logs_insert_remove_process(database: SQLite, package_ahriman: Package,
package_python_schedule: Package) -> None:
""" """
must clear all packages must clear process specific package logs
""" """
database.logs_insert(package_ahriman.base, 0.001, "message 1") database.logs_insert(LogRecordId(package_ahriman.base, 1), 0.001, "message 1")
database.logs_insert(package_python_schedule.base, 0.002, "message 2") database.logs_insert(LogRecordId(package_ahriman.base, 2), 0.001, "message 2")
database.logs_insert(LogRecordId(package_python_schedule.base, 1), 0.002, "message 3")
database.logs_delete(package_ahriman.base) database.logs_remove(package_ahriman.base, 1)
assert database.logs_get(package_ahriman.base) == "message 1"
assert database.logs_get(package_python_schedule.base) == "message 3"
def test_logs_insert_remove_full(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must clear full package logs
"""
database.logs_insert(LogRecordId(package_ahriman.base, 1), 0.001, "message 1")
database.logs_insert(LogRecordId(package_ahriman.base, 2), 0.001, "message 2")
database.logs_insert(LogRecordId(package_python_schedule.base, 1), 0.002, "message 3")
database.logs_remove(package_ahriman.base, None)
assert not database.logs_get(package_ahriman.base) assert not database.logs_get(package_ahriman.base)
assert database.logs_get(package_python_schedule.base) assert database.logs_get(package_python_schedule.base) == "message 3"
def test_logs_insert_get(database: SQLite, package_ahriman: Package) -> None: 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(package_ahriman.base, 0.002, "message 2") database.logs_insert(LogRecordId(package_ahriman.base, 1), 0.002, "message 2")
database.logs_insert(package_ahriman.base, 0.001, "message 1") database.logs_insert(LogRecordId(package_ahriman.base, 1), 0.001, "message 1")
assert database.logs_get(package_ahriman.base) == "message 1\nmessage 2" assert database.logs_get(package_ahriman.base) == "message 1\nmessage 2"

View File

@ -83,20 +83,22 @@ def test_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixtur
must remove package base must remove package base
""" """
cache_mock = mocker.patch("ahriman.core.database.SQLite.package_remove") cache_mock = mocker.patch("ahriman.core.database.SQLite.package_remove")
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.remove_logs")
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())} watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.remove(package_ahriman.base) watcher.remove(package_ahriman.base)
assert not watcher.known assert not watcher.known
cache_mock.assert_called_once_with(package_ahriman.base) cache_mock.assert_called_once_with(package_ahriman.base)
logs_mock.assert_called_once_with(package_ahriman.base, None)
def test_remove_logs(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: def test_remove_logs(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must remove package logs must remove package logs
""" """
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_delete") logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
watcher.remove_logs(package_ahriman.base) watcher.remove_logs(package_ahriman.base, 42)
logs_mock.assert_called_once_with(package_ahriman.base) logs_mock.assert_called_once_with(package_ahriman.base, 42)
def test_remove_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: def test_remove_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -148,15 +150,15 @@ def test_update_logs_new(watcher: Watcher, package_ahriman: Package, mocker: Moc
""" """
must create package logs record for new package must create package logs record for new package
""" """
delete_mock = mocker.patch("ahriman.core.database.SQLite.logs_delete") delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.remove_logs")
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.process_id)
assert watcher._last_log_record_id != log_record_id assert watcher._last_log_record_id != log_record_id
watcher.update_logs(log_record_id, 42.01, "log record") watcher.update_logs(log_record_id, 42.01, "log record")
delete_mock.assert_called_once_with(package_ahriman.base) delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.process_id)
insert_mock.assert_called_once_with(package_ahriman.base, 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
@ -165,7 +167,7 @@ def test_update_logs_update(watcher: Watcher, package_ahriman: Package, mocker:
""" """
must create package logs record for current package must create package logs record for current package
""" """
delete_mock = mocker.patch("ahriman.core.database.SQLite.logs_delete") delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.remove_logs")
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.process_id)
@ -173,7 +175,7 @@ def test_update_logs_update(watcher: Watcher, package_ahriman: Package, mocker:
watcher.update_logs(log_record_id, 42.01, "log record") watcher.update_logs(log_record_id, 42.01, "log record")
delete_mock.assert_not_called() delete_mock.assert_not_called()
insert_mock.assert_called_once_with(package_ahriman.base, 42.01, "log record") insert_mock.assert_called_once_with(log_record_id, 42.01, "log record")
def test_update_self(watcher: Watcher) -> None: def test_update_self(watcher: Watcher) -> None: