Skip to content

Commit

Permalink
[sonic-package-manager] remove leftovers from featured on uninstall (s…
Browse files Browse the repository at this point in the history
…onic-net#3305)

- What I did
Added code to remove leftover symlinks and directories created by featured. Featured creates a symlink to /dev/null when unit is masked and an auto restart configuration is left under corresponding service.d/ directory.

- How I did it
Added necessary changes and UT to cover it.

- How to verify it
Uninstall an extension and verify no leftovers from featured.

Signed-off-by: Stepan Blyschak <stepanb@nvidia.com>
  • Loading branch information
stepanblyschak authored and mssonicbld committed May 22, 2024
1 parent 673da6b commit 804e053
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 11 deletions.
46 changes: 35 additions & 11 deletions sonic_package_manager/service_creator/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import contextlib
import os
import glob
import sys
import shutil
import stat
Expand Down Expand Up @@ -33,6 +34,7 @@
TIMER_UNIT_TEMPLATE = 'timer.unit.j2'

SYSTEMD_LOCATION = '/usr/lib/systemd/system'
ETC_SYSTEMD_LOCATION = '/etc/systemd/system'

GENERATED_SERVICES_CONF_FILE = '/etc/sonic/generated_services.conf'

Expand Down Expand Up @@ -92,18 +94,30 @@ def set_executable_bit(filepath):
os.chmod(filepath, st.st_mode | stat.S_IEXEC)


def remove_if_exists(path):
def remove_file(path):
""" Remove filepath if it exists """

if not os.path.exists(path):
return
try:
os.remove(path)
log.info(f'removed {path}')
except FileNotFoundError:
pass


def remove_dir(path):
""" Remove filepath if it exists """

try:
shutil.rmtree(path)
log.info(f'removed {path}')
except FileNotFoundError:
pass

os.remove(path)
log.info(f'removed {path}')

def is_list_of_strings(command):
return isinstance(command, list) and all(isinstance(item, str) for item in command)


def run_command(command: List[str]):
""" Run arbitrary bash command.
Args:
Expand Down Expand Up @@ -197,12 +211,22 @@ def remove(self,
"""

name = package.manifest['service']['name']
remove_if_exists(os.path.join(SYSTEMD_LOCATION, f'{name}.service'))
remove_if_exists(os.path.join(SYSTEMD_LOCATION, f'{name}@.service'))
remove_if_exists(os.path.join(SERVICE_MGMT_SCRIPT_LOCATION, f'{name}.sh'))
remove_if_exists(os.path.join(DOCKER_CTL_SCRIPT_LOCATION, f'{name}.sh'))
remove_if_exists(os.path.join(DEBUG_DUMP_SCRIPT_LOCATION, f'{name}'))
remove_if_exists(os.path.join(ETC_SONIC_PATH, f'{name}_reconcile'))
remove_file(os.path.join(SYSTEMD_LOCATION, f'{name}.service'))
remove_file(os.path.join(SYSTEMD_LOCATION, f'{name}@.service'))
remove_file(os.path.join(SERVICE_MGMT_SCRIPT_LOCATION, f'{name}.sh'))
remove_file(os.path.join(DOCKER_CTL_SCRIPT_LOCATION, f'{name}.sh'))
remove_file(os.path.join(DEBUG_DUMP_SCRIPT_LOCATION, f'{name}'))
remove_file(os.path.join(ETC_SONIC_PATH, f'{name}_reconcile'))

# remove symlinks and configuration directories created by featured
remove_file(os.path.join(ETC_SYSTEMD_LOCATION, f'{name}.service'))
for unit_file in glob.glob(os.path.join(ETC_SYSTEMD_LOCATION, f'{name}@*.service')):
remove_file(unit_file)

remove_dir(os.path.join(ETC_SYSTEMD_LOCATION, f'{name}.service.d'))
for unit_dir in glob.glob(os.path.join(ETC_SYSTEMD_LOCATION, f'{name}@*.service.d')):
remove_dir(unit_dir)

self.update_dependent_list_file(package, remove=True)
self.update_generated_services_conf_file(package, remove=True)

Expand Down
2 changes: 2 additions & 0 deletions tests/sonic_package_manager/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sonic_package_manager.registry import RegistryResolver
from sonic_package_manager.version import Version
from sonic_package_manager.service_creator.creator import *
from sonic_package_manager.service_creator.creator import ETC_SYSTEMD_LOCATION


@pytest.fixture
Expand Down Expand Up @@ -405,6 +406,7 @@ def fake_db_for_migration(fake_metadata_resolver):
def sonic_fs(fs):
fs.create_file('/proc/1/root')
fs.create_dir(ETC_SONIC_PATH)
fs.create_dir(ETC_SYSTEMD_LOCATION)
fs.create_dir(SYSTEMD_LOCATION)
fs.create_dir(DOCKER_CTL_SCRIPT_LOCATION)
fs.create_dir(SERVICE_MGMT_SCRIPT_LOCATION)
Expand Down
18 changes: 18 additions & 0 deletions tests/sonic_package_manager/test_service_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from sonic_package_manager.metadata import Metadata
from sonic_package_manager.package import Package
from sonic_package_manager.service_creator.creator import *
from sonic_package_manager.service_creator.creator import ETC_SYSTEMD_LOCATION
from sonic_package_manager.service_creator.feature import FeatureRegistry


Expand Down Expand Up @@ -106,6 +107,14 @@ def test_service_creator(sonic_fs, manifest, service_creator, package_manager):
assert sonic_fs.exists(os.path.join(SERVICE_MGMT_SCRIPT_LOCATION, 'test.sh'))
assert sonic_fs.exists(os.path.join(SYSTEMD_LOCATION, 'test.service'))

# Create symlinks and directory featured creates
os.symlink('/dev/null', os.path.join(ETC_SYSTEMD_LOCATION, 'test.service'))
os.symlink('/dev/null', os.path.join(ETC_SYSTEMD_LOCATION, 'test@1.service'))
os.symlink('/dev/null', os.path.join(ETC_SYSTEMD_LOCATION, 'test@2.service'))
os.mkdir(os.path.join(ETC_SYSTEMD_LOCATION, 'test.service.d'))
os.mkdir(os.path.join(ETC_SYSTEMD_LOCATION, 'test@1.service.d'))
os.mkdir(os.path.join(ETC_SYSTEMD_LOCATION, 'test@2.service.d'))

def read_file(name):
with open(os.path.join(ETC_SONIC_PATH, name)) as file:
return file.read()
Expand All @@ -118,6 +127,15 @@ def read_file(name):
assert generated_services_conf_content.endswith('\n')
assert set(generated_services_conf_content.split()) == set(['test.service', 'test@.service'])

service_creator.remove(package)

assert not sonic_fs.exists(os.path.join(ETC_SYSTEMD_LOCATION, 'test.service'))
assert not sonic_fs.exists(os.path.join(ETC_SYSTEMD_LOCATION, 'test@1.service'))
assert not sonic_fs.exists(os.path.join(ETC_SYSTEMD_LOCATION, 'test@2.service'))
assert not sonic_fs.exists(os.path.join(ETC_SYSTEMD_LOCATION, 'test.service.d'))
assert not sonic_fs.exists(os.path.join(ETC_SYSTEMD_LOCATION, 'test@1.service.d'))
assert not sonic_fs.exists(os.path.join(ETC_SYSTEMD_LOCATION, 'test@2.service.d'))


def test_service_creator_with_timer_unit(sonic_fs, manifest, service_creator):
entry = PackageEntry('test', 'azure/sonic-test')
Expand Down

0 comments on commit 804e053

Please sign in to comment.