49a012e83c
When using WMI, the resources passed to the Msvm_VirtualSystemManagementService are not cleaned up by the garbage collector if the Service object is still referenced (cached as a property in BaseUtilsVirt). PyMI doesn't have this issue, so the Service objects can be safely cached. This issue only affects Windows / Hyper-V Server 2012. Change-Id: If8df0bf0a2f13a9e94993dde8b2a18943f2e9f83 Closes-Bug: #1697389
161 lines
5.5 KiB
Python
161 lines
5.5 KiB
Python
# Copyright 2016 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.
|
|
|
|
"""
|
|
Base WMI utility class.
|
|
"""
|
|
|
|
import imp
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
if sys.platform == 'win32':
|
|
import wmi
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_utils import reflection
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class BaseUtils(object):
|
|
|
|
_WMI_CONS = {}
|
|
|
|
def _get_wmi_obj(self, moniker, **kwargs):
|
|
return wmi.WMI(moniker=moniker, **kwargs)
|
|
|
|
def _get_wmi_conn(self, moniker, **kwargs):
|
|
if sys.platform != 'win32':
|
|
return None
|
|
if kwargs:
|
|
return self._get_wmi_obj(moniker, **kwargs)
|
|
if moniker in self._WMI_CONS:
|
|
return self._WMI_CONS[moniker]
|
|
|
|
wmi_conn = self._get_wmi_obj(moniker)
|
|
self._WMI_CONS[moniker] = wmi_conn
|
|
return wmi_conn
|
|
|
|
|
|
class BaseUtilsVirt(BaseUtils):
|
|
|
|
_wmi_namespace = '//%s/root/virtualization/v2'
|
|
_os_version = None
|
|
_old_wmi = None
|
|
|
|
def __init__(self, host='.'):
|
|
self._vs_man_svc_attr = None
|
|
self._host = host
|
|
self._conn_attr = None
|
|
self._compat_conn_attr = None
|
|
|
|
@property
|
|
def _conn(self):
|
|
if not self._conn_attr:
|
|
self._conn_attr = self._get_wmi_conn(
|
|
self._wmi_namespace % self._host)
|
|
return self._conn_attr
|
|
|
|
@property
|
|
def _compat_conn(self):
|
|
if not self._compat_conn_attr:
|
|
if not BaseUtilsVirt._os_version:
|
|
# hostutils cannot be used for this, it would end up in
|
|
# a circular import.
|
|
os_version = wmi.WMI().Win32_OperatingSystem()[0].Version
|
|
BaseUtilsVirt._os_version = list(
|
|
map(int, os_version.split('.')))
|
|
|
|
if BaseUtilsVirt._os_version >= [6, 3]:
|
|
self._compat_conn_attr = self._conn
|
|
else:
|
|
self._compat_conn_attr = self._get_wmi_compat_conn(
|
|
moniker=self._wmi_namespace % self._host)
|
|
|
|
return self._compat_conn_attr
|
|
|
|
@property
|
|
def _vs_man_svc(self):
|
|
if self._vs_man_svc_attr:
|
|
return self._vs_man_svc_attr
|
|
|
|
vs_man_svc = self._compat_conn.Msvm_VirtualSystemManagementService()[0]
|
|
if BaseUtilsVirt._os_version >= [6, 3]:
|
|
# NOTE(claudiub): caching this property on Windows / Hyper-V Server
|
|
# 2012 (using the old WMI) can lead to memory leaks. PyMI doesn't
|
|
# have those issues, so we can safely cache it.
|
|
self._vs_man_svc_attr = vs_man_svc
|
|
return vs_man_svc
|
|
|
|
def _get_wmi_compat_conn(self, moniker, **kwargs):
|
|
# old WMI should be used on Windows / Hyper-V Server 2012 whenever
|
|
# .GetText_ is used (e.g.: AddResourceSettings). PyMI's and WMI's
|
|
# .GetText_ have different results.
|
|
if not BaseUtilsVirt._old_wmi:
|
|
old_wmi_path = "%s.py" % wmi.__path__[0]
|
|
BaseUtilsVirt._old_wmi = imp.load_source('old_wmi', old_wmi_path)
|
|
return BaseUtilsVirt._old_wmi.WMI(moniker=moniker, **kwargs)
|
|
|
|
def _get_wmi_obj(self, moniker, compatibility_mode=False, **kwargs):
|
|
if not BaseUtilsVirt._os_version:
|
|
# hostutils cannot be used for this, it would end up in
|
|
# a circular import.
|
|
os_version = wmi.WMI().Win32_OperatingSystem()[0].Version
|
|
BaseUtilsVirt._os_version = list(map(int, os_version.split('.')))
|
|
|
|
if not compatibility_mode or BaseUtilsVirt._os_version >= [6, 3]:
|
|
return wmi.WMI(moniker=moniker, **kwargs)
|
|
return self._get_wmi_compat_conn(moniker=moniker, **kwargs)
|
|
|
|
|
|
class SynchronizedMeta(type):
|
|
"""Use an rlock to synchronize all class methods."""
|
|
|
|
def __init__(cls, cls_name, bases, attrs):
|
|
super(SynchronizedMeta, cls).__init__(cls_name, bases, attrs)
|
|
rlock = threading.RLock()
|
|
|
|
for attr_name in attrs:
|
|
attr = getattr(cls, attr_name)
|
|
if callable(attr):
|
|
decorated = SynchronizedMeta._synchronize(
|
|
attr, cls_name, rlock)
|
|
setattr(cls, attr_name, decorated)
|
|
|
|
@staticmethod
|
|
def _synchronize(func, cls_name, rlock):
|
|
def wrapper(*args, **kwargs):
|
|
f_qual_name = reflection.get_callable_name(func)
|
|
|
|
t_request = time.time()
|
|
try:
|
|
with rlock:
|
|
t_acquire = time.time()
|
|
LOG.debug("Method %(method_name)s acquired rlock. "
|
|
"Waited %(time_wait)0.3fs",
|
|
dict(method_name=f_qual_name,
|
|
time_wait=t_acquire - t_request))
|
|
return func(*args, **kwargs)
|
|
finally:
|
|
t_release = time.time()
|
|
LOG.debug("Method %(method_name)s released rlock. "
|
|
"Held %(time_held)0.3fs",
|
|
dict(method_name=f_qual_name,
|
|
time_held=t_release - t_acquire))
|
|
return wrapper
|