blob: 71c5e88ada363c8dce646d96cef19f4a866d1622 [file] [log] [blame]
# Copyright 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import threading
from . import sync_utils
from . import type_utils
from .process_utils import CheckOutput
from .process_utils import OpenDevNull
START_TEXT = 'start/running'
STOP_TEXT = 'stop/waiting'
Status = type_utils.Enum(['START', 'STOP', 'UNKNOWN'])
def ParseServiceStatus(status_output):
"""Parses service status output and returns service status."""
if START_TEXT in status_output:
return Status.START
if STOP_TEXT in status_output:
return Status.STOP
return Status.UNKNOWN
def SetServiceStatus(service, status=None, dut=None):
"""Sets service to the given status and returns the status"""
upstart_command = {
None: 'status',
Status.START: 'start',
Status.STOP: 'stop'}[status]
check_output = dut.CheckOutput if dut else CheckOutput
cmd = [upstart_command, service]
logging.info('SetServiceStatus: cmd=%r, function=%r', cmd, check_output)
stdout_data = check_output(cmd)
new_status = ParseServiceStatus(stdout_data)
if status is not None:
if new_status == status:
logging.info('Service %s set to %s', service, status)
else:
logging.warning('Failed to set %s to %s (%s)', service, status,
stdout_data)
return new_status
def GetServiceStatus(service, ignore_failure=False, dut=None):
"""Returns service status.
Args:
service: The service name we want to acquire status.
ignore_failure: True to suppress exception when failed to get status.
Returns:
Service status. If ignore_failure is True, a None will be returned
when failing to get status.
"""
try:
return SetServiceStatus(service, None, dut)
except Exception:
if not ignore_failure:
raise
logging.exception('Failed to get service %s.', service)
return None
def CheckServiceExists(service, dut=None):
"""Check if the given service name exists or not.
Use 'status' command and check its return code. If the command
excutes successfully, the service is considered existed.
And, vice versa.
Args:
service: The service name to test existence
dut: optional argument to check the service on the given DUT
Returns:
A boolean flag tells if the given service name exists or not.
"""
try:
check_output = dut.CheckOutput if dut else CheckOutput
cmd = ['status', service]
check_output(cmd, stderr=OpenDevNull())
except Exception:
return False
return True
class ServiceManager:
"""Object to manage services for tests.
Use SetupServices to setup services that should be enabled or disabled
before invoking a test. When the test finishes, call RestoreServices to
restore services that was affected to their status before.
"""
# This is a class variable that counts the balance of setup/restore calls: it
# will be incresed by 1 for each SetupServices() called, and decreased by 1
# for each RestoreServices() called.
_service_setup_count = 0
_lock = threading.RLock()
_original_status_map = {}
_enable_services = []
_disable_services = []
def __init__(self, dut=None):
# Lock is needed for @sync_utils.Synchronized.
# Let all instances use the same lock from class to avoid race condition of
# accessing class members.
self._lock = ServiceManager._lock
self.dut = dut
@classmethod
def _GetServiceSetupCount(cls):
"""Return the current service_setup_count.
Returns:
service_setup_count: The current balance of setup/restore calls.
"""
return cls._service_setup_count
@classmethod
def _SetServiceSetupCount(cls, new_count):
"""Set service_setup_count to a new value.
Args:
new_count: The new balance of setup/restore calls.
"""
cls._service_setup_count = new_count
@sync_utils.Synchronized
def SetupServices(self, enable_services=None, disable_services=None):
"""Makes sure the services in enable_services are started and those in
disable_services are stopped.
Args:
enable_services: A list of services that should be started.
disable_services: A list of services that should be stopped.
"""
enable_services = enable_services or []
disable_services = disable_services or []
service_setup_count = self._GetServiceSetupCount()
self._SetServiceSetupCount(service_setup_count + 1)
if service_setup_count > 0:
if (set(ServiceManager._enable_services) != set(enable_services) or
set(ServiceManager._disable_services) != set(disable_services)):
logging.warning('Trying to setup services lists which are '
'different from the existing ones. Do nothing. '
'Existing enable_services: %s. '
'Existing disable_services: %s. '
'New enable_services: %s. '
'New disable_services: %s.',
ServiceManager._enable_services,
ServiceManager._disable_services,
enable_services, disable_services)
else:
logging.debug('Services have already set up, do nothing with extra '
'SetupServices(). '
'service_setup_count: %d', service_setup_count)
return
if set(enable_services) & set(disable_services):
logging.warning('Trying to setup intersecting service lists. '
'Do nothing. New enable_services: %s. '
'New disable_services: %s.',
enable_services, disable_services)
return
ServiceManager._enable_services = []
ServiceManager._disable_services = []
for status, services in (
(Status.START, enable_services),
(Status.STOP, disable_services)):
for service in services:
try:
original_status = GetServiceStatus(service, dut=self.dut)
if original_status != status:
ServiceManager._original_status_map[service] = original_status
SetServiceStatus(service, status, self.dut)
except Exception:
# Reach here probably because disable or enable some non-existing
# services. Do not add this service to the restore list.
logging.exception('Unable to set service status of %s.', service)
else:
if status == Status.START:
ServiceManager._enable_services.append(service)
else:
ServiceManager._disable_services.append(service)
@sync_utils.Synchronized
def RestoreServices(self):
"""Restores the services affected in SetupServices back to their original
states."""
service_setup_count = self._GetServiceSetupCount()
if service_setup_count < 1:
# There should be at least 1 setup before calling restore. May happen
# if RestoreServices() is called in wrong way.
logging.error('There is no corresponding SetupServices() called before '
'RestoreServices().')
return
self._SetServiceSetupCount(service_setup_count - 1)
if service_setup_count > 1:
logging.debug('Services are still being used by other tests. Waiting for '
'the last RestoreServices(). '
'service_setup_count: %d', service_setup_count)
return
# Restore services if there's only 1 setup record left, which means this
# should be the last restore request.
for service, status in ServiceManager._original_status_map.items():
SetServiceStatus(service, status, self.dut)
ServiceManager._original_status_map.clear()
ServiceManager._enable_services = []
ServiceManager._disable_services = []