282 lines
10 KiB
Python
282 lines
10 KiB
Python
# Copyright 2014 Cloudbase Solutions Srl
|
|
# 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 ctypes
|
|
import six
|
|
import struct
|
|
import sys
|
|
|
|
from eventlet import patcher
|
|
from nova.i18n import _
|
|
from oslo_log import log as logging
|
|
from oslo_utils import units
|
|
|
|
from hyperv.nova import constants
|
|
from hyperv.nova import vmutils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
# Avoid using six.moves.queue as we need a non monkey patched class
|
|
if sys.version_info > (3, 0):
|
|
Queue = patcher.original('queue')
|
|
else:
|
|
Queue = patcher.original('Queue')
|
|
|
|
if sys.platform == 'win32':
|
|
from ctypes import wintypes
|
|
|
|
kernel32 = ctypes.windll.kernel32
|
|
|
|
class OVERLAPPED(ctypes.Structure):
|
|
_fields_ = [
|
|
('Internal', wintypes.ULONG),
|
|
('InternalHigh', wintypes.ULONG),
|
|
('Offset', wintypes.DWORD),
|
|
('OffsetHigh', wintypes.DWORD),
|
|
('hEvent', wintypes.HANDLE)
|
|
]
|
|
|
|
def __init__(self):
|
|
self.Offset = 0
|
|
self.OffsetHigh = 0
|
|
|
|
LPOVERLAPPED = ctypes.POINTER(OVERLAPPED)
|
|
LPOVERLAPPED_COMPLETION_ROUTINE = ctypes.WINFUNCTYPE(
|
|
None, wintypes.DWORD, wintypes.DWORD, LPOVERLAPPED)
|
|
|
|
kernel32.ReadFileEx.argtypes = [
|
|
wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD,
|
|
LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE]
|
|
kernel32.WriteFileEx.argtypes = [
|
|
wintypes.HANDLE, wintypes.LPCVOID, wintypes.DWORD,
|
|
LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE]
|
|
|
|
|
|
FILE_FLAG_OVERLAPPED = 0x40000000
|
|
FILE_SHARE_READ = 1
|
|
FILE_SHARE_WRITE = 2
|
|
GENERIC_READ = 0x80000000
|
|
GENERIC_WRITE = 0x40000000
|
|
OPEN_EXISTING = 3
|
|
|
|
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100
|
|
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
|
|
|
|
INVALID_HANDLE_VALUE = -1
|
|
WAIT_FAILED = 0xFFFFFFFF
|
|
WAIT_FINISHED = 0
|
|
ERROR_PIPE_BUSY = 231
|
|
ERROR_PIPE_NOT_CONNECTED = 233
|
|
ERROR_NOT_FOUND = 1168
|
|
|
|
WAIT_PIPE_DEFAULT_TIMEOUT = 5 # seconds
|
|
WAIT_IO_COMPLETION_TIMEOUT = 2 * units.k
|
|
WAIT_INFINITE_TIMEOUT = 0xFFFFFFFF
|
|
|
|
IO_QUEUE_TIMEOUT = 2
|
|
IO_QUEUE_BURST_TIMEOUT = 0.05
|
|
|
|
|
|
class HyperVIOError(vmutils.HyperVException):
|
|
msg_fmt = _("IO operation failed while executing "
|
|
"Win32 API function %(func_name)s. "
|
|
"Error code: %(error_code)s. "
|
|
"Error message %(error_message)s.")
|
|
|
|
def __init__(self, error_code=None, error_message=None,
|
|
func_name=None):
|
|
self.error_code = error_code
|
|
message = self.msg_fmt % {'func_name': func_name,
|
|
'error_code': error_code,
|
|
'error_message': error_message}
|
|
super(HyperVIOError, self).__init__(message)
|
|
|
|
|
|
class IOUtils(object):
|
|
"""Asyncronous IO helper class."""
|
|
|
|
def _run_and_check_output(self, func, *args, **kwargs):
|
|
"""Convenience helper method for running Win32 API methods."""
|
|
# A list of return values signaling that the operation failed.
|
|
error_codes = kwargs.pop('error_codes', [0])
|
|
ignored_error_codes = kwargs.pop('ignored_error_codes', None)
|
|
|
|
ret_val = func(*args, **kwargs)
|
|
|
|
if ret_val in error_codes:
|
|
func_name = func.__name__
|
|
self.handle_last_error(func_name=func_name,
|
|
ignored_error_codes=ignored_error_codes)
|
|
return ret_val
|
|
|
|
def handle_last_error(self, func_name=None, ignored_error_codes=None):
|
|
error_code = kernel32.GetLastError()
|
|
kernel32.SetLastError(0)
|
|
|
|
if ignored_error_codes and error_code in ignored_error_codes:
|
|
return
|
|
|
|
message_buffer = ctypes.c_char_p()
|
|
kernel32.FormatMessageA(
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
None, error_code, 0, ctypes.byref(message_buffer), 0, None)
|
|
|
|
error_message = message_buffer.value
|
|
kernel32.LocalFree(message_buffer)
|
|
|
|
raise HyperVIOError(error_code=error_code,
|
|
error_message=error_message,
|
|
func_name=func_name)
|
|
|
|
def wait_named_pipe(self, pipe_name, timeout=WAIT_PIPE_DEFAULT_TIMEOUT):
|
|
"""Wait a given ammount of time for a pipe to become available."""
|
|
self._run_and_check_output(kernel32.WaitNamedPipeW,
|
|
ctypes.c_wchar_p(pipe_name),
|
|
timeout * units.k)
|
|
|
|
def open(self, path, desired_access=None, share_mode=None,
|
|
creation_disposition=None, flags_and_attributes=None):
|
|
error_codes = [INVALID_HANDLE_VALUE]
|
|
handle = self._run_and_check_output(kernel32.CreateFileW,
|
|
ctypes.c_wchar_p(path),
|
|
desired_access,
|
|
share_mode,
|
|
None,
|
|
creation_disposition,
|
|
flags_and_attributes,
|
|
None,
|
|
error_codes=error_codes)
|
|
return handle
|
|
|
|
def cancel_io(self, handle, overlapped_structure=None):
|
|
"""Cancels pending IO on specified handle."""
|
|
# Ignore errors thrown when there are no requests
|
|
# to be canceled.
|
|
ignored_error_codes = [ERROR_NOT_FOUND]
|
|
self._run_and_check_output(kernel32.CancelIoEx,
|
|
handle,
|
|
overlapped_structure,
|
|
ignored_error_codes=ignored_error_codes)
|
|
|
|
def close_handle(self, handle):
|
|
self._run_and_check_output(kernel32.CloseHandle, handle)
|
|
|
|
def _wait_io_completion(self, event):
|
|
self._run_and_check_output(kernel32.WaitForSingleObjectEx,
|
|
event, WAIT_INFINITE_TIMEOUT,
|
|
True, error_codes=[WAIT_FAILED])
|
|
|
|
def set_event(self, event):
|
|
self._run_and_check_output(kernel32.SetEvent, event)
|
|
|
|
def _reset_event(self, event):
|
|
self._run_and_check_output(kernel32.ResetEvent, event)
|
|
|
|
def _create_event(self, event_attributes=None, manual_reset=True,
|
|
initial_state=False, name=None):
|
|
return self._run_and_check_output(kernel32.CreateEventW,
|
|
event_attributes, manual_reset,
|
|
initial_state, name,
|
|
error_codes=[None])
|
|
|
|
def get_completion_routine(self, callback=None):
|
|
def _completion_routine(error_code, num_bytes, lpOverLapped):
|
|
"""Sets the completion event and executes callback, if passed."""
|
|
overlapped = ctypes.cast(lpOverLapped, LPOVERLAPPED).contents
|
|
self.set_event(overlapped.hEvent)
|
|
|
|
if callback:
|
|
callback(num_bytes)
|
|
|
|
return LPOVERLAPPED_COMPLETION_ROUTINE(_completion_routine)
|
|
|
|
def get_new_overlapped_structure(self):
|
|
"""Structure used for asyncronous IO operations."""
|
|
# Event used for signaling IO completion
|
|
hEvent = self._create_event()
|
|
|
|
overlapped_structure = OVERLAPPED()
|
|
overlapped_structure.hEvent = hEvent
|
|
return overlapped_structure
|
|
|
|
def read(self, handle, buff, num_bytes,
|
|
overlapped_structure, completion_routine):
|
|
self._reset_event(overlapped_structure.hEvent)
|
|
self._run_and_check_output(kernel32.ReadFileEx,
|
|
handle, buff, num_bytes,
|
|
ctypes.byref(overlapped_structure),
|
|
completion_routine)
|
|
self._wait_io_completion(overlapped_structure.hEvent)
|
|
|
|
def write(self, handle, buff, num_bytes,
|
|
overlapped_structure, completion_routine):
|
|
self._reset_event(overlapped_structure.hEvent)
|
|
self._run_and_check_output(kernel32.WriteFileEx,
|
|
handle, buff, num_bytes,
|
|
ctypes.byref(overlapped_structure),
|
|
completion_routine)
|
|
self._wait_io_completion(overlapped_structure.hEvent)
|
|
|
|
def get_buffer(self, buff_size):
|
|
return (ctypes.c_ubyte * buff_size)()
|
|
|
|
def get_buffer_data(self, buff, num_bytes):
|
|
return bytes(bytearray(buff[:num_bytes]))
|
|
|
|
def write_buffer_data(self, buff, data):
|
|
for i, c in enumerate(data):
|
|
buff[i] = struct.unpack('B', six.b(c))[0]
|
|
|
|
|
|
class IOQueue(Queue.Queue):
|
|
def __init__(self, client_connected):
|
|
Queue.Queue.__init__(self)
|
|
self._client_connected = client_connected
|
|
|
|
def get(self, timeout=IO_QUEUE_TIMEOUT, continue_on_timeout=True):
|
|
while self._client_connected.isSet():
|
|
try:
|
|
return Queue.Queue.get(self, timeout=timeout)
|
|
except Queue.Empty:
|
|
if continue_on_timeout:
|
|
continue
|
|
else:
|
|
break
|
|
|
|
def put(self, item, timeout=IO_QUEUE_TIMEOUT):
|
|
while self._client_connected.isSet():
|
|
try:
|
|
return Queue.Queue.put(self, item, timeout=timeout)
|
|
except Queue.Full:
|
|
continue
|
|
|
|
def get_burst(self, timeout=IO_QUEUE_TIMEOUT,
|
|
burst_timeout=IO_QUEUE_BURST_TIMEOUT,
|
|
max_size=constants.SERIAL_CONSOLE_BUFFER_SIZE):
|
|
# Get as much data as possible from the queue
|
|
# to avoid sending small chunks.
|
|
data = self.get(timeout=timeout)
|
|
|
|
while data and not (len(data) > max_size):
|
|
chunk = self.get(timeout=burst_timeout,
|
|
continue_on_timeout=False)
|
|
if chunk:
|
|
data += chunk
|
|
else:
|
|
break
|
|
return data
|