summaryrefslogtreecommitdiff
path: root/daemons/lvmdbusd/request.py
blob: 69274b20fa42f22711b0cc7cd4cb2379880e728b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 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