mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
167 lines
4.8 KiB
Python
167 lines
4.8 KiB
Python
# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved.
|
|
#
|
|
# This copyrighted material is made available to anyone wishing to use,
|
|
# modify, copy, or redistribute it subject to the terms and conditions
|
|
# of the GNU General Public License v.2.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import dbus
|
|
import threading
|
|
# noinspection PyUnresolvedReferences
|
|
from gi.repository import GLib
|
|
from .job import Job
|
|
from . import cfg
|
|
from .utils import log_error, mt_async_call, extract_stack_trace
|
|
|
|
|
|
class RequestEntry(object):
|
|
def __init__(self, tmo, method, arguments, cb, cb_error,
|
|
return_tuple=True, job_state=None):
|
|
self.method = method
|
|
self.arguments = arguments
|
|
self.cb = cb
|
|
self.cb_error = cb_error
|
|
|
|
self.timer_id = -1
|
|
self.lock = threading.RLock()
|
|
self.done = False
|
|
self._result = None
|
|
self._job = None
|
|
self._rc = 0
|
|
self._rc_error = None
|
|
self._return_tuple = return_tuple
|
|
self._job_state = job_state
|
|
|
|
if tmo < 0:
|
|
# Client is willing to block forever
|
|
pass
|
|
elif tmo == 0:
|
|
self._return_job()
|
|
else:
|
|
# Note: using 990 instead of 1000 for second to ms conversion to
|
|
# account for overhead. Goal is to return just before the
|
|
# timeout amount has expired. Better to be a little early than
|
|
# late.
|
|
self.timer_id = GLib.timeout_add(
|
|
tmo * 990, RequestEntry._request_timeout, self)
|
|
|
|
@staticmethod
|
|
def _request_timeout(r):
|
|
"""
|
|
Method which gets called when the timer runs out!
|
|
:param r: RequestEntry which timed out
|
|
:return: Result of timer_expired
|
|
"""
|
|
return r.timer_expired()
|
|
|
|
def _return_job(self):
|
|
# Return job is only called when we create a request object or when
|
|
# we pop a timer. In both cases we are running in the correct context
|
|
# and do not need to schedule the call back in main context.
|
|
self._job = Job(self, self._job_state)
|
|
cfg.om.register_object(self._job, True)
|
|
if self._return_tuple:
|
|
self.cb(('/', self._job.dbus_object_path()))
|
|
else:
|
|
self.cb(self._job.dbus_object_path())
|
|
|
|
def run_cmd(self):
|
|
try:
|
|
result = self.method(*self.arguments)
|
|
self.register_result(result)
|
|
except SystemExit as se:
|
|
self.register_error(-1, str(se), se)
|
|
raise se
|
|
except dbus.exceptions.DBusException as dbe:
|
|
# This is an expected error path when something goes awry that
|
|
# we handled
|
|
self.register_error(-1, str(dbe), dbe)
|
|
except Exception as e:
|
|
# Use the request entry to return the result as the client may
|
|
# have gotten a job by the time we hit an error
|
|
# Lets set the exception text as the error message and log the
|
|
# exception in the journal for figuring out what went wrong.
|
|
cfg.debug.dump()
|
|
cfg.flightrecorder.dump()
|
|
tb = extract_stack_trace(e)
|
|
log_error("While processing %s: we encountered\n%s" % (str(self.method), tb))
|
|
log_error("Error returned to client: %s" % str(e))
|
|
self.register_error(-1, str(e), e)
|
|
|
|
def is_done(self):
|
|
with self.lock:
|
|
rc = self.done
|
|
return rc
|
|
|
|
def get_errors(self):
|
|
with self.lock:
|
|
return (self._rc, self._rc_error)
|
|
|
|
def result(self):
|
|
with self.lock:
|
|
if self.done:
|
|
return self._result
|
|
return '/'
|
|
|
|
def _reg_ending(self, result, error_rc=0, error_msg=None,
|
|
error_exception=None):
|
|
with self.lock:
|
|
self.done = True
|
|
if self.timer_id != -1:
|
|
# Try to prevent the timer from firing
|
|
GLib.source_remove(self.timer_id)
|
|
|
|
self._result = result
|
|
self._rc = error_rc
|
|
self._rc_error = error_msg
|
|
|
|
if not self._job:
|
|
# We finished and there is no job, so return result or error
|
|
# now!
|
|
# Note: If we don't have a valid cb or cbe, this indicates a
|
|
# request that doesn't need a response as we already returned
|
|
# one before the request was processed.
|
|
if error_rc == 0:
|
|
if self.cb:
|
|
if self._return_tuple:
|
|
mt_async_call(self.cb, (result, '/'))
|
|
else:
|
|
mt_async_call(self.cb, result)
|
|
else:
|
|
if self.cb_error:
|
|
if not error_exception:
|
|
if not error_msg:
|
|
error_exception = Exception(
|
|
"An error occurred, but no reason was "
|
|
"given, see service logs!")
|
|
else:
|
|
error_exception = Exception(error_msg)
|
|
|
|
mt_async_call(self.cb_error, error_exception)
|
|
else:
|
|
# We have a job, and it's complete, indicate that it's done.
|
|
self._job.Complete = True
|
|
self._job = None
|
|
|
|
def register_error(self, error_rc, error_message, error_exception):
|
|
self._reg_ending('/', error_rc, error_message, error_exception)
|
|
|
|
def register_result(self, result):
|
|
self._reg_ending(result)
|
|
|
|
def timer_expired(self):
|
|
with self.lock:
|
|
# Set the timer back to -1 as we will get a warning if we try
|
|
# to remove a timer that doesn't exist
|
|
self.timer_id = -1
|
|
if not self.done:
|
|
# Create dbus job object and return path to caller
|
|
self._return_job()
|
|
else:
|
|
# The job is done, we have nothing to do
|
|
pass
|
|
|
|
return False
|