diff --git a/selfprivacy_api/services/service.py b/selfprivacy_api/services/service.py index 9c96b40..6fbc5f7 100644 --- a/selfprivacy_api/services/service.py +++ b/selfprivacy_api/services/service.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod from typing import List, Optional +from os.path import exists from selfprivacy_api import utils from selfprivacy_api.services.config_item import ServiceConfigItem @@ -237,6 +238,16 @@ class Service(ABC): storage_used += get_storage_usage(folder) return storage_used + @classmethod + def has_folders(cls) -> int: + """ + If there are no folders on disk, moving is noop + """ + for folder in cls.get_folders(): + if exists(folder): + return True + return False + @classmethod def get_dns_records(cls, ip4: str, ip6: Optional[str]) -> List[ServiceDnsRecord]: subdomain = cls.get_subdomain() @@ -358,7 +369,10 @@ class Service(ABC): binds = self.binds() if binds == []: raise MoveError("nothing to move") - check_binds(current_volume_name, binds) + + # It is ok if service is uninitialized, we will just reregister it + if self.has_folders(): + check_binds(current_volume_name, binds) def do_move_to_volume( self, @@ -401,7 +415,18 @@ class Service(ABC): service_name = self.get_display_name() report_progress(0, job, "Performing pre-move checks...") + self.assert_can_move(volume) + if not self.has_folders(): + self.set_location(volume) + Jobs.update( + job=job, + status=JobStatus.FINISHED, + result=f"{service_name} moved successfully(no folders).", + status_text=f"NOT starting {service_name}", + progress=100, + ) + return job report_progress(5, job, f"Stopping {service_name}...") assert self is not None diff --git a/selfprivacy_api/services/test_service/__init__.py b/selfprivacy_api/services/test_service/__init__.py index c442459..f345c85 100644 --- a/selfprivacy_api/services/test_service/__init__.py +++ b/selfprivacy_api/services/test_service/__init__.py @@ -5,6 +5,7 @@ import subprocess from typing import List from os import path +from pathlib import Path # from enum import Enum @@ -73,8 +74,11 @@ class DummyService(Service): @classmethod def status_file(cls) -> str: dir = cls.folders[0] - # we do not REALLY want to store our state in our declared folders - return path.join(dir, "..", "service_status") + # We do not want to store our state in our declared folders + # Because they are moved and tossed in tests wildly + parent = Path(dir).parent + + return path.join(parent, "service_status") @classmethod def set_status(cls, status: ServiceStatus): diff --git a/tests/test_graphql/test_services.py b/tests/test_graphql/test_services.py index b349f53..97f13f3 100644 --- a/tests/test_graphql/test_services.py +++ b/tests/test_graphql/test_services.py @@ -2,7 +2,7 @@ import pytest import shutil from typing import Generator -from os import mkdir +from os import mkdir, rmdir from selfprivacy_api.utils.block_devices import BlockDevices @@ -605,6 +605,11 @@ def test_graphql_move_service_without_folders_on_old_volume( mock_lsblk_devices, dummy_service: DummyService, ): + """ + Situation when you have folders in the filetree but they are not mounted + but just folders + """ + target = "sda1" BlockDevices().update() assert BlockDevices().get_block_device(target) is not None @@ -618,6 +623,59 @@ def test_graphql_move_service_without_folders_on_old_volume( assert "sda2/test_service is not found" in data["message"] +def test_move_empty( + authorized_client, generic_userdata, mock_check_volume, dummy_service, fp +): + """ + A reregister of uninitialized service with no data. + No binds in place yet, and no rebuilds should happen. + """ + + origin = "sda1" + target = "sda2" + assert BlockDevices().get_block_device(target) is not None + assert BlockDevices().get_block_device(origin) is not None + + dummy_service.set_drive(origin) + dummy_service.set_simulated_moves(False) + dummy_service.disable() + + unit_name = "sp-nixos-rebuild.service" + rebuild_command = ["systemctl", "start", unit_name] + prepare_nixos_rebuild_calls(fp, unit_name) + + # We will NOT 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 NOT be changing ownership + chown_command = ["chown", fp.any()] + fp.pass_command(chown_command, 2) + + # We have virtual binds encapsulating our understanding where this should go. + assert len(dummy_service.binds()) == 2 + + # Remove all folders + for folder in dummy_service.get_folders(): + shutil.rmtree(folder) + + # They are virtual and unaffected by folder removal + assert len(dummy_service.binds()) == 2 + + mutation_response = api_move(authorized_client, dummy_service, target) + + data = get_data(mutation_response)["services"]["moveService"] + assert_ok(data) + assert data["service"] is not None + + assert fp.call_count(rebuild_command) == 0 + assert fp.call_count(mount_command) == 0 + assert fp.call_count(unmount_command) == 0 + assert fp.call_count(chown_command) == 0 + + def test_graphql_move_service( authorized_client, generic_userdata, mock_check_volume, dummy_service_with_binds, fp ):