#! /usr/bin/env python
# coding=utf-8
# Copyright 2019 VMware, Inc.  All rights reserved. -- VMware Confidential
#
'''
This module provides a utility for manipulating local OS services.

Example:
    serviceManager = getServiceManager()
    serviceManager.stop('vmware-vpxd')
    status = serviceManager.getStatus('vmware-vpxd')
    print status
'''
import os
import tempfile

__author__ = "VMware, Inc."

import logging
import time
import re

from vcsa_utils import isFswitchEnabled
from transport import ErrorCode, ExecutionException
from os_utils import getCommandExitCode, Platform

# =============================================================================
# Globals
# =============================================================================

logger = logging.getLogger(__name__)
SERVICE_OPERATION_TIMEOUT = 20 * 60  # 20 minutes
SERVICE_STATUS_CHECK_INTERVAL = 1  # 1 second

# =============================================================================
# Service Manager
# =============================================================================

class ServiceStatus:
    '''Lists possible Windows/Linux service statuses.
    '''
    UNKNOWN = "unknown"
    STARTED = 'started'
    STARTING = 'starting'
    STOPPED = 'stopped'
    STOPPING = 'stopping'
    PAUSED = 'paused'

class ServiceStarttype:
    '''Lists possible Windows/Linux service starttype.
    '''
    AUTOMATIC = 'automatic'
    DISABLED = 'disabled'
    MANUAL = 'manual'
    UNSUPPORTED = 'unsupported'
    UNKNOWN = 'unknown'

class IllegalServiceOperation(Exception):
    '''Thrown to indicate that the service command has failed.
    '''

class IllegalServiceError(IllegalServiceOperation):
    '''Thrown to indicate that a service passed by the consumer is an illegal
      or inappropriate.
    '''

class ServiceResult:
    '''Keep the execution result of command to a remote service.
    '''
    def __init__(self, **kwargs):
        self.output = kwargs['stdout']
        self.error = kwargs['stderr']
        self.exitCode = kwargs['exitCode']

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        result = "stdout: %s" % self.output
        if self.error:
            result += "\nstderr: %s" % self.error

        return result

# =============================================================================
# Service Manager Status Parsers
# =============================================================================

class ServiceResultParser:
    '''Serves as an abstract base for custom status parsers. Such parsers are
       usually necessary for parsing Linux services status values.
    '''

    # list of platforms supported by the parser
    supportedPlatforms = []

    def parse(self, serviceResult, serviceName):
        ''' Convert the result from the service status command to the
          status defined by the contract. See: class ServiceStatus.

        @param serviceResult: The execution result of service status command.
          The type of the property is ServiceResult.
        @type serviceResult: ServiceResult

        @param serviceName: name of service which status is being retrieved.
        @type serviceName: str

        @return: A member of ServiceStatus.
        @rtype: ServiceResult

        @throws IllegalServiceError: if the service doesn't exist
        '''
        raise NotImplementedError("Derived classes must implement this method.")

class LinuxServiceStatusParser(ServiceResultParser):
    '''Parser responsible to parse the status of Linux services.'''

    def __init__(self):
        ''' Builds status parser for Linux OS responsible to parse the status
        command and return the appropriate result. It checks the status command
        exitCode and returns STARTED if it's 0, otherwise STOPPED. This is
        default VMware services implementation but for more detailed status
        users should either supply custom parsers or use
        ServiceManager.getStatusRawData().
        '''
        self.supportedPlatforms = [Platform.LINUX]

    def parse(self, serviceResult, serviceName):
        '''
        @see: ServiceStatusParser.parse
        '''
        serviceStatus = ServiceStatus.STARTED \
            if serviceResult.exitCode == 0 else ServiceStatus.STOPPED

        return serviceStatus

class WindowsServiceStatusParser(ServiceResultParser):
    '''Parser responsible to parse the status of Windows services.'''

    def __init__(self):
        '''Builds status parser for Windows OS responsible to parse the status
        command and return the appropriate result.
        '''
        self.supportedPlatforms = [Platform.WINDOWS]

    def parse(self, serviceResult, serviceName):
        '''
        @see: ServiceStatusParser.parse
        '''
        pattern = r".*:\s*\d+\s*(\w+)\s*.*"
        serviceOutput = str(serviceResult.output).strip().split('\n')
        status = None
        if len(serviceOutput) > 2:
            serviceStatusLine = serviceOutput[2]
            logger.info('Parsing service status %s', serviceStatusLine)
            matcher = re.match(pattern, serviceStatusLine, re.DOTALL)
            if matcher:
                status = matcher.group(1)

        if status is None:
            raise IllegalServiceError("Service '%s' does not exist." % (serviceName))

        logger.info("Service %s reported status %s", serviceName, status)

        serviceStatus = ServiceStatus.UNKNOWN  # UNKNOWN by default
        if status.upper() == 'RUNNING':
            serviceStatus = ServiceStatus.STARTED
        elif status.upper() == 'PAUSED':
            serviceStatus = ServiceStatus.PAUSED
        elif status.upper() == 'STOPPED':
            serviceStatus = ServiceStatus.STOPPED
        elif status.upper() == 'STOP_PENDING':
            serviceStatus = ServiceStatus.STOPPING
        elif status.upper() == 'START_PENDING':
            serviceStatus = ServiceStatus.STARTING

        return serviceStatus

class ServiceStatusParser(ServiceResultParser):
    '''Parser responsible to parse the status of any Linux or Windows services.'''

    # This class represents the internal statuses of service-control command
    class _Status(object):
        RUNNING = 'Running'
        STOPPED = 'Stopped'
        START_PENDING = 'StartPending'
        STOP_PENDING = 'StopPending'

    def __init__(self):
        ''' Creates a status parser responsible to parse the status
        command and return the appropriate result. It checks the status command
        exitCode and returns STARTED if services is in Running state, otherwise STOPPED.
        This is default VMware services implementation but for more detailed status
        users should either supply custom parsers or use ServiceManager.getStatusRawData().
        '''
        self.supportedPlatforms = [Platform.LINUX, Platform.WINDOWS]

    def parse(self, serviceResult, serviceName):
        '''
        @see: ServiceStatusParser.parse
        '''
        pattern = '%s:'
        serviceOutput = str(serviceResult.output).strip().split('\n')
        for serviceOutputLine in serviceOutput:
            if pattern % ServiceStatusParser._Status.RUNNING in serviceOutputLine:
                return ServiceStatus.STARTED
            elif pattern % ServiceStatusParser._Status.STOPPED in serviceOutputLine:
                return ServiceStatus.STOPPED
            elif pattern % ServiceStatusParser._Status.START_PENDING in serviceOutputLine:
                return ServiceStatus.STARTING
            elif pattern % ServiceStatusParser._Status.STOP_PENDING in serviceOutputLine:
                return ServiceStatus.STOPPING

        return ServiceStatus.UNKNOWN

class ServiceStarttypeParser(ServiceResultParser):
    '''Parser responsible to parse the starttype of any Linux or Windows services.'''

    # This class represents the internal statuses of service-control command
    class _Status(object):
        AUTOMATIC = 'AUTOMATIC'
        MANUAL = 'MANUAL'
        DISABLED = 'DISABLED'

    def __init__(self):
        ''' Creates a starttype parser responsible to parse the starttype
        command and return the appropriate result. It checks the status command
        exitCode and returns AUTOMATIC if services is with starttype AUTOMATIC, etc.
        '''
        self.supportedPlatforms = [Platform.LINUX, Platform.WINDOWS]

    def parse(self, serviceResult, serviceName):
        '''
        @see: ServiceStatusParser.parse
        '''
        pattern = 'Starttype: %s'
        serviceOutput = str(serviceResult.output).strip().split('\n')
        for serviceOutputLine in serviceOutput:
            if pattern % ServiceStarttypeParser._Status.AUTOMATIC in serviceOutputLine:
                return ServiceStarttype.AUTOMATIC
            elif pattern % ServiceStarttypeParser._Status.MANUAL in serviceOutputLine:
                return ServiceStarttype.MANUAL
            elif pattern % ServiceStarttypeParser._Status.DISABLED in serviceOutputLine:
                return ServiceStarttype.DISABLED

        return ServiceStarttype.UNKNOWN

# =============================================================================
# Service Commands
# =============================================================================

# Type of commands
class ServiceCommands(object):
    START = 'start'
    STOP = 'stop'
    RESTART = 'restart'
    STATUS = 'status'
    STARTTYPE = 'starttype'

SERVICE_COMMAND_FACTORY = {
    Platform.WINDOWS : {
            ServiceCommands.START : ['net start %s'],
            ServiceCommands.STOP : ['net stop %s /y'],
            ServiceCommands.RESTART : ['net stop %s', 'net start %s'],
            ServiceCommands.STATUS : ['sc query %s'],
            ServiceCommands.STARTTYPE : []
    },
    Platform.LINUX : {
            ServiceCommands.START : ['/sbin/service %s start'],
            ServiceCommands.STOP : ['/sbin/service %s stop'],
            ServiceCommands.RESTART : ['/sbin/service %s restart'],
            ServiceCommands.STATUS : ['/sbin/service %s status'],
            ServiceCommands.STARTTYPE : []
    }
}

SERVICE_CONTROL_PATH = {
    Platform.WINDOWS : '%VMWARE_CIS_HOME%\\bin\\service-control.bat',
    Platform.LINUX : '/bin/service-control'
}

VMON_CLI_PATH = {
    Platform.WINDOWS : '%VMWARE_CIS_HOME%\\bin\\vmon-cli.bat',
    Platform.LINUX : '/usr/sbin/vmon-cli'
}

VMWARE_SERVICE_COMMAND_FACTORY = {
    Platform.WINDOWS : {
            ServiceCommands.START : ['{0} --start %s'.format(SERVICE_CONTROL_PATH[Platform.WINDOWS])],
            ServiceCommands.STOP : ['{0} --stop %s'.format(SERVICE_CONTROL_PATH[Platform.WINDOWS])],
            ServiceCommands.RESTART : ['{0} --stop %s'.format(SERVICE_CONTROL_PATH[Platform.WINDOWS]),
                                       '{0} --start %s'.format(SERVICE_CONTROL_PATH[Platform.WINDOWS])],
            ServiceCommands.STATUS : ['{0} --status %s'.format(SERVICE_CONTROL_PATH[Platform.WINDOWS])],
            ServiceCommands.STARTTYPE : ['{0} --status %s'.format(VMON_CLI_PATH[Platform.WINDOWS])]
    },
    Platform.LINUX : {
            ServiceCommands.START : ['{0} --start %s'.format(SERVICE_CONTROL_PATH[Platform.LINUX])],
            ServiceCommands.STOP : ['{0} --stop %s'.format(SERVICE_CONTROL_PATH[Platform.LINUX])],
            ServiceCommands.RESTART : ['{0} --stop %s'.format(SERVICE_CONTROL_PATH[Platform.LINUX]),
                                       '{0} --start %s'.format(SERVICE_CONTROL_PATH[Platform.LINUX])],
            ServiceCommands.STATUS : ['{0} --status %s'.format(SERVICE_CONTROL_PATH[Platform.LINUX])],
            ServiceCommands.STARTTYPE : ['{0} --status %s'.format(VMON_CLI_PATH[Platform.LINUX])]
    }
}

# Service status parser factory for the different OSes
SERVICE_STATUS_PARSER_FACTORY = {
    Platform.WINDOWS : WindowsServiceStatusParser,
    Platform.LINUX : LinuxServiceStatusParser,
}

# Service status parser factory for the different OSes
VMWARE_SERVICE_STATUS_PARSER_FACTORY = {
    Platform.WINDOWS : ServiceStatusParser,
    Platform.LINUX : ServiceStatusParser,
}

# Service starttpe parser factory for the different OSes
VMWARE_SERVICE_STARTTYPE_PARSER_FACTORY = {
    Platform.WINDOWS : ServiceStarttypeParser,
    Platform.LINUX : ServiceStarttypeParser,
}

# =============================================================================
# Utilities
# =============================================================================

def _execCommand(command):
    ''' Executes a service command on the specified local host.

    @param command: Commands that need to be run on the remote machine.
    @type command: Array of comannd arguments

    @return: The command result in a string format
    @rtype: ServiceResult

    '''

    def _safeRemoveFile(filename):
        try:
            if os.path.exists(filename): os.remove(filename)
        except IOError as ioe:
            logger.warning("Can not remove temporary file '%s'. Cause: %s",
                           filename, ioe)

    def _safeGetFileContent(filename):
        content = ""
        if os.path.exists(filename):
            try:
                with open(filename, 'r') as fp:
                    content = fp.read()
            except IOError as ioe:
                logger.warning('Cannot read the file %s. Cause: %s',
                               filename, str(ioe))
        else:
            logger.info('File content cannot be retrieved because file %s '
                        'does not exist', filename)
        return content

    stdoutFilename = None
    stderrFilename = None
    try:
        commandName = os.path.basename(command[0])
        stdoutFilename = tempfile.mktemp(suffix=".log", prefix="%s-stdout-" %
                                         commandName)
        stderrFilename = tempfile.mktemp(suffix=".log", prefix="%s-stderr-" %
                                         commandName)
        exitCode = getCommandExitCode(command,
                                          localStdoutFile=stdoutFilename,
                                          localStderrFile=stderrFilename)

        stdout = _safeGetFileContent(stdoutFilename)
        stderr = _safeGetFileContent(stderrFilename)
        serviceResult = ServiceResult(stdout=stdout, stderr=stderr,
                                      exitCode=exitCode)
        return serviceResult
    except ExecutionException as e:
        if e.error_code == ErrorCode.INVALID_REQUEST:
            raise IllegalServiceError("Service does not exist. '%s' " %
                                        command)
        else:
            raise IllegalServiceOperation("Unexpected error occurs: %s" % e.msg)
    finally:
        _safeRemoveFile(stdoutFilename)
        _safeRemoveFile(stderrFilename)

def _createCommand(commandPatterns, serviceName):
    '''Creates a well formatted OS independent command for manipulating
    a given service.

    @param commandPatterns: A command pattern, e.g. /sbin/service %s start
    @type commandPatterns: str

    @param serviceName: A name of the underline service being manipulate
        with this comamnd
    @type serviceName: str

    @return: A well formatted service command
    @rtype: str
    '''
    commands = []
    for commandPattern in commandPatterns:
        command = []
        for arg in commandPattern.split(' '):
            if "%s" in arg:
                arg = arg % serviceName
            command.append(arg)
        commands.append(command)
    return  commands

def _getVMwareServices(platform):
    ''' Gets all vmware service names that can be controlled with service-control

    @param platform: The platform its running on
    @type platform: Platform

    @return: All VMware service that can be manipulated with service-control
    @rtype: Array of VMware service name
    '''
    if not os.path.exists(SERVICE_CONTROL_PATH[platform]):
        return ()

    command = [SERVICE_CONTROL_PATH[platform], '--list']
    logger.info("Executing command '%s'", command)
    result = _execCommand(command)
    if result.exitCode != 0:
        logger.warning("VMware service could not be extracted. "\
                       "Command '%s' has exit-code='%s'",
                       command, result.exitCode)
        return ()

    logger.info("Command '%s' has exit-code='%s' and %s", command,
                result.exitCode, result)

    lineFilter =  lambda line: line and not line.startswith('INFO:root:') \
                                and ('vmware-vmon' not in line or isFswitchEnabled('vmware-vmon'))
    # Parse the output as remove empty lines and logging e.g. INFO:root:
    return list((line.split()[0].strip() for line in result.output.split('\n') \
                                            if lineFilter(line)))

def _getVmonVMwareServices(platform):
    ''' Gets all vmware service names that can be controlled with vmon-cli

    @param platform: The platform its running on
    @type platform: Platform

    @return: All VMware service that can be manipulated with vmon-cli
    @rtype: Array of VMware service name
    '''

    if not os.path.exists(VMON_CLI_PATH[platform]):
        return ()

    command = [VMON_CLI_PATH[platform], '--list']
    logger.info("Executing command '%s'", command)
    result = _execCommand(command)
    if result.exitCode != 0:
        logger.warning("VMware vmon service could not be extracted. "\
                       "Command '%s' has exit-code='%s'",
                       command, result.exitCode)
        return ()

    logger.info("Command '%s' has exit-code='%s' and %s", command,
                result.exitCode, result)

    return [line for line in result.output.split('\n') if line]

class ServiceController(object):
    '''Represents a controller/manager which can be used to manipulate any service
    registered in Windows SCM or Linux /bin/service on local host regardless its OS system.
    Note: This class should never be instantiated directly
    '''

    def __init__(self, platform, commandFactory=None, statusParser=None, starttypeParser=None):
        ''' Create an instance of service-manager responsible to
          manipulate the services on the local host.

        @param platform: The platform this manager will be running on
        @type platform: Platform

        @param commandFactory: Service command factory provides service manipulating
            commands for different OSes, e.g. start/stop/restart
            Example:
            {
                'linux' : {
                   'start' : ['service vmware-vpxd start'],
                   'stop' : ['service vmware-vpxd stop'],
                   'restart' : ['service vmware-vpxd stop', 'service vmware-vpxd start'],
                   'status' : ['service vmware-vpxd status'],
                   'depends' : []
                },
                'windows' : {}
            }
        @type commandFactory: dict

        @param statusParser: Parser of the service status
        @type statusParser: ServiceResultParser

        @param starttypeParser: Parser of the service starttype
        @type starttypeParser: ServiceResultParser

        @raise ValueError: if the service URI is invalid.

        @raise ReferenceError: if reference to the ComponentAssistant or underlying
          controller is invalid
        '''
        if not platform:
            raise ReferenceError('Illegal platform reference.')

        self.platform = platform
        self.commandFactory = commandFactory or SERVICE_COMMAND_FACTORY[self.platform]
        self.statusParser = statusParser or SERVICE_STATUS_PARSER_FACTORY[self.platform]()
        self.starttypeParser = starttypeParser or VMWARE_SERVICE_STARTTYPE_PARSER_FACTORY[self.platform]()
        if not self.platform in self.statusParser.supportedPlatforms:
            raise ValueError(
                'Supplied status parser does not support current platform -- %s.'
                % self.platform)

    def _executeServiceCommand(self, serviceCommand, serviceName):
        ''' Executes a service command on the local host.

        @param serviceCommand: Service command that need to be run on the remote
          machine. The command is either start/stop/restart or status.

        @param serviceName: Name of the service being manipulating

        @param commandFactory: A factory of the service commands

        @return: The command result as array of strings.

        @throws IllegalServiceError: if the service doesn't exist
        '''
        commands = _createCommand(self.commandFactory[serviceCommand], serviceName)

        if not commands:
            logger.info('Nothing to execute for service % and command %s', serviceName, serviceCommand)
            return ServiceResult(stdout='', stderr='', exitCode=0)

        logger.info("Executing command '%s'", commands)

        output = error = ''
        exitCode = 0
        try:
            for command in commands:
                res = _execCommand(command)

                # TODO Do we still need that logic?
                # XXX: Some VMware services(e.g. vmware-rhttpproxy) calls watch-dog daemon
                # and needs several ms until their service is really started. I will
                # file a bug for every one of them to fix their service, but until then
                # this patch should workaround the problem
                if serviceCommand in [ServiceCommands.START, ServiceCommands.STOP]:
                    time.sleep(1)

                output += '%s\n' % res.output
                error += '%s\n' % res.error
                exitCode = res.exitCode
                if res.exitCode != 0:
                    # If a single command failed there is no need to execute
                    # the rest because they are a single logical unit
                    break
        except ExecutionException as e:
            if e.error_code == ErrorCode.INVALID_REQUEST:
                raise IllegalServiceError("Service '%s' does not exist." %
                                        serviceName)
            else:
                raise IllegalServiceOperation("Unexpected error occurs: %s" % e.msg)

        # Aggregated result for all commands
        result = ServiceResult(stdout=output, stderr=error,
                             exitCode=exitCode)
        logger.info("Command '%s' has exit-code='%s' and %s", commands,
                    result.exitCode, result)
        return result

    def __isServiceOperationSuccessful(self, serviceName, expectedStatus,
                                       expectedPendingStatus=None):
        '''Waits for service command to complete and tests
            if has been successfully executed.

        @param serviceName: Name of the service

        @param expectedStatus: Expected service status

        @param expectedPendingStatus: Expected service status
            if operation is still in progress, otherwise None

        @return: <code>True</code> if service is moved to expected state and
          <code>False</code> otherwise.
        '''
        startTime = time.time()
        currTime = startTime
        while currTime < (startTime + SERVICE_OPERATION_TIMEOUT):
            currTime = time.time()
            serviceStatus = self.__getStatus(serviceName, self.statusParser)
            if serviceStatus != expectedPendingStatus:
                break
            time.sleep(SERVICE_STATUS_CHECK_INTERVAL)

        logger.info('Service %s reported status %s. Expected status %s',
            serviceName, serviceStatus, expectedStatus)

        return serviceStatus == expectedStatus

    def __isServiceSuccssfullyStarted(self, serviceName):
        '''Test if the service has been successfully started.
        '''
        return self.__isServiceOperationSuccessful(
            serviceName, ServiceStatus.STARTED, ServiceStatus.STARTING)

    def __isServiceSuccssfullyStopped(self, serviceName):
        '''Test if the service has been successfully stopped.
        '''
        return self.__isServiceOperationSuccessful(
            serviceName, ServiceStatus.STOPPED, ServiceStatus.STOPPING)

    def __getStatus(self, serviceName, statusParser):
        '''Gets the status of a service with the given name. This method blocks
        until the status is retrieved.

        @param serviceName: Name of the service which status will be retrieved

        @param statusParser: A parser of the service status

        @return: A status of the service.

        @throws IllegalServiceError: if the service doesn't exist
        @throws ValueError: if the supplied parser does not support
                            the current ServiceManager.platform
        '''
        logger.debug("Retrieving status from service '%s' ...", serviceName)
        cmdResult = self._executeServiceCommand(ServiceCommands.STATUS, serviceName)
        logger.debug("Status of service %s has been retrieved.", serviceName)

        status = statusParser.parse(cmdResult, serviceName)
        logger.debug("Successfully parsed service status as -- '%s'.", status)

        return status

    # =================================#
    # Service Manager Public Interface #
    # =================================#

    def start(self, serviceName):
        '''Starts the underlined service. This method blocks until the services
          is completely started.

        @param serviceName: Name of the service being started

        @throws IllegalServiceError if the service doesn't exist
        @throws IllegalServiceOperation: if the service cannot be started
        '''
        if self.__getStatus(serviceName, self.statusParser) == ServiceStatus.STARTED:
            logger.info('The service %s has been already started', serviceName)
            return

        logger.info("Starting service '%s' ...", serviceName)
        serviceResult = self._executeServiceCommand(ServiceCommands.START, serviceName)
        logger.debug("Start service %s result: %s", serviceName, serviceResult)

        if not self.__isServiceSuccssfullyStarted(serviceName):
            errorText = "Service cannot be started. Error: %s" % \
                    serviceResult.error
            logger.error(errorText)
            raise IllegalServiceOperation(errorText)

        logger.info("Service %s: %s", serviceName, ServiceStatus.STARTED.upper())

    def stop(self, serviceName):
        '''Stops the underlined service. This method blocks until the services
        is completely stopped.

        @param serviceName: Name of the service being stopped

        @throws IllegalServiceError: if the service doesn't exist
        @throws IllegalServiceOperation: if the service cannot be stopped
        '''
        if self.__getStatus(serviceName, self.statusParser) == ServiceStatus.STOPPED:
            logger.info('The service %s has been already stopped', serviceName)
            return

        logger.info("Stopping service '%s' ...", serviceName)

        # Stop the underline service
        serviceResult = self._executeServiceCommand(ServiceCommands.STOP, serviceName)
        logger.debug("Stop service %s result: %s", serviceName, serviceResult)

        if not self.__isServiceSuccssfullyStopped(serviceName):
            errorText = "Service cannot be stopped. Error: %s" % \
                    serviceResult.error

            logger.error(errorText)
            raise IllegalServiceOperation(errorText)

        logger.info("Service %s: %s", serviceName, ServiceStatus.STOPPED.upper())

    def restart(self, serviceName):
        '''Restarts the underlined service. This method blocks until the services
        is completely restarted.

        @param serviceName: Name of the service being restarted

        @throws IllegalServiceError: if the service doesn't exist
        @throws IllegalServiceOperation: if the service cannot be restarted
        '''
        logger.debug("Restarting service '%s' ...", serviceName)
        self.stop(serviceName)
        self.start(serviceName)

    def getStatus(self, serviceName, statusParser=None):
        '''Gets the status of a service with the given name. This method blocks
        until the status is retrieved.

        @param serviceName: Name of the service which status will be retrieved

        @param statusParser: Custom status parser object. It must implement the
                             ServiceResultParser interface and provide support
                             for the current ServiceManager.platform

        @return: A status of the service.

        @throws IllegalServiceError: if the service doesn't exist
        @throws ValueError: if the supplied parser does not support
                            the current ServiceManager.platform
        '''
        logger.info("Retrieving status from service '%s' ...", serviceName)
        status = self.__getStatus(serviceName, statusParser or self.statusParser)
        logger.info("Service %s: %s", serviceName, status.upper())
        return status

    def getStatusRawData(self, serviceName):
        '''Gets the raw status text returned by querying the given service.

        @param serviceName: Name of the service which status will be retrieved

        @return: ServiceResult which represents the raw status result of the service.

        @throws IllegalServiceError: if the service doesn't exist
        '''
        logger.info("Retrieving raw status data from service '%s' ...",
                     serviceName)
        cmdResult = self._executeServiceCommand(ServiceCommands.STATUS, serviceName)
        logger.info("Raw status data of service %s has been retrieved.",
                     serviceName)

        return cmdResult

    def  getStarttype(self, serviceName):
        '''Gets the starttype  by querying the given service.

        @param serviceName: Name of the service which Starttype will be retrieved

        @return: ServiceResult which represents the Starttype result of the service.
            Returns UNKNOWN if cannot find information about the service
        '''
        logger.info("Retrieving starttype from generic service '%s' ...", serviceName)
        return ServiceStarttype.UNSUPPORTED

class VMwareServiceController(ServiceController):
    '''Represents a controller/manager which can be used to manipulate VMware specific
    service on either local or remote host regardless its OS system.
    Note: This class should never be instantiated directly
    '''

    def __init__(self, platform, supportedVmwareServices):
        ''' Create an instance of service-manager responsible to
          manipulate the services on the remote host specified by the compAssist.

        @param platform: The platform this manager will be running on
        @type platform: Platform

        @param supportedVmwareServices: Immutable list of service names this
        controller can support
        @type supportedVmwareServices: Array of strings

        @raise ValueError: if the service URI is invalid.

        @raise ReferenceError: if reference to the ComponentAssistant or underlying
          controller is invalid
        '''
        super(VMwareServiceController, self).__init__(platform,
                                                      commandFactory=VMWARE_SERVICE_COMMAND_FACTORY[platform],
                                                      statusParser=VMWARE_SERVICE_STATUS_PARSER_FACTORY[platform](),
                                                      starttypeParser=VMWARE_SERVICE_STARTTYPE_PARSER_FACTORY[platform]())

        self.vmwareServices = set(supportedVmwareServices)
        self.vmonServices = _getVmonVMwareServices(platform)

    def start(self, serviceName):
        ''' @see ServiceController.start '''
        if serviceName not in self.vmwareServices:
            raise IllegalServiceOperation('Unknown service %s.' % serviceName)

        super(VMwareServiceController, self).start(serviceName)

    def stop(self, serviceName):
        ''' @see ServiceController.stop '''
        if serviceName not in self.vmwareServices:
            raise IllegalServiceOperation('Unknown service %s.' % serviceName)

        super(VMwareServiceController, self).stop(serviceName)

    def restart(self, serviceName):
        ''' @see ServiceController.restart '''
        if serviceName not in self.vmwareServices:
            raise IllegalServiceOperation('Unknown service %s.' % serviceName)

        super(VMwareServiceController, self).restart(serviceName)

    def getStatus(self, serviceName, statusParser=None):
        ''' @see ServiceController.getStatus '''
        if serviceName not in self.vmwareServices:
            raise IllegalServiceOperation('Unknown service %s.' % serviceName)

        return super(VMwareServiceController, self).getStatus(serviceName)

    def getStatusRawData(self, serviceName):
        ''' @see ServiceController.getStatusRawData '''
        if serviceName not in self.vmwareServices:
            raise IllegalServiceOperation('Unknown service %s.' % serviceName)

        return super(VMwareServiceController, self).getStatusRawData(serviceName)

    def  getStarttype(self, serviceName):
        ''' @see ServiceController.getStatusRawData '''
        logger.info("Getting starttype of service %s", serviceName)
        if serviceName not in self.vmwareServices:
            raise IllegalServiceOperation('Unknown service %s.' % serviceName)

        # This is as the vmon-cli does not use same names
        #TODO once switching is complete remove that
        if serviceName not in ['vmware-vpostgres'] \
            and serviceName.startswith('vmware-'):
            serviceName = serviceName[len('vmware-'):]
            if serviceName.startswith('rbd-w'):
                serviceName='rbd'

        if serviceName not in self.vmonServices:
            return ServiceStarttype.UNKNOWN

        rawStarttype = self._executeServiceCommand(ServiceCommands.STARTTYPE, serviceName)
        cmdResult = self.starttypeParser.parse(rawStarttype, serviceName)
        logger.info("Starttype of service %s has been retrieved.", serviceName)

        return cmdResult

class ServiceManager(object):
    '''Represents a manager which can be used to manipulate the service on the
    remote host regardless its OS system.
    Note: This class should never be instantiated directly rather than use
    getServiceManager() method to create an appropriate instance
    '''

    OPERATIONS = ['start', 'stop', 'restart', 'getStatus', 'getStatusRawData', 'getStarttype']

    def __init__(self, platform, serviceControllers=None):
        ''' Create an instance of service-manager responsible to
          manipulate the services on the remote host specified by the compAssist.

        @param platform: The platform this manager will be running on
        @type platform: Platform

        @param serviceControllers: A controllers responsible to manipulate any Windows or
            Linux service. This is a list of pairs ([service-names], ServiceController) where
            [service-names] is immutable list of the names of the services for which this
            controller should be invoked. Default for all services will be ServiceController.
        @type serviceControllers: Array of tuples ([service-names], ServiceController)

        @raise ValueError: if the service URI is invalid.

        @raise ReferenceError: if reference to the ComponentAssistant or underlying
            controller is invalid
        '''
        if not platform:
            raise ReferenceError('Illegal platform reference.')

        self.platform = platform
        self.defaultController = ServiceController(self.platform)
        # A registry of service-controllers
        self.controllers = [] if serviceControllers is None \
                            else serviceControllers

    def __getController(self, serviceName):
        ''' Select the right controller from the registry by a given serviceName. '''
        for k, controller in self.controllers:
            if serviceName in k:
                return controller
        return self.defaultController

    def __getattr__(self, attr):
        if attr not in ServiceManager.OPERATIONS:
            # Unsupported operation
            raise AttributeError(attr)

        # Automatic redirection to super for all public methods
        def wrapper(*args, **kwargs):
            if len(args) <= 0:
                raise ValueError('Subroutine %s must have at least a single argument "service-name".' % attr)
            # args[0] always be serviceName
            controller = self.__getController(args[0])
            return getattr(controller, attr)(*args, **kwargs)
        return wrapper

# =============================================================================
# Service Manager Factory
# =============================================================================



def getServiceManager():
    '''Create a service-manager responsible to manipulate services
    OS independently.
    '''
    # Refresh the LOCAL_VMWARE_SERVICES list for every call to getServiceManager
    # Refreshing the list was added due to the PR# 2410262
    LOCAL_VMWARE_SERVICES = _getVMwareServices(Platform.LINUX)
    return ServiceManager(Platform.LINUX, serviceControllers=[(LOCAL_VMWARE_SERVICES,
                                                              VMwareServiceController(Platform.LINUX, LOCAL_VMWARE_SERVICES))])
