Adds Windows service CRUD methods

Adds functionality to create and delete given services.
Additionally the WMI calls for managing Windows services
are replaced with win32 calls.

Change-Id: I3576cb48510e4f7bb13dc48e1c68cddf76e1fb1c
Co-Authored-By: Stefan Caraiman <scaraiman@cloudbasesolutions.com>
This commit is contained in:
Alessandro Pilotti 2017-03-06 12:03:43 +02:00 committed by Stefan Caraiman
parent 47d48afa75
commit a9975e06b6
3 changed files with 258 additions and 124 deletions

View File

@ -138,6 +138,31 @@ class BaseOSUtils(object):
"""Set the username and password for a given service."""
raise NotImplementedError()
def create_service(self, service_name, display_name, path, start_mode,
username=None, password=None):
raise NotImplementedError()
def delete_service(self, service_name):
raise NotImplementedError()
def get_service_status(self, service_name):
raise NotImplementedError()
def check_service_exists(self, service_name):
raise NotImplementedError()
def get_service_start_mode(self, service_name):
raise NotImplementedError()
def set_service_start_mode(self, service_name, start_mode):
raise NotImplementedError()
def start_service(self, service_name):
raise NotImplementedError()
def stop_service(self, service_name, wait=False):
raise NotImplementedError()
def get_service_username(self, service_name):
"""Retrieve the username under which a service runs."""
raise NotImplementedError()

View File

@ -367,6 +367,31 @@ class WindowsUtils(base.BaseOSUtils):
SERVICE_START_MODE_MANUAL = "Manual"
SERVICE_START_MODE_DISABLED = "Disabled"
_SERVICE_START_TYPE_MAP = {
SERVICE_START_MODE_AUTOMATIC:
win32service.SERVICE_AUTO_START,
SERVICE_START_MODE_MANUAL:
win32service.SERVICE_DEMAND_START,
SERVICE_START_MODE_DISABLED:
win32service.SERVICE_DISABLED}
_SERVICE_STATUS_MAP = {
win32service.SERVICE_CONTINUE_PENDING:
SERVICE_STATUS_CONTINUE_PENDING,
win32service.SERVICE_PAUSE_PENDING:
SERVICE_STATUS_PAUSE_PENDING,
win32service.SERVICE_PAUSED:
SERVICE_STATUS_PAUSED,
win32service.SERVICE_RUNNING:
SERVICE_STATUS_RUNNING,
win32service.SERVICE_START_PENDING:
SERVICE_STATUS_START_PENDING,
win32service.SERVICE_STOP_PENDING:
SERVICE_STATUS_STOP_PENDING,
win32service.SERVICE_STOPPED:
SERVICE_STATUS_STOPPED,
}
ComputerNamePhysicalDnsHostname = 5
_config_key = 'SOFTWARE\\Cloudbase Solutions\\Cloudbase-Init\\'
@ -865,13 +890,8 @@ class WindowsUtils(base.BaseOSUtils):
else:
raise ex
def _get_service(self, service_name):
conn = wmi.WMI(moniker='//./root/cimv2')
service_list = conn.Win32_Service(Name=service_name)
if len(service_list):
return service_list[0]
def check_service_exists(self, service_name):
LOG.debug("Checking if service exists: %s", service_name)
try:
with self._get_service_handle(service_name):
return True
@ -882,57 +902,110 @@ class WindowsUtils(base.BaseOSUtils):
raise
def get_service_status(self, service_name):
service = self._get_service(service_name)
return service.State
LOG.debug("Getting service status for: %s", service_name)
with self._get_service_handle(
service_name, win32service.SERVICE_QUERY_STATUS) as hs:
service_status = win32service.QueryServiceStatusEx(hs)
state = service_status['CurrentState']
return self._SERVICE_STATUS_MAP.get(
state, WindowsUtils.SERVICE_STATUS_UNKNOWN)
def get_service_start_mode(self, service_name):
service = self._get_service(service_name)
return service.StartMode
LOG.debug("Getting service start mode for: %s", service_name)
with self._get_service_handle(
service_name, win32service.SERVICE_QUERY_CONFIG) as hs:
service_config = win32service.QueryServiceConfig(hs)
start_type = service_config[1]
return [k for k, v in self._SERVICE_START_TYPE_MAP.items()
if v == start_type][0]
def set_service_start_mode(self, service_name, start_mode):
# TODO(alexpilotti): Handle the "Delayed Start" case
service = self._get_service(service_name)
(ret_val,) = service.ChangeStartMode(start_mode)
if ret_val != 0:
raise exception.CloudbaseInitException(
'Setting service %(service_name)s start mode failed with '
'return value: %(ret_val)d' % {'service_name': service_name,
'ret_val': ret_val})
LOG.debug("Setting service start mode for: %s", service_name)
start_type = self._get_win32_start_type(start_mode)
with self._get_service_handle(
service_name, win32service.SERVICE_CHANGE_CONFIG) as hs:
win32service.ChangeServiceConfig(
hs, win32service.SERVICE_NO_CHANGE,
start_type, win32service.SERVICE_NO_CHANGE,
None, None, False, None, None, None, None)
def start_service(self, service_name):
LOG.debug('Starting service %s', service_name)
service = self._get_service(service_name)
(ret_val,) = service.StartService()
if ret_val != 0:
raise exception.CloudbaseInitException(
'Starting service %(service_name)s failed with return value: '
'%(ret_val)d' % {'service_name': service_name,
'ret_val': ret_val})
with self._get_service_handle(
service_name, win32service.SERVICE_START) as hs:
win32service.StartService(hs, service_name)
def stop_service(self, service_name):
def stop_service(self, service_name, wait=False):
LOG.debug('Stopping service %s', service_name)
service = self._get_service(service_name)
(ret_val,) = service.StopService()
if ret_val != 0:
raise exception.CloudbaseInitException(
'Stopping service %(service_name)s failed with return value:'
' %(ret_val)d' % {'service_name': service_name,
'ret_val': ret_val})
with self._get_service_handle(
service_name,
win32service.SERVICE_STOP |
win32service.SERVICE_QUERY_STATUS) as hs:
win32service.ControlService(hs, win32service.SERVICE_CONTROL_STOP)
if wait:
while True:
service_status = win32service.QueryServiceStatusEx(hs)
state = service_status['CurrentState']
if state == win32service.SERVICE_STOPPED:
return
time.sleep(.1)
@staticmethod
@contextlib.contextmanager
def _get_service_control_manager(
scm_access=win32service.SC_MANAGER_CONNECT):
hscm = win32service.OpenSCManager(None, None, scm_access)
try:
yield hscm
finally:
win32service.CloseServiceHandle(hscm)
@staticmethod
@contextlib.contextmanager
def _get_service_handle(service_name,
service_access=win32service.SERVICE_QUERY_CONFIG,
scm_access=win32service.SC_MANAGER_CONNECT):
hscm = win32service.OpenSCManager(None, None, scm_access)
hs = None
try:
with WindowsUtils._get_service_control_manager(scm_access) as hscm:
hs = win32service.OpenService(hscm, service_name, service_access)
yield hs
finally:
if hs:
try:
yield hs
finally:
win32service.CloseServiceHandle(hs)
win32service.CloseServiceHandle(hscm)
@staticmethod
def _get_win32_start_type(start_mode):
start_type = WindowsUtils._SERVICE_START_TYPE_MAP.get(start_mode)
if not start_type:
raise exception.InvalidStateException(
"Invalid service start mode: %s" % start_mode)
return start_type
def create_service(self, service_name, display_name, path, start_mode,
username=None, password=None):
LOG.debug('Creating service %s', service_name)
start_type = self._get_win32_start_type(start_mode)
with WindowsUtils._get_service_control_manager(
scm_access=win32service.SC_MANAGER_CREATE_SERVICE) as hscm:
hs = win32service.CreateService(
hscm, service_name, display_name,
win32service.SERVICE_ALL_ACCESS,
win32service.SERVICE_WIN32_OWN_PROCESS,
start_type,
win32service.SERVICE_ERROR_NORMAL,
path, None, False, None,
username, password)
win32service.CloseServiceHandle(hs)
def delete_service(self, service_name):
LOG.debug('Deleting service %s', service_name)
with self._get_service_handle(
service_name, win32service.SERVICE_ALL_ACCESS) as hs:
win32service.DeleteService(hs)
def set_service_credentials(self, service_name, username, password):
LOG.debug('Setting service credentials: %s', service_name)

View File

@ -879,16 +879,6 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
ret_vals = [[1], [7]]
self._test_wait_for_boot_completion(ret_vals=ret_vals)
def test_get_service(self):
conn = self._wmi_mock.WMI
conn.return_value.Win32_Service.return_value = ['fake name']
response = self._winutils._get_service('fake name')
conn.assert_called_with(moniker='//./root/cimv2')
conn.return_value.Win32_Service.assert_called_with(Name='fake name')
self.assertEqual('fake name', response)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service_handle')
def test_check_service(self, mock_get_service_handle):
@ -934,6 +924,48 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
close_service.assert_has_calls([mock.call(mock.sentinel.hs),
mock.call(mock.sentinel.hscm)])
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service_control_manager')
def test_create_service(self, mock_get_service_control_manager):
mock_hs = mock.MagicMock()
mock_service_name = "fake name"
mock_start_mode = "Automatic"
mock_display_name = mock.sentinel.mock_display_name
mock_path = mock.sentinel.path
mock_get_service_control_manager.return_value = mock_hs
with self.snatcher:
self._winutils.create_service(mock_service_name,
mock_display_name,
mock_path,
mock_start_mode)
self.assertEqual(["Creating service fake name"],
self.snatcher.output)
mock_get_service_control_manager.assert_called_once_with(
scm_access=self._win32service_mock.SC_MANAGER_CREATE_SERVICE)
self._win32service_mock.CreateService.assert_called_once_with(
mock_hs.__enter__(), mock_service_name, mock_display_name,
self._win32service_mock.SERVICE_ALL_ACCESS,
self._win32service_mock.SERVICE_WIN32_OWN_PROCESS,
self._win32service_mock.SERVICE_AUTO_START,
self._win32service_mock.SERVICE_ERROR_NORMAL,
mock_path, None, False, None, None, None)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service_handle')
def test_delete_service(self, mock_get_service_handle):
mock_hs = mock.MagicMock()
fake_service_name = "fake name"
mock_get_service_handle.return_value = mock_hs
with self.snatcher:
self._winutils.delete_service(fake_service_name)
self.assertEqual(["Deleting service fake name"],
self.snatcher.output)
self._win32service_mock.DeleteService.assert_called_once_with(
mock_hs.__enter__())
mock_get_service_handle.assert_called_once_with(
fake_service_name, self._win32service_mock.SERVICE_ALL_ACCESS)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service_handle')
def test_set_service_credentials(self, mock_get_service):
@ -1037,96 +1069,100 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
service_username=".\\username")
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service')
def test_get_service_status(self, mock_get_service):
mock_service = mock.MagicMock()
mock_get_service.return_value = mock_service
'._get_service_handle')
def test_get_service_status(self, mock_get_service_handle):
mock_hs = mock.MagicMock()
fake_service_name = "fake name"
fake_status = {'CurrentState': 'fake-status'}
mock_get_service_handle.return_value = mock_hs
expected_log = ["Getting service status for: %s" % fake_service_name]
self._win32service_mock.QueryServiceStatusEx.return_value = fake_status
with self.snatcher:
response = self._winutils.get_service_status(fake_service_name)
response = self._winutils.get_service_status('fake name')
self.assertEqual(mock_service.State, response)
self._win32service_mock.QueryServiceStatusEx.assert_called_once_with(
mock_hs.__enter__())
mock_get_service_handle.assert_called_once_with(
fake_service_name, self._win32service_mock.SERVICE_QUERY_STATUS)
self.assertEqual(self.snatcher.output, expected_log)
self.assertEqual("Unknown", response)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service')
def test_get_service_start_mode(self, mock_get_service):
mock_service = mock.MagicMock()
mock_get_service.return_value = mock_service
'._get_service_handle')
def test_get_service_start_mode(self, mock_get_service_handle):
mock_hs = mock.MagicMock()
fake_service_name = "fake name"
mock_mode = self._win32service_mock.SERVICE_AUTO_START
fake_status = ['', mock_mode]
mock_get_service_handle.return_value = mock_hs
expected_mode = "Automatic"
expected_log = [
"Getting service start mode for: %s" % fake_service_name]
self._win32service_mock.QueryServiceConfig.return_value = fake_status
response = self._winutils.get_service_start_mode('fake name')
with self.snatcher:
response = self._winutils.get_service_start_mode(fake_service_name)
self.assertEqual(mock_service.StartMode, response)
self._win32service_mock.QueryServiceConfig.assert_called_once_with(
mock_hs.__enter__())
mock_get_service_handle.assert_called_once_with(
fake_service_name, self._win32service_mock.SERVICE_QUERY_CONFIG)
self.assertEqual(self.snatcher.output, expected_log)
self.assertEqual(expected_mode, response)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service')
def _test_set_service_start_mode(self, mock_get_service, ret_val):
mock_service = mock.MagicMock()
mock_get_service.return_value = mock_service
mock_service.ChangeStartMode.return_value = (ret_val,)
if ret_val != 0:
self.assertRaises(exception.CloudbaseInitException,
self._winutils.set_service_start_mode,
'fake name', 'fake mode')
else:
self._winutils.set_service_start_mode('fake name', 'fake mode')
mock_service.ChangeStartMode.assert_called_once_with('fake mode')
def test_set_service_start_mode(self):
self._test_set_service_start_mode(ret_val=0)
def test_set_service_start_mode_exception(self):
self._test_set_service_start_mode(ret_val=1)
'._get_service_handle')
def test_set_service_start_mode(self, mock_get_service_handle):
mock_hs = mock.MagicMock()
fake_service_name = "fake name"
fake_start_mode = "Automatic"
mock_get_service_handle.return_value = mock_hs
with self.snatcher:
self._winutils.set_service_start_mode(fake_service_name,
fake_start_mode)
self.assertEqual(["Setting service start mode for: fake name"],
self.snatcher.output)
self._win32service_mock.ChangeServiceConfig.assert_called_once_with(
mock_hs.__enter__(),
self._win32service_mock.SERVICE_NO_CHANGE,
self._win32service_mock.SERVICE_AUTO_START,
self._win32service_mock.SERVICE_NO_CHANGE,
None, None, False, None, None, None, None)
mock_get_service_handle.assert_called_once_with(
fake_service_name,
self._win32service_mock.SERVICE_CHANGE_CONFIG)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service')
def _test_start_service(self, mock_get_service, ret_val):
mock_service = mock.MagicMock()
mock_get_service.return_value = mock_service
mock_service.StartService.return_value = (ret_val,)
if ret_val != 0:
self.assertRaises(exception.CloudbaseInitException,
self._winutils.start_service,
'fake name')
else:
with self.snatcher:
self._winutils.start_service('fake name')
'._get_service_handle')
def test_start_service(self, mock_get_service_handle):
mock_hs = mock.MagicMock()
fake_service_name = "fake name"
mock_get_service_handle.return_value = mock_hs
with self.snatcher:
self._winutils.start_service(fake_service_name)
self.assertEqual(["Starting service fake name"],
self.snatcher.output)
mock_service.StartService.assert_called_once_with()
def test_start_service(self):
self._test_set_service_start_mode(ret_val=0)
def test_start_service_exception(self):
self._test_set_service_start_mode(ret_val=1)
self._win32service_mock.StartService.assert_called_once_with(
mock_hs.__enter__(), fake_service_name)
mock_get_service_handle.assert_called_once_with(
fake_service_name, self._win32service_mock.SERVICE_START)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'._get_service')
def _test_stop_service(self, mock_get_service, ret_val):
mock_service = mock.MagicMock()
mock_get_service.return_value = mock_service
mock_service.StopService.return_value = (ret_val,)
if ret_val != 0:
self.assertRaises(exception.CloudbaseInitException,
self._winutils.stop_service,
'fake name')
else:
with self.snatcher:
self._winutils.stop_service('fake name')
'._get_service_handle')
def test_stop_service(self, mock_get_service_handle):
mock_hs = mock.MagicMock()
fake_service_name = "fake name"
mock_get_service_handle.return_value = mock_hs
with self.snatcher:
self._winutils.stop_service(fake_service_name)
self.assertEqual(["Stopping service fake name"],
self.snatcher.output)
mock_service.StopService.assert_called_once_with()
def test_stop_service(self):
self._test_stop_service(ret_val=0)
def test_stop_service_exception(self):
self._test_stop_service(ret_val=1)
self._win32service_mock.ControlService.assert_called_once_with(
mock_hs.__enter__(),
self._win32service_mock.SERVICE_CONTROL_STOP)
mock_get_service_handle.assert_called_once_with(
fake_service_name, self._win32service_mock.SERVICE_STOP |
self._win32service_mock.SERVICE_QUERY_STATUS)
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
'.stop_service')