Merge "Adds Windows service CRUD methods"
This commit is contained in:
commit
e40bb953d2
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user