View | Details | Raw Unified | Return to bug 30258
Collapse All | Expand All

(-)src/univention/management/console/modules/decorators.py (-44 / +83 lines)
 Lines 53-58    Link Here 
53
"""
53
"""
54
54
55
import inspect
55
import inspect
56
from threading import Thread
56
57
57
from univention.lib.i18n import Translation
58
from univention.lib.i18n import Translation
58
_ = Translation( 'univention.management.console' ).translate
59
_ = Translation( 'univention.management.console' ).translate
 Lines 258-269    Link Here 
258
	if function is None:
259
	if function is None:
259
		return lambda f: simple_response(f, with_flavor)
260
		return lambda f: simple_response(f, with_flavor)
260
261
262
	# does it
263
	#   return value ?
264
	#   => just send the value to the frontend
265
	# or
266
	#   yield value ?
267
	#   => send the value to the frontend and
268
	#      finish the function in a thread
269
	function_yields = inspect.isgeneratorfunction(function)
270
261
	# fake a generator function that yields whatever the original
271
	# fake a generator function that yields whatever the original
262
	# function returned
272
	# function returned
263
	def _fake_func(self, iterator, *args):
273
	def _fake_func(self, iterator, *args):
264
		for args in iterator:
274
		for args in iterator:
265
			break
275
			break
266
		yield function(self, *args)
276
		if function_yields:
277
			for res in function(self, *args):
278
				yield res
279
		else:
280
			res = function(self, *args)
281
			yield res
267
	copy_function_meta_data(function, _fake_func, copy_arg_inspect=True)
282
	copy_function_meta_data(function, _fake_func, copy_arg_inspect=True)
268
	# fake another variable name
283
	# fake another variable name
269
	# the name is not important as it is removed from the list while
284
	# the name is not important as it is removed from the list while
 Lines 279-286    Link Here 
279
294
280
		# fake a multi_request
295
		# fake a multi_request
281
		request.options = [request.options]
296
		request.options = [request.options]
282
		result = _multi_response(self, request)
297
		gen = _multi_response(self, request)
283
		self.finished(request.id, result[0])
298
		result = gen.next()
299
		self.finished(request.id, result)
300
		if function_yields:
301
			def _run_to_end(local_gen):
302
				for result in local_gen:
303
					pass
304
			thread = Thread(target=_run_to_end, args=[gen])
305
			thread.start()
284
306
285
	copy_function_meta_data(function, _response)
307
	copy_function_meta_data(function, _response)
286
	return _response
308
	return _response
 Lines 329-335    Link Here 
329
		return lambda f: multi_response(f, with_flavor, single_values)
351
		return lambda f: multi_response(f, with_flavor, single_values)
330
	response_func = _eval_simple_decorated_function(function, with_flavor, single_values)
352
	response_func = _eval_simple_decorated_function(function, with_flavor, single_values)
331
	def _response(self, request):
353
	def _response(self, request):
332
		result = response_func(self, request)
354
		result = list(response_func(self, request))
333
		self.finished(request.id, result)
355
		self.finished(request.id, result)
334
	copy_function_meta_data(function, _response)
356
	copy_function_meta_data(function, _response)
335
	return _response
357
	return _response
 Lines 375-390    Link Here 
375
							# check for required arguments (those without default)
397
							# check for required arguments (those without default)
376
							raise UMC_OptionMissing(arg)
398
							raise UMC_OptionMissing(arg)
377
399
378
		
379
		# checked for required arguments, set default... now run!
400
		# checked for required arguments, set default... now run!
380
		result = []
381
382
		iterator = RequestOptionsIterator(request.options, arguments, single_values)
401
		iterator = RequestOptionsIterator(request.options, arguments, single_values)
383
		nones = [None] * len(arguments)
402
		nones = [None] * len(arguments)
384
		for res in function(self, iterator, *nones):
403
		return function(self, iterator, *nones)
385
			result.append(res)
386
387
		return result
388
	return _response
404
	return _response
389
405
390
class RequestOptionsIterator(object):
406
class RequestOptionsIterator(object):
 Lines 439-469    Link Here 
439
	# copy __module__, otherwise it would be "univention.management.console.modules.decorators"
455
	# copy __module__, otherwise it would be "univention.management.console.modules.decorators"
440
	new_function.__module__ = original_function.__module__
456
	new_function.__module__ = original_function.__module__
441
457
442
def log(function=None, sensitives=None, customs=None, single_values=False):
458
def log(function=None, sensitives=None, customs=None, single_values=False, logger=None, simple_threaded=False):
443
	'''Log decorator to be used with
459
	'''Log decorator to be used with :func:`simple_response`.
444
	:func:`simple_response`::
445
460
461
	You can specify any function to handle the log information. By default
462
	it is handled by MODULE.info, but you can pass MODULE.process (already
463
	logs when *umc/module/debug/level* is set to 2) as well as any other
464
	function that can handle strings::
465
446
	 @simple_response
466
	 @simple_response
447
	 @log
467
	 @log
448
	 def my_func(self, var1, var2):
468
	 def my_func(self, var1, var2):
449
	     return "%s__%s" % (var1, var2)
469
	     return "%s__%s" % (var1, var2)
450
470
451
	The above example will write two lines into the logfile for the
471
	The above example will write two lines into the logfile for the module
452
	module (given that the the UCR variable *umc/module/debug/level*
472
	(given that the the UCR variable *umc/module/debug/level* is set to at
453
	is set to at least 3)::
473
	least 3)::
454
474
455
	 <date>  MODULE      ( INFO    ) : my_func got: var1='value1', var2='value2'
475
	 <date>  MODULE      ( INFO    ) : my_func got: var1='value1', var2='value2'
456
	 <date>  MODULE      ( INFO    ) : my_func returned: 'value1__value2'
476
	 <date>  MODULE      ( INFO    ) : my_func returned: 'value1__value2'
457
477
458
	The variable names are ordered by appearance and hold the values that
478
	The variable names are ordered by appearance and hold the values that
459
	are actually going to be passed to the function (i.e. after they were
479
	are actually going to be passed to the function (i.e. after they were
460
	:func:`sanitize` 'd or set to their default value).
480
	:func:`sanitize` 'd or set to their default value). You may specify the
461
	You may specify the names of sensitive arguments that should not
481
	names of sensitive arguments that should not show up in log files and
462
	show up in log files and custom functions that can alter the
482
	custom functions that can alter the representation of a certain
463
	representation of a certain variable's values (useful for non-standard
483
	variable's values (useful for non-standard datatypes like regular
464
	datatypes like regular expressions - you may have used a
484
	expressions - you may have used a
465
	:class:`~univention.management.console.modules.sanitizers.PatternSanitizer`
485
	:class:`~univention.management.console.modules.sanitizers.PatternSanitizer`)::
466
	)::
467
486
468
	 @sanitize(pattern=PatternSanitizer())
487
	 @sanitize(pattern=PatternSanitizer())
469
	 @simple_reponse
488
	 @simple_reponse
 Lines 479-501    Link Here 
479
	The decorator also works with :func:`multi_response`::
498
	The decorator also works with :func:`multi_response`::
480
499
481
	 @multi_response
500
	 @multi_response
482
	 @log
501
	 @log(logger=MODULE.process) # logs to PROCESS, not INFO
483
	 def multi_my_func(self, var1, var2):
502
	 def multi_my_func(self, var1, var2):
484
	     return "%s__%s" % (var1, var2)
503
	     return "%s__%s" % (var1, var2)
485
504
486
	This results in something like::
505
	This results in something like::
487
506
488
	 <date>  MODULE      ( INFO    ) : multi_my_func got: [var1='value1', var2='value2'], [var1='value3', var2='value4']
507
	 <date>  MODULE      ( PROCESS ) : multi_my_func got: [var1='value1', var2='value2'], [var1='value3', var2='value4']
489
	 <date>  MODULE      ( INFO    ) : multi_my_func returned: ['value1__value2', 'value3__value4']
508
	 <date>  MODULE      ( PROCESS ) : multi_my_func returned: ['value1__value2', 'value3__value4']
490
	'''
509
	'''
491
	if function is None:
510
	if function is None:
492
		return lambda f: log(f, sensitives, customs, single_values)
511
		return lambda f: log(f, sensitives, customs, single_values, logger, simple_threaded)
493
	if customs is None:
512
	if customs is None:
494
		customs = {}
513
		customs = {}
495
	if sensitives is None:
514
	if sensitives is None:
496
		sensitives = []
515
		sensitives = []
497
	for sensitive in sensitives:
516
	for sensitive in sensitives:
498
		customs[sensitive] = lambda x: '********'
517
		customs[sensitive] = lambda x: '********'
518
	if logger is None:
519
		logger = MODULE.info
499
520
500
	def _log(names, args):
521
	def _log(names, args):
501
		if single_values:
522
		if single_values:
 Lines 507-537    Link Here 
507
	name = function.__name__
528
	name = function.__name__
508
	# multi_response yields i.e. is generator function
529
	# multi_response yields i.e. is generator function
509
	if inspect.isgeneratorfunction(function):
530
	if inspect.isgeneratorfunction(function):
510
		# remove self, iterator
531
		# problem: simple_response can also yield
511
		names = names[2:]
532
		if simple_threaded:
512
		def _response(self, iterator, *args):
533
			# remove self
513
			arg_reprs = []
534
			names = names[1:]
514
			for element in iterator:
535
			def _response_simple_threaded(self, *args):
515
				arg_repr = _log(names, element)
536
				arg_repr = _log(names, args)
516
				if arg_repr:
537
				if arg_repr:
517
					arg_reprs.append(arg_repr)
538
					logger('%s got: %s' % (name, ', '.join(arg_repr)))
518
			if arg_reprs:
539
				result = function(self, *args)
519
				MODULE.info('%s got: [%s]' % (name, '], ['.join(', '.join(arg_repr) for arg_repr in arg_reprs)))
540
				res = result.next()
520
			result = []
541
				logger('%s returned: %r' % (name, res))
521
			for res in function(self, iterator, *args):
522
				result.append(res)
523
				yield res
542
				yield res
524
			MODULE.info('%s returned: %r' % (name, result))
543
				for res in result:
544
					yield res
545
			_response = _response_simple_threaded
546
		else:
547
			# remove self, iterator
548
			names = names[2:]
549
			def _response_multiple(self, iterator, *args):
550
				arg_reprs = []
551
				for element in iterator:
552
					arg_repr = _log(names, element)
553
					if arg_repr:
554
						arg_reprs.append(arg_repr)
555
				if arg_reprs:
556
					logger('%s got: [%s]' % (name, '], ['.join(', '.join(arg_repr) for arg_repr in arg_reprs)))
557
				result = []
558
				for res in function(self, iterator, *args):
559
					result.append(res)
560
					yield res
561
				logger('%s returned: %r' % (name, result))
562
			_response = _response_multiple
525
	else:
563
	else:
526
		# remove self
564
		# remove self
527
		names = names[1:]
565
		names = names[1:]
528
		def _response(self, *args):
566
		def _response_simple(self, *args):
529
			arg_repr = _log(names, args)
567
			arg_repr = _log(names, args)
530
			if arg_repr:
568
			if arg_repr:
531
				MODULE.info('%s got: %s' % (name, ', '.join(arg_repr)))
569
				logger('%s got: %s' % (name, ', '.join(arg_repr)))
532
			result = function(self, *args)
570
			result = function(self, *args)
533
			MODULE.info('%s returned: %r' % (name, result))
571
			logger('%s returned: %r' % (name, result))
534
			return result
572
			return result
573
		_response = _response_simple
535
	copy_function_meta_data(function, _response, copy_arg_inspect=True)
574
	copy_function_meta_data(function, _response, copy_arg_inspect=True)
536
	return _response
575
	return _response
537
576
(-)umc/python/appcenter/__init__.py (-41 / +25 lines)
 Lines 38-51    Link Here 
38
import urllib2
38
import urllib2
39
import re
39
import re
40
40
41
# related third party
42
import notifier
43
import notifier.threads
44
45
# univention
41
# univention
46
from univention.lib.package_manager import PackageManager, LockError
42
from univention.lib.package_manager import PackageManager, LockError
47
from univention.management.console.log import MODULE
43
from univention.management.console.log import MODULE
48
from univention.management.console.modules.decorators import simple_response, sanitize, sanitize_list, multi_response
44
from univention.management.console.modules.decorators import simple_response, sanitize, sanitize_list, multi_response, log
49
from univention.management.console.modules.sanitizers import PatternSanitizer, MappingSanitizer, DictSanitizer, StringSanitizer, ChoicesSanitizer, ListSanitizer, EmailSanitizer, BooleanSanitizer
45
from univention.management.console.modules.sanitizers import PatternSanitizer, MappingSanitizer, DictSanitizer, StringSanitizer, ChoicesSanitizer, ListSanitizer, EmailSanitizer, BooleanSanitizer
50
from univention.updater import UniventionUpdater
46
from univention.updater import UniventionUpdater
51
from univention.updater.errors import ConfigurationError
47
from univention.updater.errors import ConfigurationError
 Lines 150-160    Link Here 
150
			application=StringSanitizer(minimum=1, required=True),
146
			application=StringSanitizer(minimum=1, required=True),
151
			force=BooleanSanitizer()
147
			force=BooleanSanitizer()
152
		)
148
		)
153
	def invoke(self, request):
149
	@simple_response
154
		function = request.options.get('function')
150
	@log(logger=MODULE.process, simple_threaded=True)
155
		application_id = request.options.get('application')
151
	def invoke(self, function, application, force):
152
		application_id = application
156
		application = Application.find(application_id)
153
		application = Application.find(application_id)
157
		force = request.options.get('force')
158
		try:
154
		try:
159
			# make sure that the application cane be installed/updated
155
			# make sure that the application cane be installed/updated
160
			can_continue = True
156
			can_continue = True
 Lines 184-206    Link Here 
184
				result['remove'] = application.uninstall_dry_run(self.package_manager)
180
				result['remove'] = application.uninstall_dry_run(self.package_manager)
185
				can_continue = False
181
				can_continue = False
186
			result['can_continue'] = can_continue
182
			result['can_continue'] = can_continue
187
			self.finished(request.id, result)
188
183
184
			yield result
185
189
			if can_continue:
186
			if can_continue:
190
				def _thread(module, application, function):
187
				with self.package_manager.locked(reset_status=True, set_finished=True):
191
					with module.package_manager.locked(reset_status=True, set_finished=True):
188
					with self.package_manager.no_umc_restart(exclude_apache=True):
192
						with module.package_manager.no_umc_restart(exclude_apache=True):
189
						if function in ('install', 'update'):
193
							if function in ('install', 'update'):
190
							# dont have to add component: already added during dry_run
194
								# dont have to add component: already added during dry_run
191
							application.install(self.package_manager, self.component_manager, add_component=False, send_as=function)
195
								return application.install(module.package_manager, module.component_manager, add_component=False, send_as=function)
192
						else:
196
							else:
193
							application.uninstall(self.package_manager, self.component_manager)
197
								return application.uninstall(module.package_manager, module.component_manager)
194
198
				def _finished(thread, result):
199
					if isinstance(result, BaseException):
200
						MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result)))
201
				thread = notifier.threads.Simple('invoke',
202
					notifier.Callback(_thread, self, application, function), _finished)
203
				thread.run()
204
		except LockError:
195
		except LockError:
205
			# make it thread safe: another process started a package manager
196
			# make it thread safe: another process started a package manager
206
			# this module instance already has a running package manager
197
			# this module instance already has a running package manager
 Lines 282-311    Link Here 
282
			}, required=True),
273
			}, required=True),
283
		packages=ListSanitizer(StringSanitizer(minimum=1), required=True)
274
		packages=ListSanitizer(StringSanitizer(minimum=1), required=True)
284
		)
275
		)
285
	def packages_invoke(self, request):
276
	@simple_response
277
	@log(logger=MODULE.process, simple_threaded=True)
278
	def packages_invoke(self, packages, function):
286
		""" executes an installer action """
279
		""" executes an installer action """
287
		packages = request.options.get('packages')
288
		function = request.options.get('function')
289
280
290
		try:
281
		try:
291
			with self.package_manager.locked(reset_status=True):
282
			with self.package_manager.locked(reset_status=True):
292
				not_found = [pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None]
283
				not_found = [pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None]
293
				self.finished(request.id, {'not_found' : not_found})
284
				yield {'not_found' : not_found}
294
285
295
				if not not_found:
286
				if not not_found:
296
					def _thread(package_manager, function, packages):
287
					with self.package_manager.locked(set_finished=True):
297
						with package_manager.locked(set_finished=True):
288
						with self.package_manager.no_umc_restart():
298
							with package_manager.no_umc_restart():
289
							if function == 'install':
299
								if function == 'install':
290
								self.package_manager.install(*packages)
300
									package_manager.install(*packages)
291
							else:
301
								else:
292
								self.package_manager.uninstall(*packages)
302
									package_manager.uninstall(*packages)
303
					def _finished(thread, result):
304
						if isinstance(result, BaseException):
305
							MODULE.warn('Exception during %s %s: %r' % (function, packages, str(result)))
306
					thread = notifier.threads.Simple('invoke', 
307
						notifier.Callback(_thread, self.package_manager, function, packages), _finished)
308
					thread.run()
309
		except LockError:
293
		except LockError:
310
			# make it thread safe: another process started a package manager
294
			# make it thread safe: another process started a package manager
311
			# this module instance already has a running package manager
295
			# this module instance already has a running package manager

Return to bug 30258