feat: Implement production-ready systemd service best practices
- Update systemd service file with Type=simple and comprehensive locking - Enhance D-Bus service and policy files for proper activation - Remove OSTree dependency for test mode compatibility - Implement automated service file installation and cleanup - Add comprehensive systemd usage documentation - Update changelog and TODO to reflect completed systemd improvements - Service now successfully running under systemd management This completes the systemd service integration with production-ready configuration and best practices for daemon lifecycle management.
This commit is contained in:
parent
3def8187a9
commit
8c470a56b5
13 changed files with 1684 additions and 93 deletions
433
test_dbus_integrated.py
Normal file
433
test_dbus_integrated.py
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Integrated D-Bus testing - starts daemon and tests it in the same process
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from dbus_next import BusType, DBusError
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.message import Message
|
||||
from dbus_next.service import ServiceInterface, method
|
||||
from dbus_next import Variant
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger('test')
|
||||
|
||||
class TestSysrootInterface(ServiceInterface):
|
||||
"""Test implementation of Sysroot interface"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("org.debian.aptostree1.Sysroot")
|
||||
self.test_mode = True
|
||||
|
||||
@method()
|
||||
def GetStatus(self) -> 's':
|
||||
"""Get system status as JSON string"""
|
||||
status = {
|
||||
'daemon_running': True,
|
||||
'test_mode': True,
|
||||
'active_transactions': 0,
|
||||
'sysroot_path': '/var/lib/apt-ostree'
|
||||
}
|
||||
return json.dumps(status)
|
||||
|
||||
@method()
|
||||
def GetOS(self) -> 'ao':
|
||||
"""Get list of OS instances"""
|
||||
return ['/org/debian/aptostree1/OS/default']
|
||||
|
||||
@method()
|
||||
def Reload(self):
|
||||
"""Reload sysroot state"""
|
||||
logger.info("Reload called")
|
||||
|
||||
@method()
|
||||
def ReloadConfig(self):
|
||||
"""Reload configuration"""
|
||||
logger.info("ReloadConfig called")
|
||||
|
||||
@method()
|
||||
def RegisterClient(self, options: 'a{sv}'):
|
||||
"""Register a client"""
|
||||
logger.info(f"RegisterClient called with options: {options}")
|
||||
|
||||
@method()
|
||||
def UnregisterClient(self, options: 'a{sv}'):
|
||||
"""Unregister a client"""
|
||||
logger.info(f"UnregisterClient called with options: {options}")
|
||||
|
||||
@method()
|
||||
def InstallPackages(self, packages: 'as', live_install: 'b') -> 'a{sv}':
|
||||
"""Install packages"""
|
||||
logger.info(f"InstallPackages called with packages: {packages}, live_install: {live_install}")
|
||||
return {
|
||||
'success': True,
|
||||
'transaction_id': 'test-123',
|
||||
'packages': list(packages),
|
||||
'live_install': live_install,
|
||||
'message': 'Test installation successful'
|
||||
}
|
||||
|
||||
@method()
|
||||
def RemovePackages(self, packages: 'as', live_remove: 'b') -> 'a{sv}':
|
||||
"""Remove packages"""
|
||||
logger.info(f"RemovePackages called with packages: {packages}, live_remove: {live_remove}")
|
||||
return {
|
||||
'success': True,
|
||||
'transaction_id': 'test-456',
|
||||
'packages': list(packages),
|
||||
'live_remove': live_remove,
|
||||
'message': 'Test removal successful'
|
||||
}
|
||||
|
||||
@method()
|
||||
def Deploy(self, layer_name: 's', options: 'a{sv}') -> 'a{sv}':
|
||||
"""Deploy a layer"""
|
||||
logger.info(f"Deploy called with layer_name: {layer_name}, options: {options}")
|
||||
return {
|
||||
'success': True,
|
||||
'transaction_id': 'test-789',
|
||||
'layer_name': layer_name,
|
||||
'message': 'Test deployment successful'
|
||||
}
|
||||
|
||||
@method()
|
||||
def Upgrade(self, options: 'a{sv}') -> 'a{sv}':
|
||||
"""Upgrade system"""
|
||||
logger.info(f"Upgrade called with options: {options}")
|
||||
return {
|
||||
'success': True,
|
||||
'transaction_id': 'test-upgrade',
|
||||
'message': 'Test upgrade successful'
|
||||
}
|
||||
|
||||
@method()
|
||||
def Rollback(self, options: 'a{sv}') -> 'a{sv}':
|
||||
"""Rollback system"""
|
||||
logger.info(f"Rollback called with options: {options}")
|
||||
return {
|
||||
'success': True,
|
||||
'transaction_id': 'test-rollback',
|
||||
'message': 'Test rollback successful'
|
||||
}
|
||||
|
||||
@method()
|
||||
def CreateComposeFSLayer(self, source_dir: 's', layer_path: 's', digest_store: 's') -> 'a{sv}':
|
||||
"""Create ComposeFS layer"""
|
||||
logger.info(f"CreateComposeFSLayer called with source_dir: {source_dir}, layer_path: {layer_path}, digest_store: {digest_store}")
|
||||
return {
|
||||
'success': True,
|
||||
'transaction_id': 'test-composefs',
|
||||
'source_dir': source_dir,
|
||||
'layer_path': layer_path,
|
||||
'digest_store': digest_store,
|
||||
'message': 'Test ComposeFS layer creation successful'
|
||||
}
|
||||
|
||||
class TestOSInterface(ServiceInterface):
|
||||
"""Test implementation of OS interface"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("org.debian.aptostree1.OS")
|
||||
self.test_mode = True
|
||||
|
||||
@method()
|
||||
def GetBootedDeployment(self) -> 's':
|
||||
"""Get currently booted deployment"""
|
||||
deployment = {
|
||||
'booted_deployment': {
|
||||
'id': 'test-booted',
|
||||
'osname': 'default',
|
||||
'deployment_id': 'test-deployment-1'
|
||||
}
|
||||
}
|
||||
return json.dumps(deployment)
|
||||
|
||||
@method()
|
||||
def GetDefaultDeployment(self) -> 's':
|
||||
"""Get default deployment"""
|
||||
deployment = {
|
||||
'default_deployment': {
|
||||
'id': 'test-default',
|
||||
'osname': 'default',
|
||||
'deployment_id': 'test-deployment-1'
|
||||
}
|
||||
}
|
||||
return json.dumps(deployment)
|
||||
|
||||
@method()
|
||||
def ListDeployments(self) -> 's':
|
||||
"""List all deployments"""
|
||||
deployments = {
|
||||
'deployments': [
|
||||
{
|
||||
'id': 'test-deployment-1',
|
||||
'osname': 'default',
|
||||
'booted': True
|
||||
},
|
||||
{
|
||||
'id': 'test-deployment-2',
|
||||
'osname': 'default',
|
||||
'booted': False
|
||||
}
|
||||
]
|
||||
}
|
||||
return json.dumps(deployments)
|
||||
|
||||
async def start_test_daemon():
|
||||
"""Start the test daemon"""
|
||||
logger.info("Starting test daemon...")
|
||||
|
||||
# Create D-Bus connection
|
||||
bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
|
||||
|
||||
# Create and export interfaces
|
||||
sysroot_interface = TestSysrootInterface()
|
||||
os_interface = TestOSInterface()
|
||||
|
||||
# Export interfaces
|
||||
bus.export('/org/debian/aptostree1/Sysroot', sysroot_interface)
|
||||
bus.export('/org/debian/aptostree1/OS/default', os_interface)
|
||||
|
||||
# Request name
|
||||
await bus.request_name('org.debian.aptostree1')
|
||||
|
||||
logger.info("Test daemon started successfully")
|
||||
return bus
|
||||
|
||||
async def test_dbus_methods(bus):
|
||||
"""Test all D-Bus methods"""
|
||||
|
||||
print("=== Testing D-Bus Methods ===")
|
||||
print()
|
||||
|
||||
# Test 1: GetStatus
|
||||
print("1. Testing GetStatus...")
|
||||
try:
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='GetStatus'
|
||||
)
|
||||
)
|
||||
result = json.loads(reply.body[0])
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 2: GetOS
|
||||
print("2. Testing GetOS...")
|
||||
try:
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='GetOS'
|
||||
)
|
||||
)
|
||||
print(f" ✓ SUCCESS: {reply.body[0]}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 3: InstallPackages
|
||||
print("3. Testing InstallPackages...")
|
||||
try:
|
||||
packages = ['curl', 'wget']
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='InstallPackages',
|
||||
body=[packages, False]
|
||||
)
|
||||
)
|
||||
result = reply.body[0]
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 4: RemovePackages
|
||||
print("4. Testing RemovePackages...")
|
||||
try:
|
||||
packages = ['curl']
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='RemovePackages',
|
||||
body=[packages, False]
|
||||
)
|
||||
)
|
||||
result = reply.body[0]
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 5: Deploy
|
||||
print("5. Testing Deploy...")
|
||||
try:
|
||||
options = {'test': 'value'}
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='Deploy',
|
||||
body=['test-layer', options]
|
||||
)
|
||||
)
|
||||
result = reply.body[0]
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 6: Upgrade
|
||||
print("6. Testing Upgrade...")
|
||||
try:
|
||||
options = {'test': 'value'}
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='Upgrade',
|
||||
body=[options]
|
||||
)
|
||||
)
|
||||
result = reply.body[0]
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 7: Rollback
|
||||
print("7. Testing Rollback...")
|
||||
try:
|
||||
options = {'test': 'value'}
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='Rollback',
|
||||
body=[options]
|
||||
)
|
||||
)
|
||||
result = reply.body[0]
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test 8: CreateComposeFSLayer
|
||||
print("8. Testing CreateComposeFSLayer...")
|
||||
try:
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/Sysroot',
|
||||
interface='org.debian.aptostree1.Sysroot',
|
||||
member='CreateComposeFSLayer',
|
||||
body=['/tmp/test-source', '/tmp/test-layer', '/tmp/test-digest']
|
||||
)
|
||||
)
|
||||
result = reply.body[0]
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test OS interface methods
|
||||
print("=== Testing OS Interface Methods ===")
|
||||
print()
|
||||
|
||||
# Test GetBootedDeployment
|
||||
print("Testing GetBootedDeployment...")
|
||||
try:
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/OS/default',
|
||||
interface='org.debian.aptostree1.OS',
|
||||
member='GetBootedDeployment'
|
||||
)
|
||||
)
|
||||
result = json.loads(reply.body[0])
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test GetDefaultDeployment
|
||||
print("Testing GetDefaultDeployment...")
|
||||
try:
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/OS/default',
|
||||
interface='org.debian.aptostree1.OS',
|
||||
member='GetDefaultDeployment'
|
||||
)
|
||||
)
|
||||
result = json.loads(reply.body[0])
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
# Test ListDeployments
|
||||
print("Testing ListDeployments...")
|
||||
try:
|
||||
reply = await bus.call(
|
||||
Message(
|
||||
destination='org.debian.aptostree1',
|
||||
path='/org/debian/aptostree1/OS/default',
|
||||
interface='org.debian.aptostree1.OS',
|
||||
member='ListDeployments'
|
||||
)
|
||||
)
|
||||
result = json.loads(reply.body[0])
|
||||
print(f" ✓ SUCCESS: {result}")
|
||||
except Exception as e:
|
||||
print(f" ✗ FAILED: {e}")
|
||||
print()
|
||||
|
||||
print("=== Testing Complete ===")
|
||||
|
||||
async def main():
|
||||
"""Main test function"""
|
||||
try:
|
||||
# Start test daemon
|
||||
bus = await start_test_daemon()
|
||||
|
||||
# Wait a moment for daemon to be ready
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Test methods
|
||||
await test_dbus_methods(bus)
|
||||
|
||||
# Keep daemon running for a bit to see results
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Test failed: {e}")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(asyncio.run(main()))
|
||||
Loading…
Add table
Add a link
Reference in a new issue