windows: fix terminating processes

The neutron Windows exec call helper doesn't properly handle the
situation in which the process that it's trying to kill was already
terminated, in which case using wmi to fetch the process can raise
an exception.

This change ensures that those WMI exceptions will be properly
handled.

Closes-Bug: 1872663

Change-Id: I00c810fe541ac5e1e9923155fe90eb07a0b4b3dd
This commit is contained in:
Lucian Petrut 2020-04-14 11:25:18 +03:00
parent b5e96c49bf
commit 8fd3e884c7
2 changed files with 39 additions and 2 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ctypes
import io import io
import os import os
@ -38,6 +39,8 @@ LOG = logging.getLogger(__name__)
subprocess = eventlet.patcher.original('subprocess') subprocess = eventlet.patcher.original('subprocess')
subprocess.threading = eventlet.patcher.original('threading') subprocess.threading = eventlet.patcher.original('threading')
ERROR_KEY_DELETED = 0x03FA
def create_process(cmd, run_as_root=False, addl_env=None, def create_process(cmd, run_as_root=False, addl_env=None,
tpool_proxy=True): tpool_proxy=True):
@ -71,8 +74,16 @@ def _get_wmi_process(pid):
if not pid: if not pid:
return None return None
conn = wmi.WMI() try:
processes = conn.Win32_Process(ProcessId=pid) conn = wmi.WMI()
processes = conn.Win32_Process(ProcessId=pid)
except wmi.x_wmi as exc:
hresult = exc.com_error.hresult
err_code = ctypes.c_uint(hresult).value & 0xFFFF
if err_code == ERROR_KEY_DELETED:
return None
raise
if processes: if processes:
return processes[0] return processes[0]
return None return None

View File

@ -26,6 +26,13 @@ from neutron.agent.windows import utils
from neutron.tests import base from neutron.tests import base
class x_wmi(Exception):
def __init__(self, info='', com_error=None):
super(x_wmi, self).__init__(info)
self.info = info
self.com_error = com_error
@ddt.ddt @ddt.ddt
class WindowsUtilsTestCase(base.BaseTestCase): class WindowsUtilsTestCase(base.BaseTestCase):
@mock.patch('os.environ', {mock.sentinel.key0: mock.sentinel.val0}) @mock.patch('os.environ', {mock.sentinel.key0: mock.sentinel.val0})
@ -87,6 +94,25 @@ class WindowsUtilsTestCase(base.BaseTestCase):
if pid: if pid:
mock_conn.Win32_Process.assert_called_once_with(ProcessId=pid) mock_conn.Win32_Process.assert_called_once_with(ProcessId=pid)
@ddt.data({},
{"hresult": 0xff,
"expect_exc": True})
@ddt.unpack
@mock.patch.object(utils, 'wmi', create=True)
def test_get_wmi_process_exc(self, mock_wmi, expect_exc=False,
hresult=0x800703FA):
mock_conn = mock_wmi.WMI.return_value
mock_wmi.x_wmi = x_wmi
com_error = mock.Mock(hresult=hresult)
exc = x_wmi(com_error=com_error)
mock_conn.Win32_Process.side_effect = exc
if expect_exc:
self.assertRaises(
x_wmi, utils._get_wmi_process, mock.sentinel.pid)
else:
self.assertIsNone(utils._get_wmi_process(mock.sentinel.pid))
@ddt.data(True, False) @ddt.data(True, False)
@mock.patch.object(utils, '_get_wmi_process') @mock.patch.object(utils, '_get_wmi_process')
def test_kill_process(self, process_exists, mock_get_process): def test_kill_process(self, process_exists, mock_get_process):