mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2025-03-13 10:14:13 +00:00
Merge pull request 'rebuild-when-moving' (#101) from rebuild-when-moving into master
Reviewed-on: https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api/pulls/101 Reviewed-by: Inex Code <inex.code@selfprivacy.org>
This commit is contained in:
commit
efc6b47cfe
9 changed files with 141 additions and 91 deletions
selfprivacy_api
graphql/common_types
jobs
models
services
utils
tests/test_graphql
|
@ -2,6 +2,7 @@ import typing
|
||||||
import strawberry
|
import strawberry
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: use https://strawberry.rocks/docs/integrations/pydantic when it is stable
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class DnsRecord:
|
class DnsRecord:
|
||||||
"""DNS record"""
|
"""DNS record"""
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import typing
|
from typing import Optional, List
|
||||||
import strawberry
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import strawberry
|
||||||
|
|
||||||
from selfprivacy_api.graphql.common_types.backup import BackupReason
|
from selfprivacy_api.graphql.common_types.backup import BackupReason
|
||||||
from selfprivacy_api.graphql.common_types.dns import DnsRecord
|
from selfprivacy_api.graphql.common_types.dns import DnsRecord
|
||||||
|
|
||||||
from selfprivacy_api.services import get_service_by_id, get_services_by_location
|
from selfprivacy_api.services import get_service_by_id, get_services_by_location
|
||||||
from selfprivacy_api.services import Service as ServiceInterface
|
from selfprivacy_api.services import Service as ServiceInterface
|
||||||
|
from selfprivacy_api.services import ServiceDnsRecord
|
||||||
|
|
||||||
from selfprivacy_api.utils.block_devices import BlockDevices
|
from selfprivacy_api.utils.block_devices import BlockDevices
|
||||||
import selfprivacy_api.utils.network as network_utils
|
from selfprivacy_api.utils.network import get_ip4, get_ip6
|
||||||
|
|
||||||
|
|
||||||
def get_usages(root: "StorageVolume") -> list["StorageUsageInterface"]:
|
def get_usages(root: "StorageVolume") -> list["StorageUsageInterface"]:
|
||||||
|
@ -33,8 +36,8 @@ class StorageVolume:
|
||||||
used_space: str
|
used_space: str
|
||||||
root: bool
|
root: bool
|
||||||
name: str
|
name: str
|
||||||
model: typing.Optional[str]
|
model: Optional[str]
|
||||||
serial: typing.Optional[str]
|
serial: Optional[str]
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
|
@ -46,7 +49,7 @@ class StorageVolume:
|
||||||
@strawberry.interface
|
@strawberry.interface
|
||||||
class StorageUsageInterface:
|
class StorageUsageInterface:
|
||||||
used_space: str
|
used_space: str
|
||||||
volume: typing.Optional[StorageVolume]
|
volume: Optional[StorageVolume]
|
||||||
title: str
|
title: str
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ class StorageUsageInterface:
|
||||||
class ServiceStorageUsage(StorageUsageInterface):
|
class ServiceStorageUsage(StorageUsageInterface):
|
||||||
"""Storage usage for a service"""
|
"""Storage usage for a service"""
|
||||||
|
|
||||||
service: typing.Optional["Service"]
|
service: Optional["Service"]
|
||||||
|
|
||||||
|
|
||||||
@strawberry.enum
|
@strawberry.enum
|
||||||
|
@ -86,6 +89,20 @@ def get_storage_usage(root: "Service") -> ServiceStorageUsage:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: This won't be needed when deriving DnsRecord via strawberry pydantic integration
|
||||||
|
# https://strawberry.rocks/docs/integrations/pydantic
|
||||||
|
# Remove when the link above says it got stable.
|
||||||
|
def service_dns_to_graphql(record: ServiceDnsRecord) -> DnsRecord:
|
||||||
|
return DnsRecord(
|
||||||
|
record_type=record.type,
|
||||||
|
name=record.name,
|
||||||
|
content=record.content,
|
||||||
|
ttl=record.ttl,
|
||||||
|
priority=record.priority,
|
||||||
|
display_name=record.display_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class Service:
|
class Service:
|
||||||
id: str
|
id: str
|
||||||
|
@ -98,16 +115,26 @@ class Service:
|
||||||
can_be_backed_up: bool
|
can_be_backed_up: bool
|
||||||
backup_description: str
|
backup_description: str
|
||||||
status: ServiceStatusEnum
|
status: ServiceStatusEnum
|
||||||
url: typing.Optional[str]
|
url: Optional[str]
|
||||||
dns_records: typing.Optional[typing.List[DnsRecord]]
|
|
||||||
|
@strawberry.field
|
||||||
|
def dns_records(self) -> Optional[List[DnsRecord]]:
|
||||||
|
service = get_service_by_id(self.id)
|
||||||
|
if service is None:
|
||||||
|
raise LookupError(f"no service {self.id}. Should be unreachable")
|
||||||
|
|
||||||
|
raw_records = service.get_dns_records(get_ip4(), get_ip6())
|
||||||
|
dns_records = [service_dns_to_graphql(record) for record in raw_records]
|
||||||
|
return dns_records
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def storage_usage(self) -> ServiceStorageUsage:
|
def storage_usage(self) -> ServiceStorageUsage:
|
||||||
"""Get storage usage for a service"""
|
"""Get storage usage for a service"""
|
||||||
return get_storage_usage(self)
|
return get_storage_usage(self)
|
||||||
|
|
||||||
|
# TODO: fill this
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def backup_snapshots(self) -> typing.Optional[typing.List["SnapshotInfo"]]:
|
def backup_snapshots(self) -> Optional[List["SnapshotInfo"]]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,23 +160,10 @@ def service_to_graphql_service(service: ServiceInterface) -> Service:
|
||||||
backup_description=service.get_backup_description(),
|
backup_description=service.get_backup_description(),
|
||||||
status=ServiceStatusEnum(service.get_status().value),
|
status=ServiceStatusEnum(service.get_status().value),
|
||||||
url=service.get_url(),
|
url=service.get_url(),
|
||||||
dns_records=[
|
|
||||||
DnsRecord(
|
|
||||||
record_type=record.type,
|
|
||||||
name=record.name,
|
|
||||||
content=record.content,
|
|
||||||
ttl=record.ttl,
|
|
||||||
priority=record.priority,
|
|
||||||
display_name=record.display_name,
|
|
||||||
)
|
|
||||||
for record in service.get_dns_records(
|
|
||||||
network_utils.get_ip4(), network_utils.get_ip6()
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_volume_by_id(volume_id: str) -> typing.Optional[StorageVolume]:
|
def get_volume_by_id(volume_id: str) -> Optional[StorageVolume]:
|
||||||
"""Get volume by id"""
|
"""Get volume by id"""
|
||||||
volume = BlockDevices().get_block_device(volume_id)
|
volume = BlockDevices().get_block_device(volume_id)
|
||||||
if volume is None:
|
if volume is None:
|
||||||
|
|
|
@ -63,9 +63,13 @@ def check_running_status(job: Job, unit_name: str):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@huey.task()
|
def rebuild_system(job: Job, upgrade: bool = False):
|
||||||
def rebuild_system_task(job: Job, upgrade: bool = False):
|
"""
|
||||||
"""Rebuild the system"""
|
Broken out to allow calling it synchronously.
|
||||||
|
We cannot just block until task is done because it will require a second worker
|
||||||
|
Which we do not have
|
||||||
|
"""
|
||||||
|
|
||||||
unit_name = "sp-nixos-upgrade.service" if upgrade else "sp-nixos-rebuild.service"
|
unit_name = "sp-nixos-upgrade.service" if upgrade else "sp-nixos-rebuild.service"
|
||||||
try:
|
try:
|
||||||
command = ["systemctl", "start", unit_name]
|
command = ["systemctl", "start", unit_name]
|
||||||
|
@ -124,3 +128,9 @@ def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
status=JobStatus.ERROR,
|
status=JobStatus.ERROR,
|
||||||
status_text=str(e),
|
status_text=str(e),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@huey.task()
|
||||||
|
def rebuild_system_task(job: Job, upgrade: bool = False):
|
||||||
|
"""Rebuild the system"""
|
||||||
|
rebuild_system(job, upgrade)
|
||||||
|
|
24
selfprivacy_api/models/services.py
Normal file
24
selfprivacy_api/models/services.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceStatus(Enum):
|
||||||
|
"""Enum for service status"""
|
||||||
|
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
RELOADING = "RELOADING"
|
||||||
|
INACTIVE = "INACTIVE"
|
||||||
|
FAILED = "FAILED"
|
||||||
|
ACTIVATING = "ACTIVATING"
|
||||||
|
DEACTIVATING = "DEACTIVATING"
|
||||||
|
OFF = "OFF"
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceDnsRecord(BaseModel):
|
||||||
|
type: str
|
||||||
|
name: str
|
||||||
|
content: str
|
||||||
|
ttl: int
|
||||||
|
display_name: str
|
||||||
|
priority: Optional[int] = None
|
|
@ -1,13 +1,16 @@
|
||||||
"""Abstract class for a service running on a server"""
|
"""Abstract class for a service running on a server"""
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from selfprivacy_api import utils
|
||||||
from selfprivacy_api.jobs import Job, Jobs, JobStatus, report_progress
|
from selfprivacy_api.utils import ReadUserData, WriteUserData
|
||||||
|
from selfprivacy_api.utils.waitloop import wait_until_true
|
||||||
from selfprivacy_api.utils.block_devices import BlockDevice, BlockDevices
|
from selfprivacy_api.utils.block_devices import BlockDevice, BlockDevices
|
||||||
|
|
||||||
|
from selfprivacy_api.jobs import Job, Jobs, JobStatus, report_progress
|
||||||
|
from selfprivacy_api.jobs.upgrade_system import rebuild_system
|
||||||
|
|
||||||
|
from selfprivacy_api.models.services import ServiceStatus, ServiceDnsRecord
|
||||||
from selfprivacy_api.services.generic_size_counter import get_storage_usage
|
from selfprivacy_api.services.generic_size_counter import get_storage_usage
|
||||||
from selfprivacy_api.services.owned_path import OwnedPath, Bind
|
from selfprivacy_api.services.owned_path import OwnedPath, Bind
|
||||||
from selfprivacy_api.services.moving import (
|
from selfprivacy_api.services.moving import (
|
||||||
|
@ -20,34 +23,10 @@ from selfprivacy_api.services.moving import (
|
||||||
move_data_to_volume,
|
move_data_to_volume,
|
||||||
)
|
)
|
||||||
|
|
||||||
from selfprivacy_api import utils
|
|
||||||
from selfprivacy_api.utils.waitloop import wait_until_true
|
|
||||||
from selfprivacy_api.utils import ReadUserData, WriteUserData
|
|
||||||
|
|
||||||
DEFAULT_START_STOP_TIMEOUT = 5 * 60
|
DEFAULT_START_STOP_TIMEOUT = 5 * 60
|
||||||
|
|
||||||
|
|
||||||
class ServiceStatus(Enum):
|
|
||||||
"""Enum for service status"""
|
|
||||||
|
|
||||||
ACTIVE = "ACTIVE"
|
|
||||||
RELOADING = "RELOADING"
|
|
||||||
INACTIVE = "INACTIVE"
|
|
||||||
FAILED = "FAILED"
|
|
||||||
ACTIVATING = "ACTIVATING"
|
|
||||||
DEACTIVATING = "DEACTIVATING"
|
|
||||||
OFF = "OFF"
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceDnsRecord(BaseModel):
|
|
||||||
type: str
|
|
||||||
name: str
|
|
||||||
content: str
|
|
||||||
ttl: int
|
|
||||||
display_name: str
|
|
||||||
priority: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Service(ABC):
|
class Service(ABC):
|
||||||
"""
|
"""
|
||||||
Service here is some software that is hosted on the server and
|
Service here is some software that is hosted on the server and
|
||||||
|
@ -387,14 +366,6 @@ class Service(ABC):
|
||||||
report_progress(95, job, f"Finishing moving {service_name}...")
|
report_progress(95, job, f"Finishing moving {service_name}...")
|
||||||
self.set_location(new_volume)
|
self.set_location(new_volume)
|
||||||
|
|
||||||
Jobs.update(
|
|
||||||
job=job,
|
|
||||||
status=JobStatus.FINISHED,
|
|
||||||
result=f"{service_name} moved successfully.",
|
|
||||||
status_text=f"Starting {service_name}...",
|
|
||||||
progress=100,
|
|
||||||
)
|
|
||||||
|
|
||||||
def move_to_volume(self, volume: BlockDevice, job: Job) -> Job:
|
def move_to_volume(self, volume: BlockDevice, job: Job) -> Job:
|
||||||
service_name = self.get_display_name()
|
service_name = self.get_display_name()
|
||||||
|
|
||||||
|
@ -407,6 +378,17 @@ class Service(ABC):
|
||||||
report_progress(9, job, "Stopped service, starting the move...")
|
report_progress(9, job, "Stopped service, starting the move...")
|
||||||
self.do_move_to_volume(volume, job)
|
self.do_move_to_volume(volume, job)
|
||||||
|
|
||||||
|
report_progress(98, job, "Move complete, rebuilding...")
|
||||||
|
rebuild_system(job, upgrade=False)
|
||||||
|
|
||||||
|
Jobs.update(
|
||||||
|
job=job,
|
||||||
|
status=JobStatus.FINISHED,
|
||||||
|
result=f"{service_name} moved successfully.",
|
||||||
|
status_text=f"Starting {service_name}...",
|
||||||
|
progress=100,
|
||||||
|
)
|
||||||
|
|
||||||
return job
|
return job
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -8,10 +8,9 @@ from os import path
|
||||||
|
|
||||||
# from enum import Enum
|
# from enum import Enum
|
||||||
|
|
||||||
from selfprivacy_api.jobs import Job, Jobs, JobStatus
|
from selfprivacy_api.jobs import Job
|
||||||
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
|
from selfprivacy_api.services.service import Service, ServiceStatus
|
||||||
from selfprivacy_api.utils.block_devices import BlockDevice
|
from selfprivacy_api.utils.block_devices import BlockDevice
|
||||||
import selfprivacy_api.utils.network as network_utils
|
|
||||||
|
|
||||||
from selfprivacy_api.services.test_service.icon import BITWARDEN_ICON
|
from selfprivacy_api.services.test_service.icon import BITWARDEN_ICON
|
||||||
|
|
||||||
|
@ -89,7 +88,7 @@ class DummyService(Service):
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_status(cls, status: ServiceStatus):
|
def set_status(cls, status: ServiceStatus):
|
||||||
with open(cls.status_file(), "w") as file:
|
with open(cls.status_file(), "w") as file:
|
||||||
status_string = file.write(status.value)
|
file.write(status.value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_status(cls) -> ServiceStatus:
|
def get_status(cls) -> ServiceStatus:
|
||||||
|
@ -102,16 +101,17 @@ class DummyService(Service):
|
||||||
cls, new_status: ServiceStatus, delay_sec: float
|
cls, new_status: ServiceStatus, delay_sec: float
|
||||||
):
|
):
|
||||||
"""simulating a delay on systemd side"""
|
"""simulating a delay on systemd side"""
|
||||||
status_file = cls.status_file()
|
if delay_sec == 0:
|
||||||
|
cls.set_status(new_status)
|
||||||
|
return
|
||||||
|
|
||||||
|
status_file = cls.status_file()
|
||||||
command = [
|
command = [
|
||||||
"bash",
|
"bash",
|
||||||
"-c",
|
"-c",
|
||||||
f" sleep {delay_sec} && echo {new_status.value} > {status_file}",
|
f" sleep {delay_sec} && echo {new_status.value} > {status_file}",
|
||||||
]
|
]
|
||||||
handle = subprocess.Popen(command)
|
subprocess.Popen(command)
|
||||||
if delay_sec == 0:
|
|
||||||
handle.communicate()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_backuppable(cls, new_value: bool) -> None:
|
def set_backuppable(cls, new_value: bool) -> None:
|
||||||
|
@ -192,6 +192,5 @@ class DummyService(Service):
|
||||||
if self.simulate_moving is False:
|
if self.simulate_moving is False:
|
||||||
return super(DummyService, self).do_move_to_volume(volume, job)
|
return super(DummyService, self).do_move_to_volume(volume, job)
|
||||||
else:
|
else:
|
||||||
Jobs.update(job, status=JobStatus.FINISHED)
|
|
||||||
self.set_drive(volume.name)
|
self.set_drive(volume.name)
|
||||||
return job
|
return job
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from selfprivacy_api.services.service import ServiceStatus
|
from selfprivacy_api.models.services import ServiceStatus
|
||||||
|
|
||||||
|
|
||||||
def get_service_status(service: str) -> ServiceStatus:
|
def get_service_status(unit: str) -> ServiceStatus:
|
||||||
"""
|
"""
|
||||||
Return service status from systemd.
|
Return service status from systemd.
|
||||||
Use systemctl show to get the status of a service.
|
Use systemctl show to get the status of a service.
|
||||||
Get ActiveState from the output.
|
Get ActiveState from the output.
|
||||||
"""
|
"""
|
||||||
service_status = subprocess.check_output(["systemctl", "show", service])
|
service_status = subprocess.check_output(["systemctl", "show", unit])
|
||||||
if b"LoadState=not-found" in service_status:
|
if b"LoadState=not-found" in service_status:
|
||||||
return ServiceStatus.OFF
|
return ServiceStatus.OFF
|
||||||
if b"ActiveState=active" in service_status:
|
if b"ActiveState=active" in service_status:
|
||||||
|
|
|
@ -13,8 +13,7 @@ from selfprivacy_api.services.test_service import DummyService
|
||||||
|
|
||||||
from tests.common import generate_service_query
|
from tests.common import generate_service_query
|
||||||
from tests.test_graphql.common import assert_empty, assert_ok, get_data
|
from tests.test_graphql.common import assert_empty, assert_ok, get_data
|
||||||
from tests.test_block_device_utils import lsblk_singular_mock
|
from tests.test_graphql.test_system_nixos_tasks import prepare_nixos_rebuild_calls
|
||||||
|
|
||||||
|
|
||||||
LSBLK_BLOCKDEVICES_DICTS = [
|
LSBLK_BLOCKDEVICES_DICTS = [
|
||||||
{
|
{
|
||||||
|
@ -618,10 +617,7 @@ def test_graphql_move_service_without_folders_on_old_volume(
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_move_service(
|
def test_graphql_move_service(
|
||||||
authorized_client,
|
authorized_client, generic_userdata, mock_check_volume, dummy_service_with_binds, fp
|
||||||
generic_userdata,
|
|
||||||
mock_check_volume,
|
|
||||||
dummy_service_with_binds,
|
|
||||||
):
|
):
|
||||||
dummy_service = dummy_service_with_binds
|
dummy_service = dummy_service_with_binds
|
||||||
|
|
||||||
|
@ -633,10 +629,30 @@ def test_graphql_move_service(
|
||||||
dummy_service.set_drive(origin)
|
dummy_service.set_drive(origin)
|
||||||
dummy_service.set_simulated_moves(False)
|
dummy_service.set_simulated_moves(False)
|
||||||
|
|
||||||
|
unit_name = "sp-nixos-rebuild.service"
|
||||||
|
rebuild_command = ["systemctl", "start", unit_name]
|
||||||
|
prepare_nixos_rebuild_calls(fp, unit_name)
|
||||||
|
|
||||||
|
# We will be mounting and remounting folders
|
||||||
|
mount_command = ["mount", fp.any()]
|
||||||
|
unmount_command = ["umount", fp.any()]
|
||||||
|
fp.pass_command(mount_command, 2)
|
||||||
|
fp.pass_command(unmount_command, 2)
|
||||||
|
|
||||||
|
# We will be changing ownership
|
||||||
|
chown_command = ["chown", fp.any()]
|
||||||
|
fp.pass_command(chown_command, 2)
|
||||||
|
|
||||||
mutation_response = api_move(authorized_client, dummy_service, target)
|
mutation_response = api_move(authorized_client, dummy_service, target)
|
||||||
|
|
||||||
data = get_data(mutation_response)["services"]["moveService"]
|
data = get_data(mutation_response)["services"]["moveService"]
|
||||||
assert_ok(data)
|
assert_ok(data)
|
||||||
|
assert data["service"] is not None
|
||||||
|
|
||||||
|
assert fp.call_count(rebuild_command) == 1
|
||||||
|
assert fp.call_count(mount_command) == 2
|
||||||
|
assert fp.call_count(unmount_command) == 2
|
||||||
|
assert fp.call_count(chown_command) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_mailservice_cannot_enable_disable(authorized_client):
|
def test_mailservice_cannot_enable_disable(authorized_client):
|
||||||
|
|
|
@ -97,16 +97,7 @@ def test_graphql_system_rebuild_unauthorized(client, fp, action):
|
||||||
assert fp.call_count([fp.any()]) == 0
|
assert fp.call_count([fp.any()]) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("action", ["rebuild", "upgrade"])
|
def prepare_nixos_rebuild_calls(fp, unit_name):
|
||||||
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
|
# Start the unit
|
||||||
fp.register(["systemctl", "start", unit_name])
|
fp.register(["systemctl", "start", unit_name])
|
||||||
|
|
||||||
|
@ -129,6 +120,19 @@ def test_graphql_system_rebuild(authorized_client, fp, action, mock_sleep_interv
|
||||||
|
|
||||||
fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive")
|
fp.register(["systemctl", "show", unit_name], stdout="ActiveState=inactive")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("action", ["rebuild", "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
|
||||||
|
)
|
||||||
|
|
||||||
|
prepare_nixos_rebuild_calls(fp, unit_name)
|
||||||
|
|
||||||
response = authorized_client.post(
|
response = authorized_client.post(
|
||||||
"/graphql",
|
"/graphql",
|
||||||
json={
|
json={
|
||||||
|
|
Loading…
Add table
Reference in a new issue