neutron/neutron/agent/windows/utils.py

177 lines
5.5 KiB
Python

# Copyright 2015 Cloudbase Solutions.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import builtins
import ctypes
import io
import os
import eventlet
from eventlet import tpool
from neutron_lib import exceptions
from neutron_lib.utils import helpers
from oslo_log import log as logging
from oslo_utils import encodeutils
from neutron._i18n import _
if os.name == 'nt':
import wmi
LOG = logging.getLogger(__name__)
# subprocess.Popen will spawn two threads consuming stdout/stderr when passing
# data through stdin. We need to make sure that *native* threads will be used
# as pipes are blocking on Windows.
subprocess = eventlet.patcher.original('subprocess')
subprocess.threading = eventlet.patcher.original('threading')
ERROR_KEY_DELETED = 0x03FA
def create_process(cmd, run_as_root=False, addl_env=None,
tpool_proxy=True):
cmd = list(map(str, cmd))
LOG.debug("Running command: %s", cmd)
env = os.environ.copy()
if addl_env:
env.update(addl_env)
popen = subprocess.Popen
obj = popen(cmd, shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
preexec_fn=None,
close_fds=False)
if tpool_proxy and eventlet.getcurrent().parent:
# If we intend to access the process streams, we need to wrap this
# in a tpool proxy object, avoding blocking other greenthreads.
#
# The 'file' type is not available on Python 3.x.
file_type = getattr(builtins, 'file', io.IOBase)
obj = tpool.Proxy(obj, autowrap=(file_type, ))
return obj, cmd
def _get_wmi_process(pid):
if not pid:
return None
try:
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:
return processes[0]
return None
def kill_process(pid, signal, run_as_root=False):
"""Kill the process with the given pid using the given signal."""
process = _get_wmi_process(pid)
try:
if process:
process.Terminate()
except Exception:
if _get_wmi_process(pid):
raise
def execute(cmd, process_input=None, addl_env=None,
check_exit_code=True, return_stderr=False, log_fail_as_error=True,
extra_ok_codes=None, run_as_root=False, do_decode=True):
if process_input is not None:
_process_input = encodeutils.to_utf8(process_input)
else:
_process_input = None
obj, cmd = create_process(cmd, addl_env=addl_env, tpool_proxy=False)
_stdout, _stderr = avoid_blocking_call(obj.communicate, _process_input)
obj.stdin.close()
_stdout = helpers.safe_decode_utf8(_stdout)
_stderr = helpers.safe_decode_utf8(_stderr)
m = _("\nCommand: %(cmd)s\nExit code: %(code)s\nStdin: %(stdin)s\n"
"Stdout: %(stdout)s\nStderr: %(stderr)s") % \
{'cmd': cmd,
'code': obj.returncode,
'stdin': process_input or '',
'stdout': _stdout,
'stderr': _stderr}
extra_ok_codes = extra_ok_codes or []
if obj.returncode and obj.returncode in extra_ok_codes:
obj.returncode = None
log_msg = m.strip().replace('\n', '; ')
if obj.returncode and log_fail_as_error:
LOG.error(log_msg)
else:
LOG.debug(log_msg)
if obj.returncode and check_exit_code:
raise exceptions.ProcessExecutionError(m, returncode=obj.returncode)
return (_stdout, _stderr) if return_stderr else _stdout
def avoid_blocking_call(f, *args, **kwargs):
"""Ensure that the method "f" will not block other greenthreads.
Performs the call to the function "f" received as parameter in a
different thread using tpool.execute when called from a greenthread.
This will ensure that the function "f" will not block other greenthreads.
If not called from a greenthread, it will invoke the function "f" directly.
The function "f" will receive as parameters the arguments "args" and
keyword arguments "kwargs".
"""
# Note that eventlet.getcurrent will always return a greenlet object.
# In case of a greenthread, the parent greenlet will always be the hub
# loop greenlet.
if eventlet.getcurrent().parent:
return tpool.execute(f, *args, **kwargs)
else:
return f(*args, **kwargs)
def get_root_helper_child_pid(pid, expected_cmd, run_as_root=False):
# We don't use a root helper on Windows.
return str(pid)
def process_is_running(pid):
"""Find if the given PID is running in the system."""
return _get_wmi_process(pid) is not None
def pid_invoked_with_cmdline(pid, expected_cmd):
process = _get_wmi_process(pid)
if not process:
return False
command = process.CommandLine
return command == " ".join(expected_cmd)