mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2024-11-25 21:41:27 +00:00
tests: Cover upgrade and rebuild task
This commit is contained in:
parent
d8666fa179
commit
c63552241c
|
@ -19,6 +19,7 @@
|
||||||
pytest
|
pytest
|
||||||
pytest-datadir
|
pytest-datadir
|
||||||
pytest-mock
|
pytest-mock
|
||||||
|
pytest-subprocess
|
||||||
black
|
black
|
||||||
mypy
|
mypy
|
||||||
pylsp-mypy
|
pylsp-mypy
|
||||||
|
|
|
@ -4,9 +4,15 @@ After starting, track the status of the systemd unit and update the Job
|
||||||
status accordingly.
|
status accordingly.
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
from selfprivacy_api.utils.huey import huey
|
from selfprivacy_api.utils.huey import huey
|
||||||
from selfprivacy_api.jobs import JobStatus, Jobs, Job
|
from selfprivacy_api.jobs import JobStatus, Jobs, Job
|
||||||
import time
|
from datetime import datetime
|
||||||
|
|
||||||
|
START_TIMEOUT = 60 * 5
|
||||||
|
START_INTERVAL = 1
|
||||||
|
RUN_TIMEOUT = 60 * 60
|
||||||
|
RUN_INTERVAL = 5
|
||||||
|
|
||||||
|
|
||||||
@huey.task()
|
@huey.task()
|
||||||
|
@ -27,7 +33,7 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
status_text="Starting the system rebuild...",
|
status_text="Starting the system rebuild...",
|
||||||
)
|
)
|
||||||
# Get current time to handle timeout
|
# Get current time to handle timeout
|
||||||
start_time = time.time()
|
start_time = datetime.now()
|
||||||
# Wait for the systemd unit to start
|
# Wait for the systemd unit to start
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -37,18 +43,16 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
print(status.stdout.strip())
|
|
||||||
if status.stdout.strip() == "active":
|
if status.stdout.strip() == "active":
|
||||||
break
|
break
|
||||||
# Timeount after 5 minutes
|
if (datetime.now() - start_time).total_seconds() > START_TIMEOUT:
|
||||||
if time.time() - start_time > 300:
|
|
||||||
Jobs.update(
|
Jobs.update(
|
||||||
job=job,
|
job=job,
|
||||||
status=JobStatus.ERROR,
|
status=JobStatus.ERROR,
|
||||||
error="System rebuild timed out.",
|
error="System rebuild timed out.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
time.sleep(1)
|
time.sleep(START_INTERVAL)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
pass
|
pass
|
||||||
Jobs.update(
|
Jobs.update(
|
||||||
|
@ -56,7 +60,6 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
status=JobStatus.RUNNING,
|
status=JobStatus.RUNNING,
|
||||||
status_text="Rebuilding the system...",
|
status_text="Rebuilding the system...",
|
||||||
)
|
)
|
||||||
print("Rebuilding the system...")
|
|
||||||
# Wait for the systemd unit to finish
|
# Wait for the systemd unit to finish
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -66,9 +69,7 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
print(f"Unit status: {status.stdout.strip()}")
|
|
||||||
if status.stdout.strip() == "inactive":
|
if status.stdout.strip() == "inactive":
|
||||||
print("System rebuilt.")
|
|
||||||
Jobs.update(
|
Jobs.update(
|
||||||
job=job,
|
job=job,
|
||||||
status=JobStatus.FINISHED,
|
status=JobStatus.FINISHED,
|
||||||
|
@ -77,7 +78,6 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
elif status.stdout.strip() == "failed":
|
elif status.stdout.strip() == "failed":
|
||||||
print("System rebuild failed.")
|
|
||||||
Jobs.update(
|
Jobs.update(
|
||||||
job=job,
|
job=job,
|
||||||
status=JobStatus.ERROR,
|
status=JobStatus.ERROR,
|
||||||
|
@ -85,7 +85,6 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
elif status.stdout.strip() == "active":
|
elif status.stdout.strip() == "active":
|
||||||
print("Geting a log line")
|
|
||||||
log_line = subprocess.run(
|
log_line = subprocess.run(
|
||||||
[
|
[
|
||||||
"journalctl",
|
"journalctl",
|
||||||
|
@ -100,25 +99,21 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
).stdout.strip()
|
).stdout.strip()
|
||||||
print(log_line)
|
|
||||||
Jobs.update(
|
Jobs.update(
|
||||||
job=job,
|
job=job,
|
||||||
status=JobStatus.RUNNING,
|
status=JobStatus.RUNNING,
|
||||||
status_text=f"{log_line}",
|
status_text=f"{log_line}",
|
||||||
)
|
)
|
||||||
# Timeout of 60 minutes
|
|
||||||
if time.time() - start_time > 3600:
|
|
||||||
print("System rebuild timed out.")
|
|
||||||
Jobs.update(
|
|
||||||
job=job,
|
|
||||||
status=JobStatus.ERROR,
|
|
||||||
error="System rebuild timed out.",
|
|
||||||
)
|
|
||||||
break
|
|
||||||
time.sleep(5)
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print("subprocess.CalledProcessError")
|
|
||||||
pass
|
pass
|
||||||
|
if (datetime.now() - start_time).total_seconds() > RUN_TIMEOUT:
|
||||||
|
Jobs.update(
|
||||||
|
job=job,
|
||||||
|
status=JobStatus.ERROR,
|
||||||
|
error="System rebuild timed out.",
|
||||||
|
)
|
||||||
|
break
|
||||||
|
time.sleep(RUN_INTERVAL)
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
Jobs.update(
|
Jobs.update(
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# pylint: disable=missing-function-docstring
|
# pylint: disable=missing-function-docstring
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from selfprivacy_api.jobs import JobStatus, Jobs
|
||||||
|
|
||||||
|
|
||||||
class ProcessMock:
|
class ProcessMock:
|
||||||
"""Mock subprocess.Popen"""
|
"""Mock subprocess.Popen"""
|
||||||
|
@ -37,6 +39,13 @@ def mock_subprocess_check_output(mocker):
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_sleep_intervals(mocker):
|
||||||
|
mock_start = mocker.patch("selfprivacy_api.jobs.upgrade_system.START_INTERVAL", 0)
|
||||||
|
mock_run = mocker.patch("selfprivacy_api.jobs.upgrade_system.RUN_INTERVAL", 0)
|
||||||
|
return (mock_start, mock_run)
|
||||||
|
|
||||||
|
|
||||||
API_REBUILD_SYSTEM_MUTATION = """
|
API_REBUILD_SYSTEM_MUTATION = """
|
||||||
mutation rebuildSystem {
|
mutation rebuildSystem {
|
||||||
system {
|
system {
|
||||||
|
@ -44,46 +53,14 @@ mutation rebuildSystem {
|
||||||
success
|
success
|
||||||
message
|
message
|
||||||
code
|
code
|
||||||
|
job {
|
||||||
|
uid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_system_rebuild_unauthorized(client, mock_subprocess_popen):
|
|
||||||
"""Test system rebuild without authorization"""
|
|
||||||
response = client.post(
|
|
||||||
"/graphql",
|
|
||||||
json={
|
|
||||||
"query": API_REBUILD_SYSTEM_MUTATION,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json().get("data") is None
|
|
||||||
assert mock_subprocess_popen.call_count == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_system_rebuild(authorized_client, mock_subprocess_popen):
|
|
||||||
"""Test system rebuild"""
|
|
||||||
response = authorized_client.post(
|
|
||||||
"/graphql",
|
|
||||||
json={
|
|
||||||
"query": API_REBUILD_SYSTEM_MUTATION,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json().get("data") is not None
|
|
||||||
assert response.json()["data"]["system"]["runSystemRebuild"]["success"] is True
|
|
||||||
assert response.json()["data"]["system"]["runSystemRebuild"]["message"] is not None
|
|
||||||
assert response.json()["data"]["system"]["runSystemRebuild"]["code"] == 200
|
|
||||||
assert mock_subprocess_popen.call_count == 1
|
|
||||||
assert mock_subprocess_popen.call_args[0][0] == [
|
|
||||||
"systemctl",
|
|
||||||
"start",
|
|
||||||
"sp-nixos-rebuild.service",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
API_UPGRADE_SYSTEM_MUTATION = """
|
API_UPGRADE_SYSTEM_MUTATION = """
|
||||||
mutation upgradeSystem {
|
mutation upgradeSystem {
|
||||||
system {
|
system {
|
||||||
|
@ -91,44 +68,161 @@ mutation upgradeSystem {
|
||||||
success
|
success
|
||||||
message
|
message
|
||||||
code
|
code
|
||||||
|
job {
|
||||||
|
uid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_system_upgrade_unauthorized(client, mock_subprocess_popen):
|
@pytest.mark.parametrize("action", ["rebuild", "upgrade"])
|
||||||
"""Test system upgrade without authorization"""
|
def test_graphql_system_rebuild_unauthorized(client, fp, action):
|
||||||
|
"""Test system rebuild without authorization"""
|
||||||
|
query = (
|
||||||
|
API_REBUILD_SYSTEM_MUTATION
|
||||||
|
if action == "rebuild"
|
||||||
|
else API_UPGRADE_SYSTEM_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/graphql",
|
"/graphql",
|
||||||
json={
|
json={
|
||||||
"query": API_UPGRADE_SYSTEM_MUTATION,
|
"query": query,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json().get("data") is None
|
assert response.json().get("data") is None
|
||||||
assert mock_subprocess_popen.call_count == 0
|
assert fp.call_count([fp.any()]) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_system_upgrade(authorized_client, mock_subprocess_popen):
|
@pytest.mark.parametrize("action", ["rebuild", "upgrade"])
|
||||||
"""Test system upgrade"""
|
def test_graphql_system_rebuild(authorized_client, fp, action, mock_sleep_intervals):
|
||||||
|
"""Test system rebuild"""
|
||||||
|
unit_name = f"sp-nixos-{action}.service"
|
||||||
|
query = (
|
||||||
|
API_REBUILD_SYSTEM_MUTATION
|
||||||
|
if action == "rebuild"
|
||||||
|
else API_UPGRADE_SYSTEM_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start the unit
|
||||||
|
fp.register(["systemctl", "start", unit_name])
|
||||||
|
|
||||||
|
# Wait for it to start
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="inactive")
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="inactive")
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="active")
|
||||||
|
|
||||||
|
# Check its exectution
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="active")
|
||||||
|
fp.register(
|
||||||
|
["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"],
|
||||||
|
stdout="Starting rebuild...",
|
||||||
|
)
|
||||||
|
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="active")
|
||||||
|
fp.register(
|
||||||
|
["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"], stdout="Rebuilding..."
|
||||||
|
)
|
||||||
|
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="inactive")
|
||||||
|
|
||||||
response = authorized_client.post(
|
response = authorized_client.post(
|
||||||
"/graphql",
|
"/graphql",
|
||||||
json={
|
json={
|
||||||
"query": API_UPGRADE_SYSTEM_MUTATION,
|
"query": query,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json().get("data") is not None
|
assert response.json().get("data") is not None
|
||||||
assert response.json()["data"]["system"]["runSystemUpgrade"]["success"] is True
|
assert (
|
||||||
assert response.json()["data"]["system"]["runSystemUpgrade"]["message"] is not None
|
response.json()["data"]["system"][f"runSystem{action.capitalize()}"]["success"]
|
||||||
assert response.json()["data"]["system"]["runSystemUpgrade"]["code"] == 200
|
is True
|
||||||
assert mock_subprocess_popen.call_count == 1
|
)
|
||||||
assert mock_subprocess_popen.call_args[0][0] == [
|
assert (
|
||||||
"systemctl",
|
response.json()["data"]["system"][f"runSystem{action.capitalize()}"]["message"]
|
||||||
"start",
|
is not None
|
||||||
"sp-nixos-upgrade.service",
|
)
|
||||||
]
|
assert (
|
||||||
|
response.json()["data"]["system"][f"runSystem{action.capitalize()}"]["code"]
|
||||||
|
== 200
|
||||||
|
)
|
||||||
|
|
||||||
|
assert fp.call_count(["systemctl", "start", unit_name]) == 1
|
||||||
|
assert fp.call_count(["systemctl", "is-active", unit_name]) == 6
|
||||||
|
|
||||||
|
job_id = response.json()["data"]["system"][f"runSystem{action.capitalize()}"][
|
||||||
|
"job"
|
||||||
|
]["uid"]
|
||||||
|
assert Jobs.get_job(job_id).status == JobStatus.FINISHED
|
||||||
|
assert Jobs.get_job(job_id).type_id == f"system.nixos.{action}"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("action", ["rebuild", "upgrade"])
|
||||||
|
def test_graphql_system_rebuild_failed(
|
||||||
|
authorized_client, fp, action, mock_sleep_intervals
|
||||||
|
):
|
||||||
|
"""Test system rebuild"""
|
||||||
|
unit_name = f"sp-nixos-{action}.service"
|
||||||
|
query = (
|
||||||
|
API_REBUILD_SYSTEM_MUTATION
|
||||||
|
if action == "rebuild"
|
||||||
|
else API_UPGRADE_SYSTEM_MUTATION
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start the unit
|
||||||
|
fp.register(["systemctl", "start", unit_name])
|
||||||
|
|
||||||
|
# Wait for it to start
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="inactive")
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="inactive")
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="active")
|
||||||
|
|
||||||
|
# Check its exectution
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="active")
|
||||||
|
fp.register(
|
||||||
|
["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"],
|
||||||
|
stdout="Starting rebuild...",
|
||||||
|
)
|
||||||
|
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="active")
|
||||||
|
fp.register(
|
||||||
|
["journalctl", "-u", unit_name, "-n", "1", "-o", "cat"], stdout="Rebuilding..."
|
||||||
|
)
|
||||||
|
|
||||||
|
fp.register(["systemctl", "is-active", unit_name], stdout="failed")
|
||||||
|
|
||||||
|
response = authorized_client.post(
|
||||||
|
"/graphql",
|
||||||
|
json={
|
||||||
|
"query": query,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json().get("data") is not None
|
||||||
|
assert (
|
||||||
|
response.json()["data"]["system"][f"runSystem{action.capitalize()}"]["success"]
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
response.json()["data"]["system"][f"runSystem{action.capitalize()}"]["message"]
|
||||||
|
is not None
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
response.json()["data"]["system"][f"runSystem{action.capitalize()}"]["code"]
|
||||||
|
== 200
|
||||||
|
)
|
||||||
|
|
||||||
|
assert fp.call_count(["systemctl", "start", unit_name]) == 1
|
||||||
|
assert fp.call_count(["systemctl", "is-active", unit_name]) == 6
|
||||||
|
|
||||||
|
job_id = response.json()["data"]["system"][f"runSystem{action.capitalize()}"][
|
||||||
|
"job"
|
||||||
|
]["uid"]
|
||||||
|
assert Jobs.get_job(job_id).status == JobStatus.ERROR
|
||||||
|
assert Jobs.get_job(job_id).type_id == f"system.nixos.{action}"
|
||||||
|
|
||||||
|
|
||||||
API_ROLLBACK_SYSTEM_MUTATION = """
|
API_ROLLBACK_SYSTEM_MUTATION = """
|
||||||
|
|
Loading…
Reference in a new issue