コード例 #1
0
class Instance(Base, ProgressMixin):

    PLUGIN_DIR = os.path.dirname(plugins.__file__)

    def init(self):
        self.modules = {}
        self.load()

    @sanitize(plugin=StringSanitizer(required=True), args=DictSanitizer({}))
    @simple_response(with_progress=True)
    def run(self, plugin, args=None):
        plugin = self.get(plugin)
        MODULE.process('Running %s' % (plugin, ))
        for line in plugin.run_descr:
            MODULE.process(line)
        args = args or {}

        return plugin.execute(self, **args)

    def new_progress(self, *args, **kwargs):
        progress = super(Instance, self).new_progress(*args, **kwargs)
        progress.retry_after = 600
        return progress

    @sanitize(pattern=PatternSanitizer(default='.*'))
    @simple_response
    def query(self, pattern):
        return [plugin.dict for plugin in self if plugin.match(pattern)]

    @property
    def plugins(self):
        for plugin in listdir(self.PLUGIN_DIR):
            if plugin.endswith('.py') and plugin != '__init__.py':
                yield plugin[:-3]

    def load(self):
        for plugin in self.plugins:
            try:
                self.modules[plugin] = Plugin(plugin)
            except ImportError as exc:
                MODULE.error('Could not load plugin %r: %r' % (plugin, exc))
                raise
        self.modules = OrderedDict(
            sorted(self.modules.items(), key=lambda t: t[0]))

    def get(self, plugin):
        return self.modules[plugin]

    def __iter__(self):
        return iter(self.modules.values())
コード例 #2
0
class Instance(Base, ProgressMixin):

    PLUGIN_DIR = os.path.dirname(plugins.__file__)

    def init(self):
        self.modules = {}
        self.load()

    @sanitize(plugin=StringSanitizer(required=True), args=DictSanitizer({}))
    @simple_response
    def run(self, plugin, args=None):
        plugin = self.get(plugin)
        args = args or {}

        def thread(self, request):
            return plugin.execute(self, **args)

        return thread

    @sanitize(pattern=PatternSanitizer(default='.*'))
    @simple_response
    def query(self, pattern):
        return [plugin.dict for plugin in self if plugin.match(pattern)]

    @property
    def plugins(self):
        for plugin in listdir(self.PLUGIN_DIR):
            if plugin.endswith('.py') and plugin != '__init__.py':
                yield plugin[:-3]

    def load(self):
        for plugin in self.plugins:
            try:
                self.modules[plugin] = Plugin(plugin)
            except ImportError as exc:
                MODULE.error('Could not load plugin %r: %r' % (plugin, exc))
                raise

    def get(self, plugin):
        return self.modules[plugin]

    def __iter__(self):
        return iter(self.modules.values())
コード例 #3
0
class Instance(Base):
    @sanitize(pattern=PatternSanitizer(default='.*'))
    @simple_response
    def query(self, pattern):
        ucr.load()
        srvs = ServiceInfo()

        lang = _.im_self.locale.language
        if lang in (None, 'C'):
            lang = 'en'

        result = []
        for name, srv in srvs.services.items():
            key = srv.get('start_type', '%s/autostart' % (name, ))
            entry = {
                'service':
                name,
                'description':
                srv.get('description[%s]' % (lang, ), srv.get('description')),
                'autostart':
                ucr.get(key, 'yes'),
                'isRunning':
                srv.running,
            }
            if entry['autostart'] not in ('yes', 'no', 'manually'):
                entry['autostart'] = 'yes'
            for value in entry.values():
                if pattern.match(str(value)):
                    result.append(entry)
                    break
        return result

    @sanitize(StringSanitizer(required=True))
    def start(self, request):
        func = notifier.Callback(self._change_services, request.options,
                                 'start')
        thread = notifier.threads.Simple(
            'services', func,
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    @sanitize(StringSanitizer(required=True))
    def stop(self, request):
        func = notifier.Callback(self._change_services, request.options,
                                 'stop')
        thread = notifier.threads.Simple(
            'services', func,
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    @sanitize(StringSanitizer(required=True))
    def restart(self, request):
        func = notifier.Callback(self._change_services, request.options,
                                 'restart')
        thread = notifier.threads.Simple(
            'services', func,
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    def _change_services(self, services, action):
        error_messages = []
        srvs = ServiceInfo()
        for srv in services:
            service = srvs.get_service(srv)
            try:
                getattr(service, action)()
            except ServiceError as exc:
                MODULE.warn('Error during %s of %s: %s' % (action, srv, exc))
                error_messages.append(
                    '%s\n%s' %
                    ({
                        'start': _('Starting the service %s failed:'),
                        'stop': _('Stopping the service %s failed:'),
                        'restart': _('Restarting the service %s failed:'),
                    }[action] % srv, exc))

        if error_messages:
            raise UMC_Error('\n\n'.join(error_messages))
        return {'success': True}

    @sanitize(StringSanitizer(required=True))
    def start_auto(self, request):
        self._change_start_type(request.options, 'yes')
        self.finished(request.id, {'success': True},
                      _('Successfully changed start type'))

    @sanitize(StringSanitizer(required=True))
    def start_manual(self, request):
        self._change_start_type(request.options, 'manually')
        self.finished(request.id, {'success': True},
                      _('Successfully changed start type'))

    @sanitize(StringSanitizer(required=True))
    def start_never(self, request):
        self._change_start_type(request.options, 'no')
        self.finished(request.id, {'success': True},
                      _('Successfully changed start type'))

    def _change_start_type(self, service_names, start_type):
        service_info = ServiceInfo()
        services = [(service_name, service_info.services[service_name])
                    for service_name in service_names
                    if service_name in service_info.services]
        values = [
            '%s=%s' % (service.get('start_type', '%s/autostart' %
                                   (service_name, )), start_type)
            for service_name, service in services
        ]
        univention.config_registry.handler_set(values)
        failed = [x for x in service_names if not service_info.services.get(x)]
        if failed:
            raise UMC_Error(
                '%s %s' %
                (_('Could not change start type of the following services:'),
                 ', '.join(failed)))
コード例 #4
0
class Instance(Base):

	@sanitize(
		pattern=PatternSanitizer(default='.*'),
		category=ChoicesSanitizer(choices=['user', 'pid', 'command', 'all'], default='all')
	)
	@simple_response
	def query(self, pattern, category='all'):
		processes = []
		for process in psutil.process_iter():
			try:
				cpu_time = process.cpu_times()
				proc = {
					'timestamp': time.time(),
					'cpu_time': cpu_time.user + cpu_time.system,
					'user': process.username(),
					'pid': process.pid,
					'cpu': 0.0,
					'mem': process.memory_percent(),
					'command': ' '.join(process.cmdline() or []) or process.name(),
				}
			except psutil.NoSuchProcess:
				continue

			categories = [category]
			if category == 'all':
				categories = ['user', 'pid', 'command']
			if any(pattern.match(str(proc[cat])) for cat in categories):
				processes.append(proc)

		# Calculate correct cpu percentage
		time.sleep(1)
		for process_entry in processes:
			try:
				process = psutil.Process(process_entry['pid'])
				cpu_time = process.cpu_times()
			except psutil.NoSuchProcess:
				continue
			elapsed_time = time.time() - process_entry.pop('timestamp')
			elapsed_cpu_time = cpu_time.user + cpu_time.system - process_entry.pop('cpu_time')
			cpu_percent = (elapsed_cpu_time / elapsed_time) * 100
			process_entry['cpu'] = cpu_percent

		return processes

	@sanitize(
		signal=ChoicesSanitizer(choices=['SIGTERM', 'SIGKILL']),
		pid=ListSanitizer(IntegerSanitizer())
	)
	@simple_response
	def kill(self, signal, pid):
		failed = []
		for pid_ in pid:
			try:
				process = psutil.Process(pid_)
				if signal == 'SIGTERM':
					process.terminate()
				elif signal == 'SIGKILL':
					process.kill()
			except psutil.NoSuchProcess as exc:
				failed.append(str(pid_))
				MODULE.error('Could not %s pid %s: %s' % (signal, pid_, exc))
		if failed:
			failed = ', '.join(failed)
			raise UMC_Error(_('No process found with PID %s') % (failed))
コード例 #5
0
class Instance(Base, ProgressMixin):
    def __init__(self, *args, **kwargs):
        Base.__init__(self, *args, **kwargs)
        ProgressMixin.__init__(self)
        self._finishedLock = threading.Lock()
        self._finishedResult = True
        self._progressParser = util.ProgressParser()
        self.__keep_alive_request = None
        self._net_apply_running = 0
        # reset umask to default
        os.umask(0o022)

    def init(self):
        os.putenv('LANG', str(self.locale))
        _locale.setlocale(_locale.LC_ALL, str(self.locale))
        if not util.is_system_joined():
            self._preload_city_data()

    def _preload_city_data(self):
        util.get_city_data()
        util.get_country_data()

    def _get_localized_label(self, label_dict):
        # return the correctly loca
        return label_dict.get(self.locale.language) or label_dict.get(
            'en', '') or label_dict.get('', '')

    def ping(self, request):
        if request.options.get('keep_alive'):
            self.__keep_alive_request = request
            return
        self.finished(request.id, None)

    @simple_response
    def close_browser(self):
        try:
            with open('/var/cache/univention-system-setup/browser.pid',
                      'rb') as fd:
                pid = int(fd.readline().strip())
                process = psutil.Process(pid)
                process.kill()
                return True
        except IOError as exc:
            MODULE.warn('cannot open browser PID file: %s' % (exc, ))
        except ValueError as exc:
            MODULE.error('browser PID is not a number: %s' % (exc, ))
        except psutil.NoSuchProcess as exc:
            MODULE.error('cannot kill process with PID: %s' % (exc, ))
        return False

    @simple_response
    def load(self):
        '''Return a dict with all necessary values for system-setup read from the current
		status of the system.'''
        return util.load_values(self.locale.language)

    @simple_response
    def save_keymap(self, layout=None):
        '''Set the systems x-keymap according to
		request.options[keymap]'''

        # Don't set in debian installer mode
        if ucr.is_true('system/setup/boot/installer'):
            return True

        if layout:
            subprocess.call(
                ['/usr/bin/setxkbmap', '-display', ':0', '-layout', layout])
        return True

    def save(self, request):
        '''Reconfigures the system according to the values specified in the dict given as
		option named "values".'''

        # get new values
        values = request.options.get('values', {})
        run_hooks = request.options.get('run_hooks', False)

        script_args = []
        if run_hooks:
            # create a status file that indicates that save has been triggered
            util.create_status_file()

            # enforce particular arguments for setup scripts
            script_args = [
                '--appliance-mode', '--force-recreate', '--demo-mode'
            ]

        def _thread(request, obj):
            # acquire the lock until the scripts have been executed
            self._finishedResult = False
            obj._finishedLock.acquire()
            try:
                subfolders = {
                    'network': ['30_net'],
                    'certificate': ['40_ssl'],
                    'languages': ['15_keyboard', '20_language', '35_timezone'],
                }.get(request.flavor)

                self._progressParser.reset(subfolders)

                if request.flavor == 'setup':
                    # adjust progress fractions for setup wizard with pre-configurred settings
                    fractions = self._progressParser.fractions
                    fractions['05_role/10role'] = 0
                    fractions['10_basis/12domainname'] = 0
                    fractions['10_basis/14ldap_basis'] = 0
                    fractions['90_postjoin/10admember'] = 0
                    self._progressParser.calculateFractions()

                MODULE.info('saving profile values')
                util.write_profile(values)

                if not values:
                    MODULE.error('No property "values" given for save().')
                    return False

                # in case of changes of the IP address, restart UMC server and web server
                # for this we ignore changes of virtual or non-default devices
                # ... no need to restart the UMC server if cleanup scripts are run anyway
                restart = False
                if not run_hooks:
                    MODULE.info('Check whether ip addresses have been changed')
                    for ikey, ival in values.iteritems():
                        if RE_IPV4.match(ikey) or RE_IPV6_DEFAULT.match(
                                ikey) or RE_SSL.match(ikey):
                            restart = True
                            break
                    MODULE.info('Restart servers: %s' % restart)

                # on a joined system or on a basesystem, we can run the setup scripts
                MODULE.info('runnning system setup scripts (flavor %r)' %
                            (request.flavor, ))

                util.run_scripts(self._progressParser,
                                 restart,
                                 subfolders,
                                 lang=str(self.locale),
                                 args=script_args)

                # run cleanup scripts and appliance hooks if needed
                if run_hooks:
                    util.cleanup(with_appliance_hooks=True)

                # done :)
                self._finishedResult = True
                return True
            finally:
                obj._finishedLock.release()

        def _finished(thread, result):
            if self.__keep_alive_request:
                self.finished(self.__keep_alive_request.id, None)
                self.__keep_alive_request = None

            if isinstance(result, BaseException):
                msg = ''.join(thread.trace + traceback.format_exception_only(
                    *thread.exc_info[:2]))
                MODULE.warn('Exception during saving the settings: %s' %
                            (msg, ))
                self._progressParser.current.errors.append(
                    _('Encountered unexpected error during setup process: %s')
                    % result)
                self._progressParser.current.critical = True
                self._finishedResult = True

        thread = notifier.threads.Simple(
            'save', notifier.Callback(_thread, request, self), _finished)
        thread.run()
        self.finished(request.id, None)

    @simple_response
    def join(self, values=None, dcname=None, username=None, password=None):
        '''Join and reconfigure the system according to the values specified in the dict given as
		option named "values".'''

        # get old and new values
        orgValues = util.load_values()
        values = values or {}

        # determine new system role
        oldrole = orgValues.get('server/role', '')
        newrole = values.get('server/role', oldrole)

        # create a status file that indicates that save has been triggered
        util.create_status_file()

        def _thread(obj, username, password):
            # acquire the lock until the scripts have been executed
            self._finishedResult = False
            obj._finishedLock.acquire()
            try:
                self._progressParser.reset()

                # write the profile file and run setup scripts
                util.auto_complete_values_for_join(values)

                # on unjoined DC master the nameserver must be set to the external nameserver
                if newrole == 'domaincontroller_master' and not orgValues.get(
                        'joined'):
                    for i in range(1, 4):
                        # overwrite these values only if they are set, because the UMC module
                        # will save only changed values
                        if values.get('dns/forwarder%d' % i):
                            values['nameserver%d' % i] = values.get(
                                'dns/forwarder%d' % i)

                MODULE.info('saving profile values')
                util.write_profile(values)

                # unjoined DC master (that is not being converted to a basesystem) -> run the join script
                MODULE.info('runnning system setup join script')
                util.run_joinscript(self._progressParser,
                                    values,
                                    username,
                                    password,
                                    dcname,
                                    lang=str(self.locale))

                # done :)
                self._finishedResult = True

                return True
            finally:
                obj._finishedLock.release()

        def _finished(thread, result):
            if self.__keep_alive_request:
                self.finished(self.__keep_alive_request.id, None)
                self.__keep_alive_request = None

            if isinstance(result, BaseException):
                msg = ''.join(thread.trace + traceback.format_exception_only(
                    *thread.exc_info[:2]))
                MODULE.warn('Exception during saving the settings: %s' %
                            (msg, ))
                self._progressParser.current.errors.append(
                    _('Encountered unexpected error during setup process: %s')
                    % (result, ))
                self._progressParser.current.critical = True
                self._finishedResult = True

        thread = notifier.threads.Simple(
            'join', notifier.Callback(_thread, self, username, password),
            _finished)
        thread.run()
        return

    def check_finished(self, request):
        '''Check whether the join/setup scripts are finished. This method implements a long
		polling request, i.e., the request is only finished at the moment when all scripts
		have been executed or due to a timeout. If it returns because of the timeout, a new
		try can be started.'''
        def _thread(request, obj):
            def progress_info(state, **kwargs):
                info = {
                    'component': state.fractionName,
                    'info': state.message,
                    'errors': state.errors,
                    'critical': state.critical,
                    'steps': state.percentage
                }
                info.update(kwargs)
                MODULE.info(
                    'Progress state: %(steps).1f%% - %(component)s - %(info)s'
                    % info)
                return info

            # acquire the lock in order to wait for the join/setup scripts to finish
            # do this for 30 sec and then return anyway
            SLEEP_TIME = 0.200
            WAIT_TIME = 30
            ntries = WAIT_TIME / SLEEP_TIME
            while not obj._finishedLock.acquire(False):
                if ntries <= 0 or self._progressParser.changed and self._progressParser.current:
                    state = self._progressParser.current
                    return progress_info(state, finished=False)
                time.sleep(SLEEP_TIME)
                ntries -= 1

            obj._finishedLock.release()

            # scripts are done, return final result
            # return all errors that we gathered throughout the setup
            state = self._progressParser.current
            return progress_info(state, finished=obj._finishedResult)

        thread = notifier.threads.Simple(
            'check_finished', notifier.Callback(_thread, request, self),
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    @simple_response(with_flavor=True)
    def validate(self, values=None, flavor=None):
        '''Validate the specified values given in the dict as option named "values".
		Return a dict (with variable names as key) of dicts with the structure:
		{ "valid": True/False, "message": "..." }'''

        # init variables
        messages = []
        values = values or {}
        orgValues = util.load_values()
        is_wizard_mode = flavor == 'wizard'

        # determine new system role
        newrole = values.get('server/role', orgValues.get('server/role', ''))
        ad_member = values.get('ad/member', orgValues.get('ad/member', ''))

        # mix original and new values
        allValues = copy.copy(values)
        for ikey, ival in orgValues.iteritems():
            if ikey not in allValues:
                allValues[ikey] = ival

        # helper functions
        # TODO: 'valid' is not correctly evaluated in frontend
        # i.e. if valid you may continue without getting message
        def _check(key, check, message, critical=True):
            if key not in values:
                return
            if not check(values[key]):
                messages.append({
                    'message': message,
                    'valid': not critical,
                    'key': key
                })

        def _append(key, message):
            MODULE.warn('Validation failed for key %s: %s' % (key, message))
            messages.append({'key': key, 'valid': False, 'message': message})

        # host and domain name
        packages = set(values.get('components', []))
        _check(
            'hostname', util.is_hostname,
            _('The hostname or the hostname part of the fully qualified domain name is invalid. Please go back to the host setting and make sure, that the hostname only contains letter (a-zA-Z) and digits (0-9).'
              ))

        hostname_length_critical = ad_member or 'univention-samba' in packages or 'univention-samba4' in packages
        appliance_str = _('the UCS system')
        if ucr['umc/web/appliance/name']:
            appliance_str = _('the %s appliance') % (
                ucr['umc/web/appliance/name'], )
        hostname_length_message = _(
            'A valid NetBIOS name can not be longer than 13 characters. If Samba is installed, the hostname should be shortened.'
        ) if hostname_length_critical else _(
            'The hostname %s is longer than 13 characters. It will not be possible to install an Active Directory compatible Domaincontroller (Samba 4) or UCS@school. The hostname cannot be changed after the installation of %s. It is recommended to shorten the hostname to maximal 13 characters.'
        ) % (
            values.get('hostname', ''),
            appliance_str,
        )
        _check('hostname',
               lambda x: len(x) <= 13,
               hostname_length_message,
               critical=hostname_length_critical)

        _check(
            'domainname', util.is_domainname,
            _("Please enter a valid fully qualified domain name (e.g. host.example.com)."
              ))
        hostname = allValues.get('hostname', '')
        domainname = allValues.get('domainname', '')
        if hostname or domainname:
            if len('%s%s' % (hostname, domainname)) >= 63:
                _append(
                    'domainname',
                    _('The length of fully qualified domain name is greater than 63 characters.'
                      ))
            if hostname == domainname.split('.')[0]:
                _append('domainname', _("Hostname is equal to domain name."))
        if is_wizard_mode and not util.is_system_joined():
            if newrole == 'domaincontroller_master' and not values.get(
                    'domainname'):
                _append(
                    'domainname',
                    _("No fully qualified domain name has been specified for the system."
                      ))
            elif not values.get('hostname'):
                _append('hostname',
                        _("No hostname has been specified for the system."))

        # windows domain
        _check(
            'windows/domain', lambda x: x == x.upper(),
            _("The windows domain name can only consist of upper case characters."
              ))
        _check(
            'windows/domain', lambda x: len(x) <= 15,
            _("The windows domain name cannot be longer than 15 characters."))
        _check('windows/domain', util.is_windowsdomainname,
               _("The windows domain name is not valid."))

        # LDAP base
        _check(
            'ldap/base', util.is_ldap_base,
            _("The LDAP base may neither contain blanks nor any special characters. Its structure needs to consist of at least two relative distinguished names (RDN) which may only use the attribute tags 'dc', 'cn', 'c', 'o', or 'l' (e.g., dc=test,dc=net)."
              ))

        # root password
        _check(
            'root_password', lambda x: len(x) >= 8,
            _("The root password is too short. For security reasons, your password must contain at least 8 characters."
              ))
        _check('root_password', util.is_ascii,
               _("The root password may only contain ascii characters."))

        # ssl + email
        labels = {
            'ssl/country': _('Country'),
            'ssl/state': _('State'),
            'ssl/locality': _('Location'),
            'ssl/organization': _('Organization'),
            'organization': _('Organization'),
            'ssl/organizationalunit': _('Business unit'),
            'ssl/email': _('Email address'),
            'email_address': _('Email address'),
            'ssl/common': _('Common name for the root SSL certificate'),
        }
        for maxlenth, keys in [
            (2, ('ssl/country', )), (128, (
                'ssl/state',
                'ssl/locality',
            )),
            (64, ('organization', 'ssl/organization', 'ssl/organizationalunit',
                  'ssl/email', 'email_address', 'ssl/common'))
        ]:
            for ikey in keys:
                _check(
                    ikey, lambda x: len(x) <= maxlenth,
                    _('The following value is too long, only %(max)s characters allowed: %(name)s'
                      ) % {
                          'max': maxlenth,
                          'name': labels[ikey]
                      })

        for ikey in ('ssl/country', 'ssl/state', 'ssl/locality',
                     'ssl/organization', 'ssl/organizationalunit', 'ssl/email',
                     'ssl/common'):
            for table in (stringprep.in_table_c21_c22, stringprep.in_table_a1,
                          stringprep.in_table_c8, stringprep.in_table_c3,
                          stringprep.in_table_c4, stringprep.in_table_c5,
                          lambda c: c == u'\ufffd'):
                _check(
                    ikey, lambda x: not any(map(table, unicode(x))),
                    _('The value for %s contains invalid characters.') %
                    (labels[ikey], ))

        _check('ssl/country', lambda x: len(x) == 2,
               _('Country must be a country code consisting of 2 characters.'))
        for ikey in ['ssl/email', 'email_address']:
            _check(ikey, lambda x: x.find('@') > 0,
                   _("Please enter a valid email address"))

        # net
        try:
            interfaces = network.Interfaces()
            interfaces.from_dict(allValues.get('interfaces', {}))
            interfaces.check_consistency()
        except network.DeviceError as exc:
            _append('interfaces', str(exc))

        # validate the primary network interface
        _check('interfaces/primary', lambda x: not x or x in interfaces,
               _('The primary network device must exist.'))

        # check nameservers
        for ikey, iname in [('nameserver[1-3]', _('Domain name server')),
                            ('dns/forwarder[1-3]', _('External name server'))]:
            reg = re.compile('^(%s)$' % ikey)
            for jkey, jval in values.iteritems():
                if reg.match(jkey):
                    if not values.get(jkey):
                        # allow empty value
                        continue
                    _check(
                        jkey, util.is_ipaddr,
                        _('The specified IP address (%(name)s) is not valid: %(value)s'
                          ) % {
                              'name': iname,
                              'value': jval
                          })

        if is_wizard_mode and not util.is_system_joined() and (
                newrole not in ['domaincontroller_master', 'basesystem']
                or ad_member):
            if all(nameserver in values and not values[nameserver]
                   for nameserver in ('nameserver1', 'nameserver2',
                                      'nameserver3')):
                # 'nameserver1'-key exists → widget is displayed → = not in UCS/debian installer mode
                if not any(interface.ip4dynamic or interface.ip6dynamic
                           for interface in interfaces.values()):
                    _append('nameserver1',
                            _('A domain name server needs to be specified.'))
                    # _append('nameserver1', _('At least one domain name server needs to be given if DHCP or SLAAC is not specified.'))

            # see whether the domain can be determined automatically
            ucr.load()
            guessed_domain = None
            for obj in [values, ucr]:
                for nameserver in ('nameserver1', 'nameserver2',
                                   'nameserver3'):
                    nameserver = obj.get(nameserver)
                    if nameserver:
                        guessed_domain = None
                        if obj.get('ad/member') and obj.get('ad/address'):
                            try:
                                ad_domain_info = lookup_adds_dc(
                                    obj.get('ad/address'),
                                    ucr={'nameserver1': nameserver})
                            except failedADConnect:
                                pass
                            else:
                                guessed_domain = ad_domain_info['Domain']
                        else:
                            guessed_domain = util.get_ucs_domain(nameserver)
                        if guessed_domain:
                            differing_domain_name = values.get(
                                'domainname') and values['domainname'].lower(
                                ) != guessed_domain.lower()
                            if differing_domain_name:
                                _append(
                                    'domainname',
                                    _('The specified domain name is different to the %s domain name found via the configured DNS server: %s'
                                      ) % (
                                          _('Active Directory')
                                          if ad_member else _('UCS'),
                                          guessed_domain,
                                      ))
                            else:
                                # communicate guessed domainname to frontend
                                messages.append({
                                    'valid': True,
                                    'key': 'domainname',
                                    'value': guessed_domain,
                                })
                            break
                if guessed_domain:
                    break
            if not guessed_domain:
                if not values.get('domainname'):
                    _append(
                        'domainname',
                        _('Cannot automatically determine the domain. Please specify the server\'s fully qualified domain name.'
                          ))

                if values.get('nameserver1') and values.get('start/join'):
                    _append(
                        'nameserver1',
                        _('The specified nameserver %s is not part of a valid UCS domain.'
                          ) % (values['nameserver1'], ))

        # check gateways
        if values.get('gateway'):  # allow empty value
            _check(
                'gateway', util.is_ipv4addr,
                _('The specified gateway IPv4 address is not valid: %s') %
                values.get('gateway'))
        if values.get('ipv6/gateway'):  # allow empty value
            _check(
                'ipv6/gateway', util.is_ipv6addr,
                _('The specified gateway IPv6 address is not valid: %s') %
                values.get('ipv6/gateway'))

        # proxy
        _check(
            'proxy/http', util.is_proxy,
            _('The specified proxy address is not valid (e.g., http://10.201.1.1:8080): %s'
              ) % allValues.get('proxy/http', ''))

        # software checks
        if 'univention-virtual-machine-manager-node-kvm' in packages and 'univention-virtual-machine-manager-node-xen' in packages:
            _append(
                'components',
                _('It is not possible to install KVM and XEN components on one system. Please select only one of these components.'
                  ))
        if 'univention-samba' in packages and 'univention-samba4' in packages:
            _append(
                'components',
                _('It is not possible to install Samba 3 and Samba 4 on one system. Please select only one of these components.'
                  ))

        return messages

    @sanitize(pattern=PatternSanitizer(default='.*',
                                       required=True,
                                       add_asterisks=True))
    @simple_response
    def lang_locales(self, pattern, category='language_en'):
        '''Return a list of all available locales.'''
        return util.get_available_locales(pattern, category)

    def lang_timezones(self, request):
        '''Return a list of all available time zones.'''
        try:
            file = open('/usr/share/univention-system-setup/locale/timezone')
        except EnvironmentError:
            MODULE.error(
                'Cannot find locale data for timezones in /usr/share/univention-system-setup/locale'
            )
            self.finished(request.id, None)
            return

        timezones = [i.strip('\n') for i in file if not i.startswith('#')]

        self.finished(request.id, timezones)

    @simple_response
    def lang_keyboard_model(self):
        '''Return a list of all available keyboard models.'''

        tree = lxml.etree.parse(open('/usr/share/X11/xkb/rules/base.xml'))
        models = tree.xpath("//model")

        model_result = [{
            'label':
            i18nXKeyboard.translate(
                model.xpath('./configItem/description')[0].text),
            'id':
            model.xpath('./configItem/name')[0].text
        } for model in models]

        return model_result

    @simple_response
    def lang_keyboard_layout(self):
        '''Return a list of all available keyboard layouts.'''

        tree = lxml.etree.parse(open('/usr/share/X11/xkb/rules/base.xml'))
        layouts = tree.xpath("//layout")

        layout_result = [{
            'label':
            i18nXKeyboard.translate(
                layout.xpath('./configItem/description')[0].text),
            'id':
            layout.xpath('./configItem/name')[0].text,
            'language':
            layout.xpath('./configItem/shortDescription')[0].text,
            'countries':
            ':'.join([
                icountry.text
                for icountry in layout.xpath('./configItem/countryList/*')
            ]),
        } for layout in layouts]

        return layout_result

    @sanitize(keyboardlayout=StringSanitizer(default='us'))
    @simple_response
    def lang_keyboard_variante(self, keyboardlayout):
        '''Return a list of all available keyboard variantes.'''

        variante_result = []
        tree = lxml.etree.parse(open('/usr/share/X11/xkb/rules/base.xml'))
        layouts = tree.xpath("//layout")

        for layout in layouts:
            layoutID = layout.xpath("./configItem/name")[0].text
            if layoutID != keyboardlayout:
                continue
            variants = layout.xpath("./variantList/variant")
            variante_result += [{
                'label':
                i18nXKeyboard.translate(
                    variant.xpath('./configItem/description')[0].text),
                'id':
                variant.xpath('./configItem/name')[0].text
            } for variant in variants]

        variante_result.insert(0, {'label': '', 'id': ''})

        return variante_result

    def lang_countrycodes(self, request):
        '''Return a list of all countries with their two letter chcountry codes.'''
        country_data = util.get_country_data()
        countries = [{
            'id':
            icountry,
            'label':
            self._get_localized_label(idata.get('label', {})),
        } for icountry, idata in country_data.iteritems()
                     if idata.get('label')]

        # add the value from ucr value to the list
        # this is required because invalid values will be unset in frontend
        # Bug #26409
        tmpUCR = univention.config_registry.ConfigRegistry()
        tmpUCR.load()
        ssl_country = tmpUCR.get('ssl/country')
        if ssl_country not in [i['id'] for i in countries]:
            countries.append({'label': ssl_country, 'id': ssl_country})

        self.finished(request.id, countries)

    def net_apply(self, request):
        if self._net_apply_running > 0:
            # do not start another process applying the network settings
            return False

        values = request.options.get('values', {})
        demo_mode = request.options.get('demo_mode', False)

        def _thread(obj):
            obj._net_apply_running += 1
            MODULE.process('Applying network settings')
            with util.written_profile(values):
                util.run_networkscrips(demo_mode)

        def _finished(thread, result):
            self._net_apply_running -= 1
            self.finished(request.id, True)

        thread = notifier.threads.Simple('net_apply',
                                         notifier.Callback(_thread, self),
                                         _finished)
        thread.run()

    @simple_response
    def net_apply_check_finished(self):
        if self._net_apply_running > 0:
            # raise an error if net_apply command is still running...
            # this allows long polling on the client side (poll until successful request)
            raise RequestTimeout()
        return self._net_apply_running == 0

    @simple_response
    def net_interfaces(self):
        '''Return a list of all available network interfaces.'''
        return [idev['name'] for idev in util.detect_interfaces()]

    # workaround: use with_progress to make the method threaded
    @simple_response(with_progress=True)
    def net_dhclient(self, interface, timeout=10):
        '''Request a DHCP address. Expects as options a dict containing the key
		"interface" and optionally the key "timeout" (in seconds).'''
        return util.dhclient(interface, timeout)

    @sanitize(locale=StringSanitizer(default='en_US'))
    @simple_response
    def reset_locale(self, locale):
        locale = Locale(locale)
        locale.codeset = self.locale.codeset
        MODULE.info('Switching language to: %s' % locale)
        os.putenv('LANG', str(self.locale))
        try:
            _locale.setlocale(_locale.LC_ALL, str(locale))
        except _locale.Error:
            MODULE.warn(
                'Locale %s is not supported, using fallback locale "C" instead.'
                % locale)
            _locale.setlocale(_locale.LC_ALL, 'C')
        self.locale = locale

        # dynamically change the translation methods
        _translation.set_language(str(self.locale))
        i18nXKeyboard.set_language(str(self.locale))
        network._translation.set_language(str(self.locale))
        AppCache().clear_cache()

    @sanitize(pattern=StringSanitizer(),
              max_results=IntegerSanitizer(minimum=1, default=5))
    @simple_response
    def find_city(self, pattern, max_results):
        pattern = pattern.decode(self.locale.codeset).lower()
        MODULE.info('pattern: %s' % pattern)
        if not pattern:
            return []

        # for the given pattern, find matching cities
        city_data = util.get_city_data()
        matches = []
        for icity in city_data:
            match = None
            for jlabel in icity.get('label', {}).itervalues():
                label = jlabel.decode(self.locale.codeset).lower()
                if pattern in label:
                    # matching score is the overlap if the search pattern and the matched text
                    # (as fraction between 0 and 1)
                    match_score = len(pattern) / float(len(label))
                    if match and match_score < match['match_score']:
                        # just keep the best match of a city
                        continue
                    if match_score > 0.1:
                        # found a match with more than 10% overlap :)
                        match = icity.copy()
                        match['match'] = jlabel
                        match['match_score'] = match_score
            if match:
                matches.append(match)
        MODULE.info('Search for pattern "%s" with %s matches' %
                    (pattern, len(matches)))
        if not matches:
            return None

        # add additional score w.r.t. the population size of the city
        # such that the largest city gains additional 0.4 on top
        max_population = max([imatch['population'] for imatch in matches])
        weighted_inv_max_population = 0.6 / float(max_population)
        for imatch in matches:
            imatch['final_score'] = imatch[
                'match_score'] + weighted_inv_max_population * imatch[
                    'population']

        # sort matches...
        matches.sort(key=lambda x: x['final_score'], reverse=True)
        MODULE.info('Top 5 matches: %s' % json.dumps(matches[:5], indent=2))
        matches = matches[:max_results]

        # add additional information about keyboard layout, time zone etc. and
        # get the correct localized labels
        country_data = util.get_country_data()
        for imatch in matches:
            match_country = country_data.get(imatch.get('country'))
            if match_country:
                imatch.update(util.get_random_nameserver(match_country))
                imatch.update(
                    dict(
                        default_lang=match_country.get('default_lang'),
                        country_label=self._get_localized_label(
                            match_country.get('label', {})),
                        label=self._get_localized_label(imatch.get('label'))
                        or imatch.get('match'),
                    ))

        return matches

    @simple_response
    def apps_query(self):
        return util.get_apps(True)

    @simple_response
    def check_domain(self, role, nameserver):
        result = {}
        if role == 'ad':
            try:
                ad_domain_info = lookup_adds_dc(nameserver)
                dc = ad_domain_info['DC DNS Name']
                if dc:
                    result['dc_name'] = dc
                    domain = ad_domain_info['Domain']
                    result['domain'] = domain
                    result['ucs_master'] = util.is_ucs_domain(
                        nameserver, domain)
                    ucs_master_fqdn = util.resolve_domaincontroller_master_srv_record(
                        nameserver, domain)
                    result['ucs_master_fqdn'] = ucs_master_fqdn
                    result['ucs_master_reachable'] = util.is_ssh_reachable(
                        ucs_master_fqdn)
            except (failedADConnect, connectionFailed) as exc:
                MODULE.warn('ADDS DC lookup failed: %s' % (exc, ))
        elif role == 'nonmaster':
            domain = util.get_ucs_domain(nameserver)
            if domain:
                fqdn = util.resolve_domaincontroller_master_srv_record(
                    nameserver, domain)
            else:
                fqdn = util.get_fqdn(nameserver)
            if fqdn:
                result['dc_name'] = fqdn
                domain = '.'.join(fqdn.split('.')[1:])
                result['ucs_master'] = util.is_ucs_domain(nameserver, domain)
        return result

    @simple_response
    def check_domain_join_information(self, domain_check_role, role, dns,
                                      nameserver, address, username, password):
        result = {}
        if domain_check_role == 'ad':
            domain = util.check_credentials_ad(nameserver, address, username,
                                               password)
            result['domain'] = domain
            if dns:  # "dns" means we don't want to replace the existing DC Master
                ucs_master_fqdn = util.resolve_domaincontroller_master_srv_record(
                    nameserver, domain)
                if ucs_master_fqdn:
                    # if we found a _domaincontroller_master._tcp SRV record the system will be a DC Backup/Slave/Member.
                    # We need to check the credentials of this system, too, so we ensure that the System is reachable via SSH.
                    # Otherwise the join will fail with strange error like "ping to ..." failed.
                    result.update(
                        receive_domaincontroller_master_information(
                            False, nameserver, ucs_master_fqdn, username,
                            password))
                    set_role_and_check_if_join_will_work(
                        role, ucs_master_fqdn, username, password)
        elif domain_check_role == 'nonmaster':
            result.update(
                receive_domaincontroller_master_information(
                    dns, nameserver, address, username, password))
            set_role_and_check_if_join_will_work(role, address, username,
                                                 password)
        # master? basesystem? no domain check necessary
        return result

    @simple_response
    def check_school_information(self, hostname, address, username, password):
        return check_for_school_domain(hostname, address, username, password)

    @simple_response
    def check_repository_accessibility(self):
        return get_unreachable_repository_servers()

    @simple_response
    def check_uid(self, uid, role, address, username, password):
        return check_if_uid_is_available(uid, role, address, username,
                                         password)
コード例 #6
0
class Instance(umcm.Base, ProgressMixin):
    def init(self):
        os.umask(
            0o022
        )  # umc umask is too restrictive for app center as it creates a lot of files in docker containers
        self.ucr = ucr_instance()

        self.update_applications_done = False
        install_opener(self.ucr)
        self._is_working = False

        try:
            self.package_manager = PackageManager(
                info_handler=MODULE.process,
                step_handler=None,
                error_handler=MODULE.warn,
                lock=False,
            )
        except SystemError as exc:
            MODULE.error(str(exc))
            raise umcm.UMC_Error(str(exc), status=500)
        self.package_manager.set_finished(
        )  # currently not working. accepting new tasks
        get_package_manager._package_manager = self.package_manager

        # build cache
        _update_modules()
        get_action('list').get_apps()

        # not initialize here: error prone due to network errors and also kinda slow
        self._uu = None
        self._cm = None

        # in order to set the correct locale
        locale.setlocale(locale.LC_ALL, str(self.locale))

        try:
            log_to_logfile()
        except IOError:
            pass

        # connect univention.appcenter.log to the progress-method
        handler = ProgressInfoHandler(self.package_manager)
        handler.setLevel(logging.INFO)
        get_base_logger().addHandler(handler)

        percentage = ProgressPercentageHandler(self.package_manager)
        percentage.setLevel(logging.DEBUG)
        get_base_logger().getChild('actions.install.progress').addHandler(
            percentage)
        get_base_logger().getChild('actions.upgrade.progress').addHandler(
            percentage)
        get_base_logger().getChild('actions.remove.progress').addHandler(
            percentage)

    def get_updater(self):
        if self._uu is None:
            self._uu = UniventionUpdater(False)
        return self._uu

    def get_component_manager(self):
        if self._cm is None:
            self._cm = ComponentManager(self.ucr, self.get_updater())
        return self._cm

    def error_handling(self, etype, exc, etraceback):
        error_handling(etype, exc, etraceback)
        return super(Instance, self).error_handling(exc, etype, etraceback)

    @simple_response
    def version(self, version=None):
        info = get_action('info')
        ret = info.get_compatibility()
        if not info.is_compatible(version):
            raise umcm.UMC_Error(
                'The App Center version of the requesting host is not compatible with the version of %s (%s)'
                % (get_local_fqdn(), ret))
        return ret

    @sanitize(
        version=StringSanitizer(required=True),
        function=StringSanitizer(required=False),
    )
    @simple_response
    def version2(self, version, function=None):
        info = get_action('info')
        return {
            'compatible': info.is_compatible(version, function=function),
            'version': info.get_ucs_version()
        }

    def _remote_appcenter(self, host, function=None):
        if host is None:
            raise ValueError('Cannot connect to None')
        if not host.endswith('.%s' % self.ucr.get('domainname')):
            raise ValueError('Only connect to FQDNs within the domain')
        info = get_action('info')
        opts = {'version': info.get_ucs_version()}
        if function is not None:
            opts['function'] = function
        try:
            client = Client(host, self.username, self.password)
            response = client.umc_command('appcenter/version2', opts)
        except (HTTPError) as exc:
            raise umcm.UMC_Error(
                _('Problems connecting to {0} ({1}). Please update {0}!').
                format(host, exc.message))
        except (ConnectionError, Exception) as exc:
            raise umcm.UMC_Error(
                _('Problems connecting to {} ({}).').format(host, str(exc)))
        err_msg = _(
            'The App Center version of the this host ({}) is not compatible with the version of {} ({})'
        ).format(opts['version'], host, response.result.get('version'))
        # i guess this is kind of bad
        if response.status != 200:
            raise umcm.UMC_Error(err_msg)
        # remote says he is not compatible
        if response.result.get('compatible', True) is False:
            raise umcm.UMC_Error(err_msg)
        # i'm not compatible
        if not info.is_compatible(response.result.get('version')):
            raise umcm.UMC_Error(err_msg)
        return client

    @sanitize(apps=ListSanitizer(AppSanitizer(), required=True),
              action=ChoicesSanitizer(['install', 'upgrade', 'remove'],
                                      required=True))
    @simple_response
    def resolve(self, apps, action):
        ret = {}
        ret['apps'] = resolve_dependencies(apps, action)
        ret['auto_installed'] = [
            app.id for app in ret['apps']
            if app.id not in [a.id for a in apps]
        ]
        apps = ret['apps']
        ret['errors'], ret['warnings'] = check(apps, action)
        domain = get_action('domain')
        ret['apps'] = domain.to_dict(apps)
        ret['settings'] = {}
        self.ucr.load()
        for app in apps:
            ret['settings'][app.id] = self._get_config(app, action.title())
        return ret

    @require_apps_update
    @require_password
    @sanitize(
        apps=ListSanitizer(AppSanitizer(), required=True),
        auto_installed=ListSanitizer(required=True),
        action=ChoicesSanitizer(['install', 'upgrade', 'remove'],
                                required=True),
        hosts=DictSanitizer({}, required=True),
        settings=DictSanitizer({}, required=True),
        dry_run=BooleanSanitizer(),
    )
    @simple_response(with_progress=True)
    def run(self, progress, apps, auto_installed, action, hosts, settings,
            dry_run):
        localhost = get_local_fqdn()
        ret = {}
        if dry_run:
            for host in hosts:
                _apps = [
                    next(app for app in apps if app.id == _app)
                    for _app in hosts[host]
                ]
                if host == localhost:
                    ret[host] = self._run_local_dry_run(
                        _apps, action, {}, progress)
                else:
                    try:
                        ret[host] = self._run_remote_dry_run(
                            host, _apps, action, auto_installed, {}, progress)
                    except umcm.UMC_Error:
                        ret[host] = {'unreachable': [app.id for app in _apps]}
        else:
            for app in apps:
                for host in hosts:
                    if app.id not in hosts[host]:
                        continue
                    host_result = ret.get(host, {})
                    ret[host] = host_result
                    _settings = {app.id: settings[app.id]}
                    if host == localhost:
                        host_result[app.id] = self._run_local(
                            app, action, _settings, auto_installed, progress)
                    else:
                        host_result[app.id] = self._run_remote(
                            host, app, action, auto_installed, _settings,
                            progress)[app.id]
                    if not host_result[app.id]['success']:
                        break
        return ret

    def _run_local_dry_run(self, apps, action, settings, progress):
        if action == 'upgrade':
            apps = [Apps().find_candidate(app) or app for app in apps]
        if len(apps) == 1:
            progress.title = _('%s: Running tests') % apps[0].name
        else:
            progress.title = _('%d Apps: Running tests') % len(apps)
        ret = {}
        ret['errors'], ret['warnings'] = check(apps, action)
        ret['errors'].pop('must_have_no_unmet_dependencies',
                          None)  # has to be resolved prior to this call!
        action = get_action(action)()
        ret['packages'] = {}
        for app in apps:
            args = action._build_namespace(
                app=[app],
                dry_run=True,
                install_master_packages_remotely=False,
                only_master_packages=False)
            result = action.dry_run(app, args)
            if result is not None:
                ret['packages'][app.id] = result
        return ret

    def _run_local(self, app, action, settings, auto_installed, progress):
        kwargs = {
            'noninteractive':
            True,
            'auto_installed':
            auto_installed,
            'skip_checks': [
                'shall_have_enough_ram',
                'shall_only_be_installed_in_ad_env_with_password_service',
                'must_not_have_concurrent_operation'
            ],
        }
        if settings.get(app.id):
            kwargs['set_vars'] = settings[app.id]
        if action == 'install':
            progress.title = _('Installing %s') % (app.name, )
        elif action == 'remove':
            progress.title = _('Uninstalling %s') % (app.name, )
        elif action == 'upgrade':
            progress.title = _('Upgrading %s') % (app.name, )
        action = get_action(action)
        handler = UMCProgressHandler(progress)
        handler.setLevel(logging.INFO)
        action.logger.addHandler(handler)
        try:
            package_manager = get_package_manager()
            with package_manager.no_umc_restart(exclude_apache=True):
                success = action.call(app=[app],
                                      username=self.username,
                                      password=self.password,
                                      **kwargs)
                return {'success': success}
        except AppCenterError as exc:
            raise umcm.UMC_Error(str(exc),
                                 result=dict(display_feedback=True,
                                             title='%s %s' %
                                             (exc.title, exc.info)))
        finally:
            action.logger.removeHandler(handler)

    def _run_remote_dry_run(self, host, apps, action, auto_installed, settings,
                            progress):
        return self._run_remote_logic(host,
                                      apps,
                                      action,
                                      auto_installed,
                                      settings,
                                      progress,
                                      dry_run=True)

    def _run_remote(self, host, app, action, auto_installed, settings,
                    progress):
        return self._run_remote_logic(host, [app],
                                      action,
                                      auto_installed,
                                      settings,
                                      progress,
                                      dry_run=False)

    def _run_remote_logic(self, host, apps, action, auto_installed, settings,
                          progress, dry_run):
        if len(apps) == 1:
            progress.title = _('%s: Connecting to %s') % (apps[0].name, host)
        else:
            progress.title = _('%d Apps: Connecting to %s') % (len(apps), host)
        client = self._remote_appcenter(host, function='appcenter/run')
        opts = {
            'apps': [str(app) for app in apps],
            'auto_installed': auto_installed,
            'action': action,
            'hosts': {
                host: [app.id for app in apps]
            },
            'settings': settings,
            'dry_run': dry_run
        }
        progress_id = client.umc_command('appcenter/run', opts).result['id']
        while True:
            result = client.umc_command('appcenter/progress', {
                'progress_id': progress_id
            }).result
            if result['finished']:
                return result['result'][host]
            progress.title = result['title']
            progress.intermediate.extend(result['intermediate'])
            progress.message = result['message']
            time.sleep(result['retry_after'] / 1000.0)

    @simple_response
    def query(self, quick=False):
        query_cache_file = '/var/cache/univention-appcenter/umc-query.json'
        if quick:
            try:
                with open(query_cache_file) as fd:
                    return json.load(fd)
            except (EnvironmentError, ValueError) as exc:
                MODULE.error('Error returning cached query: %s' % exc)
                return []
        self.update_applications()
        self.ucr.load()
        reload_package_manager()
        list_apps = get_action('list')
        domain = get_action('domain')
        apps = list_apps.get_apps()
        if self.ucr.is_true('appcenter/docker', True):
            if not self._test_for_docker_service():
                raise umcm.UMC_Error(
                    _('The docker service is not running! The App Center will not work properly.'
                      ) + ' ' +
                    _('Make sure docker.io is installed, try starting the service with "service docker start".'
                      ))
        info = domain.to_dict(apps)
        with open(query_cache_file, 'w') as fd:
            json.dump(info, fd)
        return info

    def update_applications(self):
        if self.ucr.is_true('appcenter/umc/update/always', True):
            update = get_action('update')
            try:
                update.call()
            except NetworkError as err:
                raise umcm.UMC_Error(str(err))
            except Abort:
                pass
            self.update_applications_done = True

    def _test_for_docker_service(self):
        if docker_bridge_network_conflict():
            msg = _(
                'A conflict between the system network settings and the docker bridge default network has been detected.'
            ) + '\n\n'
            msg += _(
                'Please either configure a different network for the docker bridge by setting the UCR variable docker/daemon/default/opts/bip to a different network and restart the system,'
            ) + ' '
            msg += _(
                'or disable the docker support in the AppCenter by setting appcenter/docker to false.'
            )
            raise umcm.UMC_Error(msg)
        if not docker_is_running():
            MODULE.warn('Docker is not running! Trying to start it now...')
            call_process(['invoke-rc.d', 'docker', 'start'])
            if not docker_is_running():
                return False
        return True

    @simple_response
    def suggestions(self, version):
        try:
            cache = AppCenterCache.build(server=default_server())
            cache_file = cache.get_cache_file('.suggestions.json')
            with open(cache_file) as fd:
                json = load(fd)
        except (EnvironmentError, ValueError):
            raise umcm.UMC_Error(_('Could not load suggestions.'))
        else:
            try:
                return json[version]
            except (KeyError, AttributeError):
                raise umcm.UMC_Error(_('Unexpected suggestions data.'))

    @simple_response
    def enable_docker(self):
        if self._test_for_docker_service():
            ucr_save({'appcenter/docker': 'enabled'})
        else:
            raise umcm.UMC_Error(
                _('Unable to start the docker service!') + ' ' +
                _('Make sure docker.io is installed, try starting the service with "service docker start".'
                  ))

    @require_apps_update
    @require_password
    @simple_response(with_progress=True)
    def sync_ldap(self):
        register = get_action('register')
        register.call(username=self.username, password=self.password)

    # used in updater-umc
    @simple_response
    def get_by_component_id(self, component_id):
        domain = get_action('domain')
        if isinstance(component_id, list):
            requested_apps = [
                Apps().find_by_component_id(cid) for cid in component_id
            ]
            return domain.to_dict(requested_apps)
        else:
            app = Apps().find_by_component_id(component_id)
            if app:
                return domain.to_dict([app])[0]
            else:
                raise umcm.UMC_Error(
                    _('Could not find an application for %s') % component_id)

    # used in updater-umc
    @simple_response
    def app_updates(self):
        upgrade = get_action('upgrade')
        domain = get_action('domain')
        return domain.to_dict(list(upgrade.iter_upgradable_apps()))

    @sanitize(application=StringSanitizer(minimum=1, required=True))
    @simple_response
    def get(self, application):
        list_apps = get_action('list')
        domain = get_action('domain')
        apps = list_apps.get_apps()
        for app in apps:
            if app.id == application:
                break
        else:
            app = None
        if app is None:
            raise umcm.UMC_Error(
                _('Could not find an application for %s') % (application, ))
        return domain.to_dict([app])[0]

    @sanitize(app=AppSanitizer(required=True))
    @simple_response
    def config(self, app, phase):
        self.ucr.load()
        return self._get_config(app, phase)

    def _get_config(self, app, phase):
        autostart = self.ucr.get('%s/autostart' % app.id, 'yes')
        is_running = app_is_running(app)
        values = {}
        for setting in app.get_settings():
            if phase in setting.show or phase in setting.show_read_only:
                value = setting.get_value(app, phase)
                if isinstance(setting, FileSetting) and not isinstance(
                        setting, PasswordFileSetting):
                    if value:
                        value = b64encode(
                            value.encode('utf-8')).decode('ascii')
                values[setting.name] = value
        return {
            'autostart': autostart,
            'is_running': is_running,
            'values': values,
        }

    @sanitize(app=AppSanitizer(required=True), values=DictSanitizer({}))
    @simple_response(with_progress=True)
    def configure(self, progress, app, values, autostart=None):
        for setting in app.get_settings():
            if isinstance(setting, FileSetting) and not isinstance(
                    setting, PasswordFileSetting):
                if values.get(setting.name):
                    values[setting.name] = b64decode(
                        values[setting.name]).decode('utf-8')
        configure = get_action('configure')
        handler = UMCProgressHandler(progress)
        handler.setLevel(logging.INFO)
        configure.logger.addHandler(handler)
        try:
            return configure.call(app=app,
                                  set_vars=values,
                                  autostart=autostart)
        finally:
            configure.logger.removeHandler(handler)

    @sanitize(app=AppSanitizer(required=True),
              mode=ChoicesSanitizer(['start', 'stop']))
    @simple_response
    def app_service(self, app, mode):
        service = get_action(mode)
        service.call(app=app)

    @sanitize(app=AppSanitizer(required=False),
              action=ChoicesSanitizer(['get', 'buy', 'search', 'vote']),
              value=StringSanitizer())
    @simple_response
    def track(self, app, action, value):
        send_information(action, app=app, value=value)

    @contextmanager
    def locked(self):
        try:
            if self._working():
                raise LockError()
            with package_lock():
                yield
        except LockError:
            raise umcm.UMC_Error(_('Another package operation is in progress'))

    def _install_master_packages_on_hosts(self, app, function):
        if function.startswith('upgrade'):
            remote_function = 'update-schema'
        else:
            remote_function = 'install-schema'
        master_packages = app.default_packages_master
        if not master_packages:
            return
        hosts = find_hosts_for_master_packages()
        all_hosts_count = len(hosts)
        package_manager = get_package_manager()
        package_manager.set_max_steps(
            all_hosts_count * 200)  # up to 50% if all hosts are installed
        # maybe we already installed local packages (on master)
        if self.ucr.get('server/role') == 'domaincontroller_master':
            # TODO: set_max_steps should reset _start_steps. need function like set_start_steps()
            package_manager.progress_state._start_steps = all_hosts_count * 100
        for host, host_is_master in hosts:
            package_manager.progress_state.info(
                _('Installing LDAP packages on %s') % host)
            try:
                if not self._install_master_packages_on_host(
                        app, remote_function, host):
                    error_message = 'Unable to install %r on %s. Check /var/log/univention/management-console-module-appcenter.log on the host and this server. All errata updates have been installed on %s?' % (
                        master_packages, host, host)
                    raise Exception(error_message)
            except Exception as e:
                MODULE.error('%s: %s' % (host, e))
                if host_is_master:
                    role = 'Primary Directory Node'
                else:
                    role = 'Backup Directory Node'
                # ATTENTION: This message is not localised. It is parsed by the frontend to markup this message! If you change this message, be sure to do the same in AppCenterPage.js
                package_manager.progress_state.error(
                    'Installing extension of LDAP schema for %s seems to have failed on %s %s'
                    % (app.component_id, role, host))
                if host_is_master:
                    raise  # only if host_is_master!
            finally:
                package_manager.add_hundred_percent()

    def _install_master_packages_on_host(self, app, function, host):
        client = Client(host, self.username, self.password)
        result = client.umc_command(
            'appcenter/invoke', {
                'function': function,
                'application': app.id,
                'force': True,
                'dont_remote_install': True
            }).result
        if result['can_continue']:
            all_errors = self._query_remote_progress(client)
            return len(all_errors) == 0
        else:
            MODULE.warn('%r' % result)
            return False

    def _install_dry_run_remote(self, app, function, dont_remote_install,
                                force):
        MODULE.process('Invoke install_dry_run_remote')
        self.ucr.load()
        if function.startswith('upgrade'):
            remote_function = 'update-schema'
        else:
            remote_function = 'install-schema'

        master_packages = app.default_packages_master

        # connect to Primary/Backup Nodes
        unreachable = []
        hosts_info = {}
        remote_info = {
            'master_unreachable': False,
            'problems_with_hosts': False,
            'serious_problems_with_hosts': False,
        }
        dry_run_threads = []
        info = get_action('info')
        if master_packages and not dont_remote_install:
            hosts = find_hosts_for_master_packages()

            # checking remote host is I/O heavy, so use threads
            #   "global" variables: unreachable, hosts_info, remote_info

            def _check_remote_host(app_id, host, host_is_master, username,
                                   password, force, remote_function):
                MODULE.process('Starting dry_run for %s on %s' %
                               (app_id, host))
                MODULE.process('%s: Connecting...' % host)
                try:
                    client = Client(host, username, password)
                except (ConnectionError, HTTPError) as exc:
                    MODULE.warn('_check_remote_host: %s: %s' % (host, exc))
                    unreachable.append(host)
                    if host_is_master:
                        remote_info['master_unreachable'] = True
                else:
                    MODULE.process('%s: ... done' % host)
                    host_info = {}
                    MODULE.process('%s: Getting version...' % host)
                    try:
                        host_version = client.umc_command(
                            'appcenter/version', {
                                'version': info.get_compatibility()
                            }).result
                    except Forbidden:
                        # command is not yet known (older app center)
                        MODULE.process('%s: ... forbidden!' % host)
                        host_version = None
                    except (ConnectionError, HTTPError) as exc:
                        MODULE.warn('%s: Could not get appcenter/version: %s' %
                                    (host, exc))
                        raise
                    except Exception as exc:
                        MODULE.error('%s: Exception: %s' % (host, exc))
                        raise
                    MODULE.process('%s: ... done' % host)
                    host_info['compatible_version'] = info.is_compatible(
                        host_version)
                    MODULE.process('%s: Invoking %s ...' %
                                   (host, remote_function))
                    try:
                        host_info['result'] = client.umc_command(
                            'appcenter/invoke_dry_run', {
                                'function': remote_function,
                                'application': app_id,
                                'force': force,
                                'dont_remote_install': True,
                            }).result
                    except Forbidden:
                        # command is not yet known (older app center)
                        MODULE.process('%s: ... forbidden!' % host)
                        host_info['result'] = {
                            'can_continue': False,
                            'serious_problems': False
                        }
                    except (ConnectionError, HTTPError) as exc:
                        MODULE.warn('Could not get appcenter/version: %s' %
                                    (exc, ))
                        raise
                    MODULE.process('%s: ... done' % host)
                    if not host_info['compatible_version'] or not host_info[
                            'result']['can_continue']:
                        remote_info['problems_with_hosts'] = True
                        if host_info['result'][
                                'serious_problems'] or not host_info[
                                    'compatible_version']:
                            remote_info['serious_problems_with_hosts'] = True
                    hosts_info[host] = host_info
                MODULE.process('Finished dry_run for %s on %s' %
                               (app_id, host))

            for host, host_is_master in hosts:
                thread = Thread(target=_check_remote_host,
                                args=(app.id, host, host_is_master,
                                      self.username, self.password, force,
                                      remote_function))
                thread.start()
                dry_run_threads.append(thread)

        result = {}

        for thread in dry_run_threads:
            thread.join()
        MODULE.process('All %d threads finished' % (len(dry_run_threads)))

        result['unreachable'] = unreachable
        result['hosts_info'] = hosts_info
        result.update(remote_info)
        return result

    def _query_remote_progress(self, client):
        all_errors = set()
        number_failures = 0
        number_failures_max = 20
        host = client.hostname
        while True:
            try:
                result = client.umc_command('appcenter/progress').result
            except (ConnectionError, HTTPError) as exc:
                MODULE.warn('%s: appcenter/progress returned an error: %s' %
                            (host, exc))
                number_failures += 1
                if number_failures >= number_failures_max:
                    MODULE.error(
                        '%s: Remote App Center cannot be contacted for more than %d seconds. Maybe just a long Apache Restart? Presume failure! Check logs on remote machine, maybe installation was successful.'
                        % number_failures_max)
                    return False
                time.sleep(1)
                continue
            else:
                # everything okay. reset "timeout"
                number_failures = 0
            MODULE.info('Result from %s: %r' % (host, result))
            info = result['info']
            steps = result['steps']
            errors = ['%s: %s' % (host, error) for error in result['errors']]
            if info:
                self.package_manager.progress_state.info(info)
            if steps:
                steps = float(
                    steps
                )  # bug in package_manager in 3.1-0: int will result in 0 because of division and steps < max_steps
                self.package_manager.progress_state.percentage(steps)
            for error in errors:
                if error not in all_errors:
                    self.package_manager.progress_state.error(error)
                    all_errors.add(error)
            if result['finished'] is True:
                break
            time.sleep(0.1)
        return all_errors

    def keep_alive(self, request):
        ''' Fix for Bug #30611: UMC kills appcenter module
		if no request is sent for $(ucr get umc/module/timeout).
		this happens if a user logs out during a very long installation.
		this function will be run by the frontend to always have one connection open
		to prevent killing the module. '''
        def _thread():
            while self._working():
                time.sleep(1)

        def _finished(thread, result):
            success = not isinstance(result, BaseException)
            if not success:
                MODULE.warn('Exception during keep_alive: %s' % result)
            self.finished(request.id, success)

        thread = notifier.threads.Simple('keep_alive',
                                         notifier.Callback(_thread), _finished)
        thread.run()

    @simple_response
    def ping(self):
        return True

    @simple_response
    def buy(self, application):
        app = Apps().find(application)
        if not app or not app.shop_url:
            return None
        ret = {}
        ret['key_id'] = self.ucr.get('license/uuid')
        ret['ucs_version'] = self.ucr.get('version/version')
        ret['app_id'] = app.id
        ret['app_version'] = app.version
        # ret['locale'] = locale.getlocale()[0] # done by frontend
        ret['user_count'] = None  # FIXME: get users and computers from license
        ret['computer_count'] = None
        return ret

    @simple_response
    def enable_disable_app(self, application, enable=True):
        app = Apps().find(application)
        if not app:
            return
        stall = get_action('stall')
        stall.call(app=app, undo=enable)

    @simple_response
    def packages_sections(self):
        """ fills the 'sections' combobox in the search form """

        sections = set()
        cache = apt.Cache()
        for package in cache:
            sections.add(package.section)

        return sorted(sections)

    @sanitize(pattern=PatternSanitizer(required=True))
    @simple_response
    def packages_query(self, pattern, section='all', key='package'):
        """ Query to fill the grid. Structure is fixed here. """
        result = []
        for package in self.package_manager.packages(reopen=True):
            if section == 'all' or package.section == section:
                toshow = False
                if pattern.pattern == '^.*$':
                    toshow = True
                elif key == 'package' and pattern.search(package.name):
                    toshow = True
                elif key == 'description' and package.candidate and pattern.search(
                        package.candidate.raw_description):
                    toshow = True
                if toshow:
                    result.append(self._package_to_dict(package, full=False))
        return result

    @simple_response
    def packages_get(self, package):
        """ retrieves full properties of one package """

        package = self.package_manager.get_package(package)
        if package is not None:
            return self._package_to_dict(package, full=True)
        else:
            # TODO: 404?
            return {}

    @sanitize(function=MappingSanitizer(
        {
            'install': 'install',
            'upgrade': 'install',
            'uninstall': 'remove',
        },
        required=True),
              packages=ListSanitizer(StringSanitizer(minimum=1),
                                     required=True),
              update=BooleanSanitizer())
    @simple_response
    def packages_invoke_dry_run(self, packages, function, update):
        if update:
            self.package_manager.update()
        packages = self.package_manager.get_packages(packages)
        kwargs = {'install': [], 'remove': [], 'dry_run': True}
        if function == 'install':
            kwargs['install'] = packages
        else:
            kwargs['remove'] = packages
        return dict(
            zip(['install', 'remove', 'broken'],
                self.package_manager.mark(**kwargs)))

    @sanitize(function=MappingSanitizer(
        {
            'install': 'install',
            'upgrade': 'install',
            'uninstall': 'remove',
        },
        required=True),
              packages=ListSanitizer(StringSanitizer(minimum=1),
                                     required=True))
    def packages_invoke(self, request):
        """ executes an installer action """
        packages = request.options.get('packages')
        function = request.options.get('function')

        try:
            if self._working():
                # make it multi-tab safe (same session many buttons to be clicked)
                raise LockError()
            with self.package_manager.locked(reset_status=True):
                not_found = [
                    pkg_name for pkg_name in packages
                    if self.package_manager.get_package(pkg_name) is None
                ]
                self.finished(request.id, {'not_found': not_found})

                if not not_found:

                    def _thread(package_manager, function, packages):
                        with package_manager.locked(set_finished=True):
                            with package_manager.no_umc_restart(
                                    exclude_apache=True):
                                if function == 'install':
                                    package_manager.install(*packages)
                                else:
                                    package_manager.uninstall(*packages)

                    def _finished(thread, result):
                        if isinstance(result, BaseException):
                            MODULE.warn('Exception during %s %s: %r' %
                                        (function, packages, str(result)))

                    thread = notifier.threads.Simple(
                        'invoke',
                        notifier.Callback(_thread, self.package_manager,
                                          function, packages), _finished)
                    thread.run()
                else:
                    self.package_manager.set_finished(
                    )  # nothing to do, ready to take new commands
        except LockError:
            # make it thread safe: another process started a package manager
            # this module instance already has a running package manager
            raise umcm.UMC_Error(_('Another package operation is in progress'))

    @contextmanager
    def is_working(self):
        self._is_working = True
        yield
        self._is_working = False

    def _working(self):
        return self._is_working or os.path.exists(
            LOCK_FILE) or not self.package_manager.progress_state._finished

    @simple_response
    def working(self):
        # TODO: PackageManager needs is_idle() or something
        #   preferably the package_manager can tell what is currently executed:
        #   package_manager.is_working() => False or _('Installing PKG')
        return self._working()

    @simple_response
    def custom_progress(self):
        timeout = 5
        ret = self.package_manager.poll(timeout)
        ret['finished'] = not self._working()
        return ret

    def _package_to_dict(self, package, full):
        """ Helper that extracts properties from a 'apt_pkg.Package' object
			and stores them into a dictionary. Depending on the 'full'
			switch, stores only limited (for grid display) or full
			(for detail view) set of properties.
		"""
        installed = package.installed  # may be None
        found = True
        candidate = package.candidate
        found = candidate is not None
        if not found:
            candidate = NoneCandidate()

        result = {
            'package': package.name,
            'installed': package.is_installed,
            'upgradable': package.is_upgradable and found,
            'summary': candidate.summary,
        }

        # add (and translate) a combined status field
        # *** NOTE *** we translate it here: if we would use the Custom Formatter
        #		of the grid then clicking on the sort header would not work.
        if package.is_installed:
            if package.is_upgradable:
                result['status'] = _('upgradable')
            else:
                result['status'] = _('installed')
        else:
            result['status'] = _('not installed')

        # additional fields needed for detail view
        if full:
            # Some fields differ depending on whether the package is installed or not:
            if package.is_installed:
                result['section'] = installed.section
                result['priority'] = installed.priority or ''
                result['summary'] = installed.summary  # take the current one
                result['description'] = installed.description
                result['installed_version'] = installed.version
                result['size'] = installed.installed_size
                if package.is_upgradable:
                    result['candidate_version'] = candidate.version
            else:
                del result[
                    'upgradable']  # not installed: don't show 'upgradable' at all
                result['section'] = candidate.section
                result['priority'] = candidate.priority or ''
                result['description'] = candidate.description
                result['size'] = candidate.installed_size
                result['candidate_version'] = candidate.version
            # format size to handle bytes
            size = result['size']
            byte_mods = ['B', 'kB', 'MB']
            for byte_mod in byte_mods:
                if size < 10000:
                    break
                size = float(size) / 1000  # MB, not MiB
            else:
                size = size * 1000  # once too often
            if size == int(size):
                format_string = '%d %s'
            else:
                format_string = '%.2f %s'
            result['size'] = format_string % (size, byte_mod)

        return result

    @simple_response
    def components_query(self):
        """	Returns components list for the grid in the ComponentsPage.
		"""
        # be as current as possible.
        self.get_updater().ucr_reinit()
        self.ucr.load()

        return [
            self.get_component_manager().component(comp.name)
            for comp in self.get_updater().get_components(all=True)
        ]

    @sanitize_list(StringSanitizer())
    @multi_response(single_values=True)
    def components_get(self, iterator, component_id):
        # be as current as possible.
        self.get_updater().ucr_reinit()
        self.ucr.load()
        for component_id in iterator:
            yield self.get_component_manager().component(component_id)

    @sanitize_list(DictSanitizer({'object': advanced_components_sanitizer}))
    @multi_response
    def components_put(self, iterator, object):
        """Writes back one or more component definitions.
		"""
        # umc.widgets.Form wraps the real data into an array:
        #
        #	[
        #		{
        #			'object' : { ... a dict with the real data .. },
        #			'options': None
        #		},
        #		... more such entries ...
        #	]
        #
        # Current approach is to return a similarly structured array,
        # filled with elements, each one corresponding to one array
        # element of the request:
        #
        #	[
        #		{
        #			'status'	:	a number where 0 stands for success, anything else
        #							is an error code
        #			'message'	:	a result message
        #			'object'	:	a dict of field -> error message mapping, allows
        #							the form to show detailed error information
        #		},
        #		... more such entries ...
        #	]
        with set_save_commit_load(self.ucr) as super_ucr:
            for object, in iterator:
                yield self.get_component_manager().put(object, super_ucr)
        self.package_manager.update()

    # do the same as components_put (update)
    # but don't allow adding an already existing entry
    components_add = sanitize_list(
        DictSanitizer({'object': add_components_sanitizer}))(components_put)
    components_add.__name__ = 'components_add'

    @sanitize_list(StringSanitizer())
    @multi_response(single_values=True)
    def components_del(self, iterator, component_id):
        for component_id in iterator:
            yield self.get_component_manager().remove(component_id)
        self.package_manager.update()

    @multi_response
    def settings_get(self, iterator):
        # *** IMPORTANT *** Our UCR copy must always be current. This is not only
        #	to catch up changes made via other channels (ucr command line etc),
        #	but also to reflect the changes we have made ourselves!
        self.ucr.load()

        for _ in iterator:
            yield {
                'unmaintained':
                self.ucr.is_true('repository/online/unmaintained', False),
                'server':
                self.ucr.get('repository/online/server', ''),
                'prefix':
                self.ucr.get('repository/online/prefix', ''),
            }

    @sanitize_list(
        DictSanitizer({'object': basic_components_sanitizer}),
        min_elements=1,
        max_elements=1  # moduleStore with one element...
    )
    @multi_response
    def settings_put(self, iterator, object):
        # FIXME: returns values although it should yield (multi_response)
        changed = False
        # Set values into our UCR copy.
        try:
            with set_save_commit_load(self.ucr) as super_ucr:
                for object, in iterator:
                    for key, value in object.items():
                        MODULE.info(
                            "   ++ Setting new value for '%s' to '%s'" %
                            (key, value))
                        super_ucr.set_registry_var(
                            '%s/%s' % (ONLINE_BASE, key), value)
                changed = super_ucr.changed()
        except Exception as e:
            MODULE.warn("   !! Writing UCR failed: %s" % str(e))
            return [{'message': str(e), 'status': PUT_WRITE_ERROR}]

        self.package_manager.update()

        # Bug #24878: emit a warning if repository is not reachable
        try:
            updater = self.get_updater()
            for line in updater.print_version_repositories().split('\n'):
                if line.strip():
                    break
            else:
                raise ConfigurationError()
        except ConfigurationError:
            msg = _(
                "There is no repository at this server (or at least none for the current UCS version)"
            )
            MODULE.warn("   !! Updater error: %s" % msg)
            response = {'message': msg, 'status': PUT_UPDATER_ERROR}
            # if nothing was committed, we want a different type of error code,
            # just to appropriately inform the user
            if changed:
                response['status'] = PUT_UPDATER_NOREPOS
            return [response]
        except BaseException as ex:
            MODULE.warn("   !! Updater error: %s" % (ex, ))
            return [{'message': str(ex), 'status': PUT_UPDATER_ERROR}]
        return [{'status': PUT_SUCCESS}]
コード例 #7
0
class Instance(Base):
    def init(self):
        # set the language in order to return the correctly localized labels/descriptions
        uit.set_language(self.locale.language)

    def __create_variable_info(self, options):
        all_info = ConfigRegistryInfo(registered_only=False)
        info = ConfigRegistryInfo(install_mode=True)
        info.read_customized()
        var = Variable()

        # description
        for line in options['descriptions']:
            text = line['text']
            if not text:
                continue
            if 'lang' in line:
                var['description[%s]' % line['lang']] = text
            else:
                var['description'] = text
        # categories
        if options['categories']:
            var['categories'] = ','.join(options['categories'])

        # type
        var['type'] = options['type']

        # are there any modifications?
        old_value = all_info.get_variable(options['key'])
        if old_value != var:
            # save
            info.add_variable(options['key'], var)
            info.write_customized()

    def is_readonly(self, key):
        ucrinfo_system = ConfigRegistryInfo(registered_only=False,
                                            load_customized=False)
        var = ucrinfo_system.get_variable(key)
        if var:
            return var.get('readonly') in ('yes', '1', 'true')
        return False

    @sanitize(
        DictSanitizer({
            'object':
            DictSanitizer({
                'key': UCRKeySanitizer(required=True),
                'value': StringSanitizer(default=''),
            })
        }))
    def add(self, request):
        # does the same as put
        ucr.load()
        already_set = set(ucr.keys()) & set(v['object']['key']
                                            for v in request.options)
        if already_set:
            raise UMC_Error(
                _('The UCR variable %s is already set.') %
                ('", "'.join(already_set)))

        self.put(request)

    @sanitize(
        DictSanitizer({
            'object':
            DictSanitizer({
                'key': UCRKeySanitizer(required=True),
                'value': StringSanitizer(default=''),
            })
        }))
    def put(self, request):
        for _var in request.options:
            var = _var['object']
            value = var['value'] or ''
            key = var['key']
            if self.is_readonly(key):
                raise UMC_Error(
                    _('The UCR variable %s is read-only and can not be changed!'
                      ) % (key, ))
            arg = ['%s=%s' % (key.encode(), value.encode())]
            handler_set(arg)

            # handle descriptions, type, and categories
            if 'descriptions' in var or 'type' in var or 'categories' in var:
                self.__create_variable_info(var)
        self.finished(request.id, True)

    def remove(self, request):
        variables = filter(lambda x: x is not None,
                           map(lambda x: x.get('object'), request.options))
        for var in variables:
            if self.is_readonly(var):
                raise UMC_Error(
                    _('The UCR variable %s is read-only and can not be removed!'
                      ) % (var, ))

        handler_unset(variables)
        self.finished(request.id, True)

    def get(self, request):
        ucrReg = ConfigRegistry()
        ucrReg.load()
        ucrInfo = ConfigRegistryInfo(registered_only=False)

        # iterate over all requested variables
        results = []
        for key in request.options:
            info = ucrInfo.get_variable(str(key))
            value = ucrReg.get(str(key))
            if not info and (value or '' == value):
                # only the value available
                results.append({'key': key, 'value': value})
            elif info:
                # info (categories etc.) available
                info['value'] = value
                info['key'] = key
                results.append(info.normalize())
            else:
                # variable not available, request failed
                raise UMC_Error(
                    _('The UCR variable %(key)s could not be found') %
                    {'key': key})
        self.finished(request.id, results)

    def categories(self, request):
        ucrInfo = ConfigRegistryInfo(registered_only=False)
        categories = []
        for id, obj in ucrInfo.categories.iteritems():
            name = obj['name']
            if ucrInfo.get_variables(id):
                categories.append({'id': id, 'label': name})
        self.finished(request.id, categories)

    @sanitize(pattern=PatternSanitizer(default='.*'),
              key=ChoicesSanitizer(['all', 'key', 'value', 'description'],
                                   required=True))
    @simple_response
    def query(self, pattern, key, category=None):
        '''Returns a dictionary of configuration registry variables
		found by searching for the (wildcard) expression defined by the
		UMCP request. Additionally a list of configuration registry
		categories can be defined.

		The dictionary returned is compatible with the Dojo data store
		format.'''
        variables = []
        if category == 'all':
            # load _all_ config registry variables
            base_info = ConfigRegistryInfo(registered_only=False)
        else:
            # load _all registered_ config registry variables
            base_info = ConfigRegistryInfo()

        if category in ('all', 'all-registered'):
            category = None

        def _match_value(name, var):
            return var.value and pattern.match(var.value)

        def _match_key(name, var):
            return pattern.match(name)

        def _match_description(name, var):
            descr = var.get('description')
            return descr and pattern.match(descr)

        def _match_all(name, var):
            return _match_value(name, var) or _match_description(
                name, var) or _match_key(name, var)

        func = locals().get('_match_%s' % key)
        for name, var in base_info.get_variables(category).iteritems():
            if func(name, var):
                variables.append({
                    'key': name,
                    'value': var.value,
                    'description': var.get('description', None),
                })

        return variables
コード例 #8
0
class Commands(object):
    @sanitize(
        partitionDevice=StringSanitizer(required=True),
        filter=PatternSanitizer(default='.*'),
    )
    def users_query(self, request):
        partitionDevice = request.options['partitionDevice']
        self._check_error(request, partitionDevice)

        callback = notifier.Callback(self._users_query, partitionDevice,
                                     request)
        tools.repquota(request.options['partitionDevice'], callback)

    def _users_query(self, pid, status, callbackResult, partition, request):
        '''This function is invoked when a repquota process has died and
		there is output to parse that is restructured as UMC Dialog'''
        if status != 0:
            MODULE.warn('repquota failed with exit code: %s' % (status, ))
        # general information
        devs = fstab.File()
        devs.find(spec=partition)

        # skip header
        header = 0
        try:
            while not callbackResult[header].startswith(b'----'):
                header += 1
        except IndexError:
            pass
        output = [
            x.decode('UTF-8', 'replace') for x in callbackResult[header + 1:]
        ]
        quotas = tools.repquota_parse(partition, output)
        result = [
            q for q in quotas if request.options['filter'].match(q['user'])
        ]
        self.finished(request.id, result)

    @sanitize(
        partitionDevice=StringSanitizer(required=True),
        user=StringSanitizer(required=True),
        sizeLimitSoft=LimitSanitizer(default=0, required=True),
        sizeLimitHard=LimitSanitizer(default=0, required=True),
        fileLimitSoft=LimitSanitizer(default=0, required=True),
        fileLimitHard=LimitSanitizer(default=0, required=True),
    )
    def users_set(self, request):
        def _thread(request):
            partition = request.options['partitionDevice']
            user = request.options['user']
            if not isinstance(user, str):  # Py2
                user = user.encode('utf-8')

            size_soft = request.options['sizeLimitSoft']
            size_hard = request.options['sizeLimitHard']
            file_soft = request.options['fileLimitSoft']
            file_hard = request.options['fileLimitHard']
            self._check_error(request, partition)

            if tools.setquota(partition, user, tools.byte2block(size_soft),
                              tools.byte2block(size_hard), file_soft,
                              file_hard):
                raise UMC_Error(
                    _('Failed to modify quota settings for user %(user)s on partition %(partition)s.'
                      ) % {
                          'user': user,
                          'partition': partition
                      })

        thread = notifier.threads.Simple(
            'Set', notifier.Callback(_thread, request),
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    def users_remove(self, request):
        def _thread(request):
            partitions = []
            failed = []

            # Determine different partitions
            for obj in request.options:
                partitions.append(obj['object'].split('@', 1)[-1])
            for partition in set(partitions):
                self._check_error(request, partition)

            # Remove user quota
            for obj in request.options:
                (user, _, partition) = obj['object'].partition('@')
                if not isinstance(user, str):  # Py2
                    user = user.encode('utf-8')
                if tools.setquota(partition, user, 0, 0, 0, 0):
                    failed.append(user)

            if failed:
                raise UMC_Error(
                    _('Could not remove the following user: %s') %
                    ', '.join(failed))

        thread = notifier.threads.Simple(
            'Remove', notifier.Callback(_thread, request),
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    def _check_error(self, request, partition_name):
        try:
            fs = fstab.File('/etc/fstab')
            mt = fstab.File('/etc/mtab')
        except IOError as error:
            MODULE.error('Could not open %s' % error.filename)
            raise UMC_Error(_('Could not open %s') % error.filename, 500)

        partition = fs.find(spec=partition_name)
        if partition:
            mounted_partition = mt.find(spec=partition.spec)
            if mounted_partition:
                if not mounted_partition.hasopt(
                        'usrquota') and not mounted_partition.hasopt(
                            'usrjquota=aquota.user'):
                    raise UMC_Error(
                        _('The following partition is mounted without quota support: %s'
                          ) % partition_name)
            else:
                raise UMC_Error(
                    _('The following partition is currently not mounted: %s') %
                    partition_name)
        else:
            raise UMC_Error(_('No partition found (%s)') % partition_name)
コード例 #9
0
class Instance(Base):
    def init(self):
        self._hostname = ucr.get('hostname')

    @sanitize(pattern=PatternSanitizer(default='.*'),
              key=ChoicesSanitizer(
                  choices=['printer', 'description', 'location'],
                  required=True))
    @simple_response
    def list_printers(self, key, pattern):
        """ Lists the printers for the overview grid. """

        quota = self._quota_enabled()  # we need it later

        result = []
        plist = self._list_printers()
        for element in plist:
            printer = element['printer']
            data = self._printer_details(printer)
            for field in data:
                element[field] = data[field]
            # filter according to query
            if pattern.match(element[key]):
                if printer in quota:
                    element['quota'] = quota[printer]
                else:
                    element['quota'] = False
                result.append(element)

        return result

    @simple_response
    @log
    def get_printer(self, printer=''):
        """ gets detail data for one printer. """

        result = self._printer_details(printer)
        result['printer'] = printer
        result['status'] = self._printer_status(printer)
        result['quota'] = self._quota_enabled(printer)
        return result

    @simple_response
    def list_jobs(self, printer=''):
        """ returns list of jobs for one printer. """

        return self._job_list(printer)

    @simple_response
    def list_quota(self, printer=''):
        """ lists all quota entries related to this printer. """

        result = []
        status = None

        try:
            from pykota.tool import PyKotaTool
            from pykota import reporter
            from pykota.storages.pgstorage import PGError
        except ImportError:
            raise UMC_Error(
                _('The print quota settings are currently disabled. Please install the package univention-printquota to enable them.'
                  ))

        reportTool = PyKotaTool()
        try:
            reportTool.deferredInit()
            printers = reportTool.storage.getMatchingPrinters(printer)
            reportingtool = reporter.openReporter(reportTool, 'html', printers,
                                                  '*', 0)
            status = reportingtool.generateReport()
        except PGError as exc:
            MODULE.error('Cannot connect to postgres: %s' % (exc, ))
            raise UMC_Error(
                _('The connection to the print quota postgres database failed. Please make sure the postgres service is running and reachable.'
                  ))
        finally:
            reportTool.regainPriv()

        if status:
            tree = lxml.html.fromstring(status)
            table = tree.find_class('pykotatable')
            for i in table:
                for a in i.iterchildren(tag='tr'):
                    data = list()
                    for b in a.iterchildren(tag='td'):
                        data.append(b.text_content().strip())
                    if data and len(data) >= 11:
                        user = data[0]
                        # limitby = data[1]
                        # overcharge = data[2]
                        used = data[3]
                        soft = data[4]
                        hard = data[5]
                        # balance = data[6]
                        # grace = data[7]
                        total = data[8]
                        # paid = data[9]
                        # warn = data[10]
                        result.append(
                            dict(
                                user=user,
                                used=used,
                                soft=soft,
                                hard=hard,
                                total=total,
                            ))

        return result

    @simple_response
    def list_users(self):
        """ convenience function for the username entry. Lists
			all user names. We don't return this as an array of {id, label}
			tuples because:

			(1) id and label are always the same here
			(2) at the frontend, we must do some postprocessing, and an array
				is easier to handle.
			(3)	the ComboBox is able to handle a plain array.
		"""

        self.lo, self.position = univention.admin.uldap.getMachineConnection(
            ldap_master=False)
        objs = self.lo.search(
            base=self.position.getDomain(),
            filter=
            '(&(|(&(objectClass=posixAccount)(objectClass=shadowAccount))(objectClass=univentionMail)(objectClass=sambaSamAccount)(objectClass=simpleSecurityObject)(&(objectClass=person)(objectClass=organizationalPerson)(objectClass=inetOrgPerson)))(!(uidNumber=0))(!(uid=*$)))',
            attr=['uid'])
        return [obj[1]["uid"][0] for obj in objs]

    @simple_response
    @log
    def enable_printer(self, printer='', on=False):
        """ can enable or disable a printer, depending on args.
			returns empty string on success, else error message.
		"""

        return self._enable_printer(printer, on)

    @simple_response
    @log
    def cancel_jobs(self, jobs, printer=''):
        """ cancels one or more print jobs. Job IDs are passed
			as an array that can be directly passed on to the
			_shell_command() method
		"""

        return self._cancel_jobs(printer, jobs)

    @simple_response
    @log
    def set_quota(self, printer='', user='', soft=0, hard=0):
        """ sets quota limits for a (printer, user) combination.
			optionally tries to create the corresponding user entry.
		"""

        if printer == '' or user == '':
            return "Required parameter missing"
        else:
            return self._set_quota(printer, user, soft, hard)

    @simple_response
    @log
    def reset_quota(self, printer='', users=None):
        """ resets quota for a (printer, user) combination."""
        users = users or []

        return self._reset_quota(printer, users)

    # ----------------------- Internal functions -------------------------

    def _job_list(self, printer):
        """ lists jobs for a given printer, directly suitable for the grid """

        # *** NOTE *** we don't set language to 'neutral' since it is useful
        #				to get localized date/time strings.

        result = []
        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-o', printer])
        expr = re.compile('\s*(\S+)\s+(\S+)\s+(\d+)\s*(.*?)$')
        if status == 0:
            for line in stdout.split("\n"):
                mobj = expr.match(line)
                if mobj:
                    entry = {
                        'job': mobj.group(1),
                        'owner': mobj.group(2),
                        'size': mobj.group(3),
                        'date': mobj.group(4)
                    }
                    result.append(entry)
        return result

    def _list_printers(self):
        """ returns a list of printers, along with their 'enabled' status. """

        result = []
        expr = re.compile('printer\s+(\S+)\s.*?(\S+abled)')
        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-p'],
                                       {'LANG': 'C'})
        if status == 0:
            for line in stdout.split("\n"):
                mobj = expr.match(line)
                if mobj:
                    entry = {'printer': mobj.group(1), 'status': mobj.group(2)}
                    result.append(entry)
        return result

    def _printer_status(self, printer):
        """ returns the 'enabled' status of a printer """

        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-p', printer],
                                       {'LANG': 'C'})
        if status == 0:
            if ' enabled ' in stdout:
                return 'enabled'
            if ' disabled ' in stdout:
                return 'disabled'
        return 'unknown'

    def _printer_details(self, printer):
        """ returns as much as possible details about a printer. """

        result = {}
        expr = re.compile('\s+([^\s\:]+)\:\s*(.*?)$')
        (stdout, stderr, status) = self._shell_command(
            ['/usr/bin/lpstat', '-l', '-p', printer], {'LANG': 'C'})
        if status == 0:
            for line in stdout.split("\n"):
                mobj = expr.match(line)
                if mobj:
                    result[mobj.group(1).lower()] = mobj.group(2)
        result['server'] = self._hostname
        return result

    def _enable_printer(self, printer, on):
        """ internal function that enables/disables a printer.
			returns empty string or error message.
		"""

        cmd = 'univention-cups-enable' if on else 'univention-cups-disable'
        (stdout, stderr, status) = self._shell_command([cmd, printer])

        if status:
            return stderr

        return ''

    def _set_quota(self, printer, user, soft, hard):
        """ sets a quota entry. Can also add a user """

        # Before we can set quota we have to ensure that the user is
        # already known to PyKota. Fortunately these tools don't complain
        # if we try to create a user that doesn't already exist.

        self._shell_command(
            ['/usr/bin/pkusers', '--skipexisting', '--add', user],
            {'LANG': 'C'})

        # Caution! order of args is important!

        (stdout, stderr, status) = self._shell_command([
            '/usr/bin/edpykota', '--printer', printer, '--softlimit',
            str(soft), '--hardlimit',
            str(hard), '--add', user
        ], {'LANG': 'C'})

        # not all errors are propagated in exit codes...
        # but at least they adhere to the general rule that
        # progress is printed to STDOUT and errors/warnings to STDERR
        if status or len(stderr):
            return stderr

        return ''

    def _reset_quota(self, printer, users):
        """ resets the 'used' counter on a quota entry. """

        cmd = ['/usr/bin/edpykota', '--printer', printer, '--reset']
        # appending user names to the args array -> spaces in user names
        # don't confuse edpykota (In 2.4, this was a problem)
        for user in users:
            if user:
                cmd.append(user)
        (stdout, stderr, status) = self._shell_command(cmd, {'LANG': 'C'})

        if status or stderr:
            return stderr

        return ''

    def _quota_enabled(self, printer=None):
        """ returns a dictionary with printer names and their 'quota active' status.
			if printer is specified, returns only quota status for this printer.
		"""

        result = {}
        expr = re.compile('device for (\S+)\:\s*(\S+)$')
        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-v'],
                                       {'LANG': 'C'})
        if status == 0:
            for line in stdout.split("\n"):
                match = expr.match(line)
                if match:
                    quota = False
                    if match.group(2).startswith('cupspykota'):
                        quota = True
                    result[match.group(1)] = quota
        # No printer specified: return the whole list.
        if printer is None:
            return result

        # Printer specified: return its quota value or False if not found.
        return result.get(printer, False)

    def _cancel_jobs(self, printer, jobs):
        """ internal function that cancels a list of jobs.
			returns empty string or error message.
		"""

        args = ['/usr/bin/cancel', '-U', '%s$' % self._hostname]
        for job in jobs:
            args.append(job)
        args.append(printer)
        (stdout, stderr, status) = self._shell_command(args)

        if status:
            return stderr
        return ''

    def _shell_command(self, args, env=None):

        proc = subprocess.Popen(args=args,
                                stdin=None,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                env=env)
        outputs = proc.communicate()

        return (outputs[0], outputs[1], proc.returncode)
コード例 #10
0
class Instance(Base):
    @sanitize(pattern=PatternSanitizer(default='.*'),
              key=ChoicesSanitizer(
                  choices=['printer', 'description', 'location'],
                  required=True))
    @simple_response
    def list_printers(self, key, pattern):
        """ Lists the printers for the overview grid. """
        result = []
        plist = self._list_printers()
        for element in plist:
            printer = element['printer']
            data = self._printer_details(printer)
            for field in data:
                element[field] = data[field]
            # filter according to query
            if pattern.match(element[key]):
                result.append(element)

        return result

    @simple_response
    @log
    def get_printer(self, printer=''):
        """ gets detail data for one printer. """

        result = self._printer_details(printer)
        result['printer'] = printer
        result['status'] = self._printer_status(printer)
        return result

    @simple_response
    def list_users(self):
        """ convenience function for the username entry. Lists
			all user names. We don't return this as an array of {id, label}
			tuples because:

			(1) id and label are always the same here
			(2) at the frontend, we must do some postprocessing, and an array
				is easier to handle.
			(3)	the ComboBox is able to handle a plain array.
		"""

        ucr = ConfigRegistry()
        ucr.load()
        identity = ucr.get('ldap/hostdn')
        with open('/etc/machine.secret') as fd:
            password = fd.readline().strip()
        server = ucr.get('ldap/server/name')
        udm = UDM.credentials(identity, password, server=server).version(2)
        users = udm.get('users/user').search()
        return [user.props.username for user in users]

    @simple_response
    def list_jobs(self, printer=''):
        """ lists jobs for a given printer, directly suitable for the grid """

        # *** NOTE *** we don't set language to 'neutral' since it is useful
        #				to get localized date/time strings.

        result = []
        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-o', printer])
        expr = re.compile(r'\s*(\S+)\s+(\S+)\s+(\d+)\s*(.*?)$')
        if status == 0:
            for line in stdout.split("\n"):
                mobj = expr.match(line)
                if mobj:
                    entry = {
                        'job': mobj.group(1),
                        'owner': mobj.group(2),
                        'size': int(mobj.group(3)),
                        'date': mobj.group(4)
                    }
                    result.append(entry)
        return result

    def _list_printers(self):
        """ returns a list of printers, along with their 'enabled' status. """

        result = []
        expr = re.compile(r'printer\s+(\S+)\s.*?(\S+abled)')
        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-p'],
                                       {'LANG': 'C'})
        if status == 0:
            for line in stdout.split("\n"):
                mobj = expr.match(line)
                if mobj:
                    entry = {'printer': mobj.group(1), 'status': mobj.group(2)}
                    result.append(entry)
        return result

    def _printer_status(self, printer):
        """ returns the 'enabled' status of a printer """

        (stdout, stderr,
         status) = self._shell_command(['/usr/bin/lpstat', '-p', printer],
                                       {'LANG': 'C'})
        if status == 0:
            if ' enabled ' in stdout:
                return 'enabled'
            if ' disabled ' in stdout:
                return 'disabled'
        return 'unknown'

    def _printer_details(self, printer):
        """ returns as much as possible details about a printer. """

        result = {}
        expr = re.compile(r'\s+([^\s\:]+)\:\s*(.*?)$')
        (stdout, stderr, status) = self._shell_command(
            ['/usr/bin/lpstat', '-l', '-p', printer], {'LANG': 'C'})
        if status == 0:
            for line in stdout.split("\n"):
                mobj = expr.match(line)
                if mobj:
                    result[mobj.group(1).lower()] = mobj.group(2)
        result['server'] = ucr.get('hostname')
        return result

    @simple_response
    @log
    def enable_printer(self, printer='', on=False):
        """ enable or disable a printer, depending on args. """
        cmd = 'univention-cups-enable' if on else 'univention-cups-disable'
        (stdout, stderr, status) = self._shell_command([cmd, printer])

        if status:
            raise UMC_Error(
                _('Could not %s printer: %s') % (
                    _('activate') if on else _('deactivate'),
                    stderr,
                ))

    @simple_response
    @log
    def cancel_jobs(self, jobs, printer=''):
        """ cancels one or more print jobs. Job IDs are passed
			as an array that can be directly passed on to the
			_shell_command() method
		"""

        args = ['/usr/bin/cancel', '-U', '%s$' % self._hostname]
        for job in jobs:
            args.append(job)
        args.append(printer)
        (stdout, stderr, status) = self._shell_command(args)
        if status:
            raise UMC_Error(_('Could not cancel job: %s') % (stderr, ))

    def _shell_command(self, args, env=None):
        proc = subprocess.Popen(args=args,
                                stdin=None,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                env=env)
        stdout, stderr = proc.communicate()
        stdout, stderr = stdout.decode('UTF-8', 'replace'), stderr.decode(
            'UTF-8', 'replace')
        return (stdout, stderr, proc.returncode)
コード例 #11
0
class Instance(umcm.Base):
    def __init__(self):
        umcm.Base.__init__(self)
        self._finishedLock = threading.Lock()
        self._finishedResult = True
        self._progressParser = util.ProgressParser()
        self._cleanup_required = False
        # reset umask to default
        os.umask(0022)

    def init(self):
        util.installer_i18n.set_language(str(self.locale))
        os.environ['LC_ALL'] = str(self.locale)

    def destroy(self):
        if self._cleanup_required:
            MODULE.info('Appliance mode: cleanup by timeout')
            # cleanup restarts umc, so MODULE.info will never
            # be called. but leave it that way, maybe it can
            # be called in the future.
            if util.cleanup():
                MODULE.info('... cleanup successful')
            else:
                MODULE.warn('... cleanup operation failed')
        return super(Instance, self).destroy()

    def _check_thread_error(self, thread, result, request):
        """Checks if the thread returned an exception. In that case in
		error response is send and the function returns True. Otherwise
		False is returned."""
        if not isinstance(result, BaseException):
            return False

        msg = '%s\n%s: %s\n' % (''.join(traceback.format_tb(
            thread.exc_info[2])), thread.exc_info[0].__name__,
                                str(thread.exc_info[1]))
        MODULE.process('An internal error occurred: %s' % msg)
        self.finished(request.id, None, msg, False)
        return True

    def _thread_finished(self, thread, result, request):
        """This method is invoked when a threaded request function is
		finished. The result is send back to the client. If the result
		is an instance of BaseException an error is returned."""
        if self._check_thread_error(thread, result, request):
            return

        self.finished(request.id, result)

    def load(self, request):
        '''Return a dict with all necessary values for system-setup read from the current
		status of the system.'''
        values = util.load_values()
        self.finished(request.id, values)

    def save_keymap(self, request):
        '''Set the systems x-keymap according to
		request.options[keymap]'''

        keymap = request.options.get('keymap')
        if keymap:
            xkeymap = util._xkeymap(keymap)
            subprocess.call([
                '/usr/bin/setxkbmap', '-display', ':0', '-layout',
                xkeymap['layout'], '-variant', xkeymap['variant']
            ])
        self.finished(request.id, True)

    def save(self, request):
        '''Reconfigures the system according to the values specified in the dict given as
		option named "values".'''

        # get old and new values
        orgValues = util.load_values()
        values = request.options.get('values', {})

        def _thread(request, obj):
            # acquire the lock until the scripts have been executed
            self._finishedResult = False
            obj._finishedLock.acquire()
            try:
                self._progressParser.reset()

                # write the profile file and run setup scripts
                orgValues = util.load_values()
                util.pre_save(values, orgValues)

                MODULE.info('saving profile values')
                util.write_profile(values)

                if not values:
                    MODULE.error('No property "values" given for save().')
                    return False

                # in case of changes of the IP address, restart UMC server and web server
                # for this we ignore changes of virtual or non-default devices
                MODULE.info('Check whether ip addresses have been changed')
                restart = False
                for ikey, ival in values.iteritems():
                    if RE_IPV4.match(ikey) or RE_IPV6_DEFAULT.match(
                            ikey) or RE_SSL.match(ikey):
                        restart = True
                        break
                MODULE.info('Restart servers: %s' % restart)

                # on a joined system or on a basesystem, we can run the setup scripts
                MODULE.info('runnning system setup scripts')
                util.run_scripts(self._progressParser, restart)

                # done :)
                self._finishedResult = True
                return True
            finally:
                obj._finishedLock.release()

        def _finished(thread, result):
            if isinstance(result, BaseException):
                MODULE.warn('Exception during saving the settings: %s' %
                            str(result))

        thread = notifier.threads.Simple(
            'save', notifier.Callback(_thread, request, self), _finished)
        thread.run()

        self.finished(request.id, True)

    def join(self, request):
        '''Join and reconfigure the system according to the values specified in the dict given as
		option named "values".'''

        # get old and new values
        orgValues = util.load_values()
        values = request.options.get('values', {})

        # determine new system role
        oldrole = orgValues.get('server/role', '')
        newrole = values.get('server/role', oldrole)
        if newrole == 'basesystem' or orgValues.get('joined'):
            raise Exception(
                _('Base systems and already joined systems cannot be joined.'))

        def _thread(request, obj, username, password):
            # acquire the lock until the scripts have been executed
            self._finishedResult = False
            obj._finishedLock.acquire()
            try:
                self._progressParser.reset()

                # write the profile file and run setup scripts
                util.pre_save(values, orgValues)

                # on unjoined DC master the nameserver must be set to the external nameserver
                if newrole == 'domaincontroller_master' and not orgValues.get(
                        'joined'):
                    for i in range(1, 4):
                        # overwrite these values only if they are set, because the UMC module
                        # will save only changed values
                        if values.get('dns/forwarder%d' % i):
                            values['nameserver%d' % i] = values.get(
                                'dns/forwarder%d' % i)

                MODULE.info('saving profile values')
                util.write_profile(values)

                # unjoined DC master (that is not being converted to a basesystem) -> run the join script
                MODULE.info('runnning system setup join script')
                util.run_joinscript(self._progressParser, username, password)

                # done :)
                self._finishedResult = True

                # we should do a cleanup now
                self._cleanup_required = True

                return True
            finally:
                obj._finishedLock.release()

        def _finished(thread, result):
            if isinstance(result, BaseException):
                MODULE.warn('Exception during saving the settings: %s' %
                            str(result))

        thread = notifier.threads.Simple(
            'save',
            notifier.Callback(_thread, request, self,
                              request.options.get('username'),
                              request.options.get('password')), _finished)
        thread.run()

        self.finished(request.id, True)

    def check_finished(self, request):
        '''Check whether the join/setup scripts are finished. This method implements a long
		polling request, i.e., the request is only finished at the moment when all scripts
		have been executed or due to a timeout. If it returns because of the timeout, a new
		try can be started.'''
        def _thread(request, obj):
            def progress_info(state, **kwargs):
                info = {
                    'component': state.fractionName,
                    'info': state.message,
                    'errors': state.errors,
                    'critical': state.critical,
                    'steps': state.percentage
                }
                info.update(kwargs)
                return info

            # acquire the lock in order to wait for the join/setup scripts to finish
            # do this for 30 sec and then return anyway
            SLEEP_TIME = 0.200
            WAIT_TIME = 30
            ntries = WAIT_TIME / SLEEP_TIME
            while not obj._finishedLock.acquire(False):
                if ntries <= 0 or self._progressParser.changed and self._progressParser.current:
                    state = self._progressParser.current
                    return progress_info(state, finished=False)
                time.sleep(SLEEP_TIME)
                ntries -= 1

            obj._finishedLock.release()

            # scripts are done, return final result
            # return all errors that we gathered throughout the setup
            state = self._progressParser.current
            return progress_info(state, finished=obj._finishedResult)

        thread = notifier.threads.Simple(
            'check_finished', notifier.Callback(_thread, request, self),
            notifier.Callback(self._thread_finished, request))
        thread.run()

    def cleanup(self, request):
        # shut down the browser in appliance mode
        # call finished() directly, so the browser will get the response in any case
        # (see Bug #27632)
        MODULE.info('Appliance mode: cleanup')
        self.finished(request.id, True)
        # put it here just in case destroy gets called during util
        self._cleanup_required = False
        if util.cleanup():
            MODULE.info('... cleanup successful')
        else:
            MODULE.warn('... cleanup operation failed')

    def validate(self, request):
        '''Validate the specified values given in the dict as option named "values".
		Return a dict (with variable names as key) of dicts with the structure:
		{ "valid": True/False, "message": "..." }'''

        # init variables
        messages = []
        values = request.options.get('values', {})
        orgValues = util.load_values()

        # determine new system role
        newrole = values.get('server/role', orgValues.get('server/role', ''))

        # mix original and new values
        allValues = copy.copy(values)
        for ikey, ival in orgValues.iteritems():
            if ikey not in allValues:
                allValues[ikey] = ival

        # helper functions
        # TODO: 'valid' is not correctly evaluated in frontend
        # i.e. if valid you may continue without getting message
        def _check(key, check, message, critical=True):
            if key not in values:
                return
            if not check(values[key]):
                messages.append({
                    'message': message,
                    'valid': not critical,
                    'key': key
                })

        def _append(key, message):
            messages.append({'key': key, 'valid': False, 'message': message})

        # system role
        _check(
            'server/role', lambda x: not (orgValues.get('joined')) or
            (orgValues.get('server/role') == values.get('server/role')),
            _('The system role may not change on a system that has already joined to domain.'
              ))

        # basis
        components = RE_SPACE.split(values.get('components', ''))
        packages = set(
            reduce(lambda x, y: x + y, [i.split(':') for i in components]))

        _check(
            'hostname', util.is_hostname,
            _('The hostname is not a valid fully qualified domain name in lowercase (e.g. host.example.com).'
              ))
        _check(
            'hostname',
            lambda x: len(x) <= 13,
            _('A valid NetBIOS name can not be longer than 13 characters. If Samba is installed, the hostname should be shortened.'
              ),
            critical=('univention-samba' in packages
                      or 'univention-samba4' in packages))

        _check(
            'domainname', util.is_domainname,
            _("Please enter a valid fully qualified domain name in lowercase (e.g. host.example.com)."
              ))

        hostname = allValues.get('hostname')
        domainname = allValues.get('domainname')
        if len(hostname + domainname) >= 63:
            _append(
                'domainname',
                _('The length of fully qualified domain name is greater than 63 characters.'
                  ))
        if hostname == domainname.split('.')[0]:
            _append('domainname', _("Hostname is equal to domain name."))

        _check(
            'windows/domain', lambda x: x == x.upper(),
            _("The windows domain name can only consist of upper case characters."
              ))
        _check(
            'windows/domain', lambda x: len(x) < 14,
            _("The length of the windows domain name needs to be smaller than 14 characters."
              ))
        _check('windows/domain', util.is_windowsdomainname,
               _("The windows domain name is not valid."))

        _check(
            'ldap/base', lambda x: x.find(' ') == -1,
            _("The LDAP base may not contain any blanks (e.g., dc=test,dc=net)."
              ))

        _check(
            'root_password', lambda x: len(x) >= 8,
            _("The root password is too short. For security reasons, your password must contain at least 8 characters."
              ))
        _check('root_password', util.is_ascii,
               _("The root password may only contain ascii characters."))

        # ssl
        for ikey, iname in [('ssl/state', _('State')),
                            ('ssl/locality', _('Location'))]:
            _check(
                ikey, lambda x: len(x) <= 128,
                _('The following value is too long, only 128 characters allowed: %s'
                  ) % iname)
        for ikey, iname in [('ssl/organization', _('Organization')),
                            ('ssl/organizationalunit', _('Business unit')),
                            ('ssl/email', _('Email address'))]:
            _check(
                ikey, lambda x: len(x) <= 64,
                _('The following value is too long, only 64 characters allowed: %s'
                  ) % iname)
        _check('ssl/email', lambda x: x.find('@') >= 0,
               _("Please enter a valid email address"))

        # net
        # validate all ipv4 addresses and there netmask
        checkedIpv4 = set()
        for ikey, ival in values.iteritems():
            if not ival:
                continue
            m = RE_IPV4.match(ikey)
            if m:
                # get the parts
                iname, idev, ivirt, itype = m.groups()

                # have we already tested this device?
                addressKey = 'interfaces/%s/address' % iname
                maskKey = 'interfaces/%s/netmask' % iname
                if addressKey in checkedIpv4:
                    continue
                checkedIpv4.add(addressKey)

                # make sure that address and netmask are correct
                virtStr = ''
                if ivirt:
                    virtStr = _(' (virtual)')
                if not util.is_ipv4addr(allValues.get(addressKey)):
                    _append(
                        addressKey,
                        _('IPv4 address is not valid [%s%s]: "%s"') %
                        (idev, virtStr, allValues.get(addressKey)))
                elif not allValues.get(maskKey) or not util.is_ipv4netmask(
                        '%s/%s' %
                    (allValues.get(addressKey), allValues.get(maskKey))):
                    _append(
                        maskKey,
                        _('IPv4 netmask is not valid [%s%s]: "%s"') %
                        (idev, virtStr, allValues.get(maskKey, '')))

        # validate all ipv6 addresses, their prefix, and identifier
        checkedIpv6 = set()
        for ikey, ival in values.iteritems():
            if not ival:
                continue
            m = RE_IPV6.match(ikey)
            if m:
                # get the parts
                idev, iid, itype = m.groups()

                # have we already tested this device?
                addressKey = 'interfaces/%s/ipv6/%s/address' % (idev, iid)
                prefixKey = 'interfaces/%s/ipv6/%s/prefix' % (idev, iid)
                if addressKey in checkedIpv6:
                    continue
                checkedIpv6.add(addressKey)

                # make sure that the ID is correct
                if not RE_IPV6_ID.match(iid):
                    _append(
                        addressKey,
                        _('The specified IPv6 identifier may only consist of letters and numbers: %s'
                          ) % iid)

                # make sure that address and prefix are correct
                if not util.is_ipv6addr(allValues.get(addressKey)):
                    _append(
                        addressKey,
                        _('IPv6 address is not valid [%s]: %s') %
                        (idev, allValues.get(addressKey)))
                if not allValues.get(prefixKey) or not util.is_ipv6netmask(
                        '%s/%s' %
                    (allValues.get(addressKey), allValues.get(prefixKey))):
                    _append(
                        prefixKey,
                        _('IPv6 prefix is not valid [%s]: %s') %
                        (idev, allValues.get(prefixKey, '')))

        # check nameservers
        for ikey, iname in [('nameserver[1-3]', _('Domain name server')),
                            ('dns/forwarder[1-3]', _('External name server'))]:
            reg = re.compile('^(%s)$' % ikey)
            for jkey, jval in values.iteritems():
                if reg.match(jkey):
                    if not values.get(jkey):
                        # allow empty value
                        continue
                    _check(
                        jkey, util.is_ipaddr,
                        _('The specified IP address (%s) is not valid: %s') %
                        (iname, jval))

        # check gateways
        if values.get('gateway'):  # allow empty value
            _check(
                'gateway', util.is_ipv4addr,
                _('The specified gateway IPv4 address is not valid: %s') %
                values.get('gateway'))
        if values.get('ipv6/gateway'):  # allow empty value
            _check(
                'ipv6/gateway', util.is_ipv6addr,
                _('The specified gateway IPv6 address is not valid: %s') %
                values.get('ipv6/gateway'))

        # proxy
        _check(
            'proxy/http', util.is_proxy,
            _('The specified proxy address is not valid (e.g., http://10.201.1.1:8080): %s'
              ) % allValues.get('proxy/http', ''))

        # check global network settings
        isSetIpv4 = False
        ipv4HasAddress = False
        ipv4HasDynamic = False
        devIpv4VirtualDevices = set()

        isSetIpv6 = False
        ipv6HasAddress = False
        hasIpv6DefaultDevices = True
        ipv6HasDynamic = False

        tmpUCR = univention.config_registry.ConfigRegistry()
        devIpv6HasDefaultID = {}
        for ikey, ival in allValues.iteritems():
            m = RE_IPV6_ADDRESS.match(ikey)
            if m:
                idev, iid = m.groups()

                # see whether the device is in the dict
                if idev not in devIpv6HasDefaultID:
                    devIpv6HasDefaultID[idev] = False

                # identifier 'default'
                devIpv6HasDefaultID[idev] |= (iid == 'default')

                # ipv6 address
                ipv6HasAddress |= util.is_ipv6addr(ival)

            # ipv4 address
            if RE_IPV4_ADDRESS.match(ikey):
                ipv4HasAddress |= util.is_ipv4addr(ival)

            # dynamic ipv4
            ipv4HasDynamic |= bool(
                RE_IPV4_DYNAMIC.match(ikey) and ival in ('dynamic', 'dhcp'))

            # dynamic ipv6
            if RE_IPV6_DYNAMIC.match(ikey):
                tmpUCR[ikey] = ival
                if tmpUCR.is_true(ikey):
                    ipv6HasDynamic = True

            # ipv6 configuration
            if RE_IPV6.match(ikey) and ival:
                isSetIpv6 = True

            # ipv4 configuration
            m = RE_IPV4.match(ikey)
            if m and ival:
                isSetIpv4 = True

                # check whether this entry is a virtual device
                idev, ivirt = m.groups()[1:3]
                if ivirt:
                    devIpv4VirtualDevices.add(idev)

        # check whether all virtual devices have a real device that is defined
        for idev in devIpv4VirtualDevices:
            mask = allValues.get('interfaces/%s/netmask' % idev)
            address = allValues.get('interfaces/%s/address' % idev)
            if not mask or not address or not util.is_ipv4netmask(
                    '%s/%s' % (address, mask)):
                _append(
                    'interfaces/%s/address' % idev,
                    _('A virtual device cannot be specified alone: %s') % idev)
                break

        # check whether all devices have a default entry
        for idev, iset in devIpv6HasDefaultID.iteritems():
            hasIpv6DefaultDevices &= iset

        # global checks
        if not (isSetIpv4 or ipv4HasDynamic) and not (isSetIpv6
                                                      or ipv6HasDynamic):
            _append(
                'interfaces/eth0/address',
                _('At least one network device (either IPv4 or IPv6) needs to be configured.'
                  ))
        if isSetIpv6 and not hasIpv6DefaultDevices:
            _append(
                'interfaces/eth0/ipv6/default/address',
                _('A default entry with the identifier "default" needs to be specified for each network device.'
                  ))
        if newrole in [
                'domaincontroller_master', 'domaincontroller_backup',
                'domaincontroller_slave', 'memberserver'
        ] and isSetIpv4 and not ipv4HasAddress:
            _append('interfaces/eth0/address',
                    _('At least one IPv4 address needs to be specified.'))
        if not ipv4HasDynamic and not ipv6HasDynamic and not allValues.get(
                'nameserver1') and not allValues.get(
                    'nameserver2') and not allValues.get('nameserver3'):
            _append(
                'nameserver1',
                _('At least one domain name server needs to be given if DHCP or SLAAC is not specified.'
                  ))

        # software checks
        if 'univention-virtual-machine-manager-node-kvm' in packages and 'univention-virtual-machine-manager-node-xen' in packages:
            _append(
                'components',
                _('It is not possible to install KVM and XEN components on one system. Please select only one of these components.'
                  ))
        if 'univention-samba' in packages and 'univention-samba4' in packages:
            _append(
                'components',
                _('It is not possible to install Samba 3 and Samba 4 on one system. Please select only one of these components.'
                  ))

        self.finished(request.id, messages)

    @sanitize(pattern=PatternSanitizer(default='.*',
                                       required=True,
                                       add_asterisks=False))
    @simple_response
    def lang_locales(self, pattern, category='language_en'):
        '''Return a list of all available locales.'''
        return util.get_available_locales(pattern, category)

    def lang_default_timezone(self, request):
        '''Returns default timezone for given locale.'''
        countrycode = request.options.get('countrycode', '')
        timezone = None
        file = open('/lib/univention-installer/locale/countrycode2timezone')

        reader = csv.reader(file, delimiter=' ')
        for row in reader:
            if row[0].startswith("#"): continue
            if len(row) > 1:
                if countrycode.upper() == row[0].upper():
                    timezone = row[1]
                    break
        file.close()

        if timezone is None:
            timezone = 'Europe/Berlin'
        self.finished(request.id, timezone)

    def lang_timezones(self, request):
        '''Return a list of all available time zones.'''
        try:
            file = open('/lib/univention-installer/locale/timezone')
        except:
            MODULE.error(
                'Cannot find locale data for timezones in /lib/univention-installer/locale'
            )
            self.finished(request.id, None)
            return

        timezones = [i.strip('\n') for i in file if not i.startswith('#')]

        self.finished(request.id, timezones)

    def lang_default_keymap(self, request):
        '''Returns default timezone for given locale.'''
        # use "or ''" to be sure to not get None
        countrycode = (request.options.get('countrycode') or '').upper()
        keymap = None
        file = open('/lib/univention-installer/locale/default-kmaps')

        reader = csv.reader(file, delimiter=':')
        for row in reader:
            if row[0].startswith("#"): continue
            if len(row) > 1:
                if row[1].upper().startswith(countrycode):
                    keymap = row[1]
                    break
        file.close()

        if keymap is None:
            keymap = 'us'
        self.finished(request.id, keymap)

    def lang_keymaps(self, request):
        '''Return a list of all available keyboard layouts.'''
        try:
            file = open('/lib/univention-installer/locale/all-kmaps')
        except:
            MODULE.error(
                'Cannot find locale data for keymaps in /lib/univention-installer/locale'
            )
            self.finished(request.id, None)
            return

        r = csv.reader(file, delimiter=':')
        keymaps = [{
            'label': i[0],
            'id': i[1]
        } for i in r if not i[0].startswith('#')]

        self.finished(request.id, keymaps)

    def lang_countrycodes(self, request):
        '''Return a list of all countries with their two letter chcountry codes.'''
        try:
            file = open('/lib/univention-installer/locale/country_codes')
        except:
            MODULE.error(
                'Cannot find locale data for keymaps in /lib/univention-installer/locale'
            )
            self.finished(request.id, None)
            return

        r = csv.reader(file, delimiter=':')
        countries = [{
            'label': i[0],
            'id': i[1]
        } for i in r if not i[0].startswith('#')]

        # add the value from ucr value to the list
        # this is required because invalid values will be unset in frontend
        # Bug #26409
        tmpUCR = univention.config_registry.ConfigRegistry()
        tmpUCR.load()
        ssl_country = tmpUCR.get('ssl/country')
        if ssl_country not in [i['id'] for i in countries]:
            countries.append({'label': ssl_country, 'id': ssl_country})

        self.finished(request.id, countries)

    def net_read(self, request):
        '''Return a dict of all current network settings.'''

    def net_interfaces(self, request):
        '''Return a list of all available network interfaces.'''
        interfaces = [idev['name'] for idev in util.detect_interfaces()]
        self.finished(request.id, interfaces)

    def net_dhclient(self, request):
        '''Request a DHCP address. Expects as options a dict containing the key
		"interface" and optionally the key "timeout" (in seconds).'''
        interface = request.options.get('interface')
        timeout = request.options.get('timeout', 45)
        if not interface:
            message = 'No property "interface" given for dhclient().'
            MODULE.error(message)
            self.finished(request.id, None, success=False, message=message)
            return

        res = util.dhclient(interface, timeout)
        self.finished(request.id, res)

    def software_components(self, request):
        '''Return a list of all available software packages. Entries have the properties
		"id", "label", and "packages" which is an array of the Debian package names.'''
        role = request.options.get('role')
        choices = [{
            'id': i['id'],
            'label': i['Name'],
            'packages': i['Packages']
        } for i in util.get_components(role=role)]
        self.finished(request.id, choices)
コード例 #12
0
ファイル: __init__.py プロジェクト: bopopescu/smart-1
class Instance(umcm.Base):
    def init(self):
        self.ucr = univention.config_registry.ConfigRegistry()
        self.ucr.load()

        util.install_opener(self.ucr)

        self.package_manager = PackageManager(
            info_handler=MODULE.process,
            step_handler=None,
            error_handler=MODULE.warn,
            lock=False,
            always_noninteractive=True,
        )
        self.uu = UniventionUpdater(False)
        self.component_manager = util.ComponentManager(self.ucr, self.uu)

        # in order to set the correct locale for Application
        locale.setlocale(locale.LC_ALL, str(self.locale))

    @sanitize(email=EmailSanitizer(required=True))
    @simple_response
    def request_new_license(self, email):
        license = LICENSE.dump_data()
        if license is None:
            raise umcm.UMC_CommandError(_('Cannot parse License from LDAP'))
        data = {}
        data['email'] = email
        data['licence'] = license
        data = urllib.urlencode(data)
        url = 'https://license.univention.de/keyid/conversion/submit'
        request = urllib2.Request(url,
                                  data=data,
                                  headers={'User-agent': 'UMC/AppCenter'})
        try:
            util.urlopen(request)
        except Exception as e:
            try:
                # try to parse an html error
                body = e.read()
                detail = re.search(
                    '<span id="details">(?P<details>.*?)</span>',
                    body).group(1)
            except:
                detail = str(e)
            raise umcm.UMC_CommandError(
                _('An error occurred while sending the request: %s') % detail)
        else:
            return True

    @sanitize(pattern=PatternSanitizer(default='.*'))
    @simple_response
    def query(self, pattern):
        LICENSE.reload()
        try:
            applications = Application.all(force_reread=True)
        except (urllib2.HTTPError, urllib2.URLError) as e:
            raise umcm.UMC_CommandError(
                _('Could not query App Center: %s') % e)
        result = []
        self.package_manager.reopen_cache()
        for application in applications:
            if pattern.search(application.name):
                props = application.to_dict(self.package_manager)

                # delete larger entries
                for ikey in ('readmeupdate', 'licenseagreement'):
                    if ikey in props:
                        del props[ikey]

                result.append(props)
        return result

    @sanitize(application=StringSanitizer(minimum=1, required=True))
    @simple_response
    def get(self, application):
        LICENSE.reload()
        application = Application.find(application)
        self.package_manager.reopen_cache()
        return application.to_dict(self.package_manager)

    @sanitize(function=ChoicesSanitizer(['install', 'uninstall', 'update'],
                                        required=True),
              application=StringSanitizer(minimum=1, required=True),
              force=BooleanSanitizer())
    def invoke(self, request):
        function = request.options.get('function')
        application_id = request.options.get('application')
        application = Application.find(application_id)
        force = request.options.get('force')
        try:
            # make sure that the application cane be installed/updated
            can_continue = True
            result = {
                'install': [],
                'remove': [],
                'broken': [],
            }
            if not application:
                MODULE.info('Application not found: %s' % application_id)
                can_continue = False
            elif function == 'install' and not application.can_be_installed(
                    self.package_manager):
                MODULE.info('Application cannot be installed: %s' %
                            application_id)
                can_continue = False
            elif function == 'update' and not application.can_be_updated():
                MODULE.info('Application cannot be updated: %s' %
                            application_id)
                can_continue = False

            if can_continue and function in ('install', 'update'):
                result = application.install_dry_run(self.package_manager,
                                                     self.component_manager,
                                                     remove_component=False)
                if result['broken'] or (result['remove'] and not force):
                    MODULE.info('Remove component: %s' % application_id)
                    self.component_manager.remove_app(application)
                    self.package_manager.update()
                    can_continue = False
            elif can_continue and function in ('uninstall', ) and not force:
                result['remove'] = application.uninstall_dry_run(
                    self.package_manager)
                can_continue = False
            result['can_continue'] = can_continue
            self.finished(request.id, result)

            if can_continue:

                def _thread(module, application, function):
                    with module.package_manager.locked(reset_status=True,
                                                       set_finished=True):
                        with module.package_manager.no_umc_restart():
                            if function in ('install', 'update'):
                                # dont have to add component: already added during dry_run
                                return application.install(
                                    module.package_manager,
                                    module.component_manager,
                                    add_component=False)
                            else:
                                return application.uninstall(
                                    module.package_manager,
                                    module.component_manager)

                def _finished(thread, result):
                    if isinstance(result, BaseException):
                        MODULE.warn('Exception during %s %s: %s' %
                                    (function, application_id, str(result)))

                thread = notifier.threads.Simple(
                    'invoke',
                    notifier.Callback(_thread, self, application, function),
                    _finished)
                thread.run()
        except LockError:
            # make it thread safe: another process started a package manager
            # this module instance already has a running package manager
            raise umcm.UMC_CommandError(
                _('Another package operation is in progress'))

    @simple_response
    def app_center_app_license(self, application):
        application = Application.find(application)
        if not application or not application.get('licensefile'):
            raise umcm.UMC_CommandError(
                _('No license file available for application: %s') %
                (application.id))

        # open the license file and replace line breaks with BR-tags
        fp = util.urlopen(application.get('licensefile'))
        txt = ''.join(fp.readlines()).strip()
        txt = txt.replace('\n\n\n', '\n<br>\n<br>\n<br>\n')
        txt = txt.replace('\n\n', '\n<br>\n<br>\n')
        return txt

    @simple_response
    def packages_sections(self):
        """ fills the 'sections' combobox in the search form """

        sections = set()
        for package in self.package_manager.packages():
            sections.add(package.section)

        return sorted(sections)

    @sanitize(pattern=PatternSanitizer(required=True))
    @simple_response
    def packages_query(self, pattern, section='all', key='package'):
        """ Query to fill the grid. Structure is fixed here. """
        result = []
        for package in self.package_manager.packages():
            if section == 'all' or package.section == section:
                toshow = False
                if pattern.pattern == '^.*$':
                    toshow = True
                elif key == 'package' and pattern.search(package.name):
                    toshow = True
                elif key == 'description' and pattern.search(
                        package.candidate.raw_description):
                    toshow = True
                if toshow:
                    result.append(self._package_to_dict(package, full=False))
        return result

    @simple_response
    def packages_get(self, package):
        """ retrieves full properties of one package """

        package = self.package_manager.get_package(package)
        if package is not None:
            return self._package_to_dict(package, full=True)
        else:
            # TODO: 404?
            return {}

    @sanitize(function=MappingSanitizer(
        {
            'install': 'install',
            'upgrade': 'install',
            'uninstall': 'remove',
        },
        required=True),
              packages=ListSanitizer(StringSanitizer(minimum=1),
                                     required=True))
    @simple_response
    def packages_invoke_dry_run(self, packages, function):
        packages = self.package_manager.get_packages(packages)
        kwargs = {'install': [], 'remove': [], 'dry_run': True}
        if function == 'install':
            kwargs['install'] = packages
        else:
            kwargs['remove'] = packages
        return dict(
            zip(['install', 'remove', 'broken'],
                self.package_manager.mark(**kwargs)))

    @sanitize(function=MappingSanitizer(
        {
            'install': 'install',
            'upgrade': 'install',
            'uninstall': 'remove',
        },
        required=True),
              packages=ListSanitizer(StringSanitizer(minimum=1),
                                     required=True))
    def packages_invoke(self, request):
        """ executes an installer action """
        packages = request.options.get('packages')
        function = request.options.get('function')

        try:
            with self.package_manager.locked(reset_status=True):
                not_found = [
                    pkg_name for pkg_name in packages
                    if self.package_manager.get_package(pkg_name) is None
                ]
                self.finished(request.id, {'not_found': not_found})

                if not not_found:

                    def _thread(package_manager, function, packages):
                        with package_manager.locked(set_finished=True):
                            with package_manager.no_umc_restart():
                                if function == 'install':
                                    package_manager.install(*packages)
                                else:
                                    package_manager.uninstall(*packages)

                    def _finished(thread, result):
                        if isinstance(result, BaseException):
                            MODULE.warn('Exception during %s %s: %r' %
                                        (function, packages, str(result)))

                    thread = notifier.threads.Simple(
                        'invoke',
                        notifier.Callback(_thread, self.package_manager,
                                          function, packages), _finished)
                    thread.run()
        except LockError:
            # make it thread safe: another process started a package manager
            # this module instance already has a running package manager
            raise umcm.UMC_CommandError(
                _('Another package operation is in progress'))

    @simple_response
    def progress(self):
        timeout = 5
        return self.package_manager.poll(timeout)

    def _package_to_dict(self, package, full):
        """ Helper that extracts properties from a 'apt_pkg.Package' object
			and stores them into a dictionary. Depending on the 'full'
			switch, stores only limited (for grid display) or full
			(for detail view) set of properties.
		"""
        installed = package.installed  # may be None
        candidate = package.candidate

        result = {
            'package': package.name,
            'installed': package.is_installed,
            'upgradable': package.is_upgradable,
            'summary': candidate.summary,
        }

        # add (and translate) a combined status field
        # *** NOTE *** we translate it here: if we would use the Custom Formatter
        #		of the grid then clicking on the sort header would not work.
        if package.is_installed:
            if package.is_upgradable:
                result['status'] = _('upgradable')
            else:
                result['status'] = _('installed')
        else:
            result['status'] = _('not installed')

        # additional fields needed for detail view
        if full:
            result['section'] = package.section
            result['priority'] = package.priority
            # Some fields differ depending on whether the package is installed or not:
            if package.is_installed:
                result['summary'] = installed.summary  # take the current one
                result['description'] = installed.description
                result['installed_version'] = installed.version
                result['size'] = installed.installed_size
                if package.is_upgradable:
                    result['candidate_version'] = candidate.version
            else:
                del result[
                    'upgradable']  # not installed: don't show 'upgradable' at all
                result['description'] = candidate.description
                result['size'] = candidate.installed_size
                result['candidate_version'] = candidate.version
            # format size to handle bytes
            size = result['size']
            byte_mods = ['B', 'kB', 'MB']
            for byte_mod in byte_mods:
                if size < 10000:
                    break
                size = float(size) / 1000  # MB, not MiB
            else:
                size = size * 1000  # once too often
            if size == int(size):
                format_string = '%d %s'
            else:
                format_string = '%.2f %s'
            result['size'] = format_string % (size, byte_mod)

        return result

    @simple_response
    def components_query(self):
        """	Returns components list for the grid in the ComponentsPage.
		"""
        # be as current as possible.
        self.uu.ucr_reinit()
        self.ucr.load()

        result = []
        for comp in self.uu.get_all_components():
            result.append(self.component_manager.component(comp))
        return result

    @sanitize_list(StringSanitizer())
    @multi_response(single_values=True)
    def components_get(self, iterator, component_id):
        # be as current as possible.
        self.uu.ucr_reinit()
        self.ucr.load()
        for component_id in iterator:
            yield self.component_manager.component(component_id)

    @sanitize_list(DictSanitizer({'object': advanced_components_sanitizer}))
    @multi_response
    def components_put(self, iterator, object):
        """Writes back one or more component definitions.
		"""
        # umc.widgets.Form wraps the real data into an array:
        #
        #	[
        #		{
        #			'object' : { ... a dict with the real data .. },
        #			'options': None
        #		},
        #		... more such entries ...
        #	]
        #
        # Current approach is to return a similarly structured array,
        # filled with elements, each one corresponding to one array
        # element of the request:
        #
        #	[
        #		{
        #			'status'	:	a number where 0 stands for success, anything else
        #							is an error code
        #			'message'	:	a result message
        #			'object'	:	a dict of field -> error message mapping, allows
        #							the form to show detailed error information
        #		},
        #		... more such entries ...
        #	]
        with util.set_save_commit_load(self.ucr) as super_ucr:
            for object, in iterator:
                yield self.component_manager.put(object, super_ucr)
        self.package_manager.update()

    # do the same as components_put (update)
    # but dont allow adding an already existing entry
    components_add = sanitize_list(
        DictSanitizer({'object': add_components_sanitizer}))(components_put)
    components_add.__name__ = 'components_add'

    @sanitize_list(StringSanitizer())
    @multi_response(single_values=True)
    def components_del(self, iterator, component_id):
        for component_id in iterator:
            yield self.component_manager.remove(component_id)
        self.package_manager.update()

    @multi_response
    def settings_get(self, iterator):
        # *** IMPORTANT *** Our UCR copy must always be current. This is not only
        #	to catch up changes made via other channels (ucr command line etc),
        #	but also to reflect the changes we have made ourselves!
        self.ucr.load()

        for _ in iterator:
            yield {
                'maintained':
                self.ucr.is_true('repository/online/maintained', False),
                'unmaintained':
                self.ucr.is_true('repository/online/unmaintained', False),
                'server':
                self.ucr.get('repository/online/server', ''),
                'prefix':
                self.ucr.get('repository/online/prefix', ''),
            }

    @sanitize_list(
        DictSanitizer({'object': basic_components_sanitizer}),
        min_elements=1,
        max_elements=1  # moduleStore with one element...
    )
    @multi_response
    def settings_put(self, iterator, object):
        # FIXME: returns values although it should yield (multi_response)
        changed = False
        # Set values into our UCR copy.
        try:
            with util.set_save_commit_load(self.ucr) as super_ucr:
                for object, in iterator:
                    for key, value in object.iteritems():
                        MODULE.info(
                            "   ++ Setting new value for '%s' to '%s'" %
                            (key, value))
                        super_ucr.set_registry_var(
                            '%s/%s' % (constants.ONLINE_BASE, key), value)
                changed = super_ucr.changed()
        except Exception as e:
            MODULE.warn("   !! Writing UCR failed: %s" % str(e))
            return [{'message': str(e), 'status': constants.PUT_WRITE_ERROR}]

        self.package_manager.update()

        # Bug #24878: emit a warning if repository is not reachable
        try:
            updater = self.uu
            for line in updater.print_version_repositories().split('\n'):
                if line.strip():
                    break
            else:
                raise ConfigurationError()
        except ConfigurationError:
            msg = _(
                "There is no repository at this server (or at least none for the current UCS version)"
            )
            MODULE.warn("   !! Updater error: %s" % msg)
            response = {'message': msg, 'status': constants.PUT_UPDATER_ERROR}
            # if nothing was committed, we want a different type of error code,
            # just to appropriately inform the user
            if changed:
                response['status'] = constants.PUT_UPDATER_NOREPOS
            return [response]
        except:
            info = sys.exc_info()
            emsg = '%s: %s' % info[:2]
            MODULE.warn("   !! Updater error [%s]: %s" % (emsg))
            return [{
                'message': str(info[1]),
                'status': constants.PUT_UPDATER_ERROR
            }]
        return [{'status': constants.PUT_SUCCESS}]
コード例 #13
0
class Instance(Base):
    def init(self):
        # set the language in order to return the correctly localized labels/descriptions
        uit.set_language(self.locale.language)

    def __create_variable_info(self, options):
        all_info = ConfigRegistryInfo(registered_only=False)
        info = ConfigRegistryInfo(install_mode=True)
        info.read_customized()
        var = Variable()

        # description
        for line in options['descriptions']:
            text = line['text']
            if not text: continue
            if 'lang' in line:
                var['description[%s]' % line['lang']] = text
            else:
                var['description'] = text
        # categories
        if options['categories']:
            var['categories'] = ','.join(options['categories'])

        # type
        var['type'] = options['type']

        # are there any modifications?
        old_value = all_info.get_variable(options['key'])
        if old_value != var:
            # save
            info.add_variable(options['key'], var)
            info.write_customized()

    def is_readonly(self, key):
        ucrinfo_system = ConfigRegistryInfo(registered_only=False,
                                            load_customized=False)
        var = ucrinfo_system.get_variable(key)
        if var:
            return var.get('readonly') in ('yes', '1', 'true')
        return False

    def add(self, request):
        # does the same as put
        self.put(request)

    def put(self, request):
        message = ''
        request.status = SUCCESS
        success = True
        if isinstance(request.options, (list, tuple)):
            for _var in request.options:
                try:
                    var = _var['object']
                    value = var['value'] or ''
                    key = var['key']
                    if self.is_readonly(key):
                        success = False
                        message = _(
                            'The UCR variable %s is read-only and can not be changed!'
                        ) % key
                        break
                    arg = ['%s=%s' % (key.encode(), value.encode())]
                    ucr.handler_set(arg)

                    # handle descriptions, type, and categories
                    if 'descriptions' in var or 'type' in var or 'categories' in var:
                        self.__create_variable_info(var)
                except KeyError:
                    # handle the case that neither key nor value are given for an UCR variable entry
                    request.status = BAD_REQUEST_INVALID_OPTS
                    self.finished(
                        request.id,
                        False,
                        message=
                        _('Invalid UCR variable entry, the properties "key" and "value" need to specified.'
                          ))
                    return
        else:
            success = False
            request.status = BAD_REQUEST_INVALID_OPTS

        self.finished(request.id, success, message)

    def remove(self, request):
        variables = filter(lambda x: x is not None,
                           map(lambda x: x.get('object'), request.options))
        for var in variables:
            if self.is_readonly(var):
                message = _(
                    'The UCR variable %s is read-only and can not be removed!'
                ) % var
                self.finished(request.id, False, message)
                return

        ucr.handler_unset(variables)
        self.finished(request.id, True)

    def get(self, request):
        ucrReg = ucr.ConfigRegistry()
        ucrReg.load()
        ucrInfo = ConfigRegistryInfo(registered_only=False)

        # iterate over all requested variables
        results = []
        for key in request.options:
            info = ucrInfo.get_variable(str(key))
            value = ucrReg.get(str(key))
            if not info and (value or '' == value):
                # only the value available
                results.append({'key': key, 'value': value})
            elif info:
                # info (categories etc.) available
                info['value'] = value
                info['key'] = key
                results.append(info.normalize())
            else:
                # variable not available, request failed
                request.status = BAD_REQUEST_INVALID_OPTS
                self.finished(
                    request.id,
                    False,
                    message=_('The UCR variable %(key)s could not be found') %
                    {'key': key})
                return
        self.finished(request.id, results)

    def categories(self, request):
        ucrInfo = ConfigRegistryInfo(registered_only=False)
        categories = []
        for id, obj in ucrInfo.categories.iteritems():
            name = obj['name']
            categories.append({'id': id, 'label': name})
        self.finished(request.id, categories)

    @sanitize(pattern=PatternSanitizer(default='.*'),
              key=ChoicesSanitizer(['all', 'key', 'value', 'description'],
                                   required=True))
    @simple_response
    def query(self, pattern, key, category=None):
        '''Returns a dictionary of configuration registry variables
		found by searching for the (wildcard) expression defined by the
		UMCP request. Additionally a list of configuration registry
		categories can be defined.

		The dictionary returned is compatible with the Dojo data store
		format.'''
        variables = []
        if category == 'all':
            # load _all_ config registry variables
            base_info = ConfigRegistryInfo(registered_only=False)
        else:
            # load _all registered_ config registry variables
            base_info = ConfigRegistryInfo()

        if category in ('all', 'all-registered'):
            category = None

        def _match_value(name, var):
            return var.value and pattern.match(var.value)

        def _match_key(name, var):
            return pattern.match(name)

        def _match_description(name, var):
            descr = var.get('description')
            return descr and pattern.match(descr)

        def _match_all(name, var):
            return _match_value(name, var) or _match_description(
                name, var) or _match_key(name, var)

        func = eval('_match_%s' % key)
        for name, var in base_info.get_variables(category).iteritems():
            if func(name, var):
                variables.append({'key': name, 'value': var.value})

        return variables
コード例 #14
0
ファイル: __init__.py プロジェクト: spaceone/ucs-school
class Instance(SchoolBaseModule):

	def __init__(self):
		SchoolBaseModule.__init__(self)
		self._tmpDir = None

	def init(self):
		SchoolBaseModule.init(self)
		# initiate paths for data distribution
		util.initPaths()

	def destroy(self):
		self._cleanTmpDir()

	def _cleanTmpDir(self):
		# clean up the temporary upload directory
		if self._tmpDir:
			MODULE.info('Clean up temporary directory: %s' % self._tmpDir)
			shutil.rmtree(self._tmpDir, ignore_errors=True)
			self._tmpDir = None

	@file_upload
	@sanitize(DictSanitizer(dict(
		filename=StringSanitizer(required=True),
		tmpfile=StringSanitizer(required=True),
	), required=True))
	def upload(self, request):
		# create a temporary upload directory, if it does not already exist
		if not self._tmpDir:
			self._tmpDir = tempfile.mkdtemp(prefix='ucsschool-distribution-upload-')
			MODULE.info('Created temporary directory: %s' % self._tmpDir)

		for file in request.options:
			filename = self.__workaround_filename_bug(file)
			destPath = os.path.join(self._tmpDir, filename)
			MODULE.info('Received file %r, saving it to %r' % (file['tmpfile'], destPath))
			shutil.move(file['tmpfile'], destPath)

		self.finished(request.id, None)

	def __workaround_filename_bug(self, file):
		# the following code block is a heuristic to support both: fixed and unfixed Bug #37716
		filename = file['filename']
		try:
			# The UMC-Webserver decodes filename in latin-1, need to revert
			filename = filename.encode('ISO8859-1')
		except UnicodeEncodeError:
			# we got non-latin characters, Bug #37716 is fixed and string contains e.g. '→'
			filename = file['filename'].encode('UTF-8')
		else:
			# the string contains at least no non-latin1 characters
			try:
				# try if the bytes could be UTF-8
				# can't fail if Bug #37716 is fixed
				filename.decode('UTF-8')
			except UnicodeDecodeError:
				filename = file['filename'].encode('UTF-8')  # Bug #37716 was fixed
		MODULE.info('Detected filename %r as %r' % (file['filename'], filename))
		# the code block can be removed and replaced by filename = file['filename'].encode('UTF-8') after Bug #37716
		return filename

	@sanitize(
		filenames=ListSanitizer(min_elements=1),
		# project=StringSanitizer(allow_none=True)
	)
	@simple_response
	def checkfiles(self, project, filenames):
		'''Checks whether the given filename has already been uploaded:

		request.options: { 'filenames': [ '...', ... ], project: '...' }

		returns: {
			'filename': '...',
			'sessionDuplicate': True|False,
			'projectDuplicate': True|False,
			'distributed': True|False
		}
		'''

		# load project
		if project:
			project = util.Project.load(project)

		result = []
		for ifile in filenames:
			ifile = ifile.encode('UTF-8')
			# check whether file has already been upload in this session
			iresult = dict(sessionDuplicate=False, projectDuplicate=False, distributed=False)
			iresult['filename'] = ifile
			iresult['sessionDuplicate'] = self._tmpDir is not None and os.path.exists(os.path.join(self._tmpDir, ifile))

			# check whether the file exists in the specified project and whether
			# it has already been distributed
			if project:
				iresult['projectDuplicate'] = ifile in project.files
				iresult['distributed'] = ifile in project.files and not os.path.exists(os.path.join(project.cachedir, ifile))
			result.append(iresult)
		return result

	@sanitize(
		pattern=PatternSanitizer(required=False, default='.*'),
		filter=ChoicesSanitizer(['all', 'private'], default='private')
	)
	@simple_response
	def query(self, pattern, filter):
		result = [dict(
			# only show necessary information
			description=i.description,
			name=i.name,
			sender=i.sender.username,
			recipients=len(i.recipients),
			files=len(i.files),
			isDistributed=i.isDistributed
		) for i in util.Project.list()
			if (pattern.match(i.name) or pattern.match(i.description)) and (filter == 'all' or compare_dn(i.sender.dn, self.user_dn))
		]
		return result

	@LDAP_Connection()
	def _get_sender(self, ldap_user_read=None, ldap_position=None):
		'''Return a User instance of the currently logged in user.'''
		try:
			user = User.from_dn(self.user_dn, None, ldap_user_read)
			obj = user.get_udm_object(ldap_user_read)
			return util.User(obj.info, dn=obj.dn)
		except udm_exceptions.base as exc:
			raise UMC_Error(_('Failed to load user information: %s') % exc)

	@sanitize(DictSanitizer(dict(object=DictSanitizer({}, required=True)), required=True))
	def put(self, request):
		"""Modify an existing project"""
		result = [self._save(entry['object'], True) for entry in request.options]
		self.finished(request.id, result)

	@sanitize(DictSanitizer(dict(object=DictSanitizer({}, required=True)), required=True))
	def add(self, request):
		"""Add a new project"""
		result = [self._save(entry['object'], False) for entry in request.options]
		self.finished(request.id, result)

	@LDAP_Connection()
	def _save(self, iprops, doUpdate=True, ldap_user_read=None, ldap_position=None):
		# try to open the UDM user object of the current user
		sender = self._get_sender()

		try:
			# remove keys that may not be set from outside
			for k in ('atJobNumCollect', 'atJobNumDistribute'):
				iprops.pop(k, None)

			# transform filenames into bytestrings
			iprops['files'] = [f.encode('UTF-8') for f in iprops.get('files', [])]

			# load the project or create a new one
			project = None
			orgProject = None
			if doUpdate:
				# try to load the given project
				orgProject = util.Project.load(iprops.get('name', ''))
				if not orgProject:
					raise UMC_Error(_('The specified project does not exist: %s') % iprops['name'])

				# create a new project with the updated values
				project = util.Project(orgProject.dict)
				project.update(iprops)
			else:
				# create a new project
				project = util.Project(iprops)

			# make sure that the project owner himself is modifying the project
			if doUpdate and not compare_dn(project.sender.dn, self.user_dn):
				raise UMC_Error(_('The project can only be modified by the owner himself'))

			# handle time settings for distribution/collection of project files
			for jsuffix, jprop, jname in (('distribute', 'starttime', _('Project distribution')), ('collect', 'deadline', _('Project collection'))):
				if '%sType' % jsuffix in iprops:
					# check the distribution/collection type: manual/automat
					jtype = (iprops['%sType' % jsuffix]).lower()
					if jtype == 'automatic':
						try:
							# try to parse the given time parameters
							strtime = '%s %s' % (iprops['%sDate' % jsuffix], iprops['%sTime' % jsuffix])
							jdate = datetime.strptime(strtime, '%Y-%m-%d %H:%M')
							setattr(project, jprop, jdate)
						except ValueError:
							raise UMC_Error(_('Could not set date for: %s') % jname)

						# make sure the execution time lies sufficiently in the future
						if getattr(project, jprop) - datetime.now() < timedelta(minutes=1):
							raise UMC_Error(_('The specified time needs to lie in the future for: %s') % jname)
					else:
						# manual distribution/collection
						setattr(project, jprop, None)

			if project.starttime and project.deadline:
				# make sure distributing happens before collecting
				if project.deadline - project.starttime < timedelta(minutes=3):
					raise UMC_Error(_('Distributing the data needs to happen sufficiently long enough before collecting them'))

			if 'recipients' in iprops:
				# lookup the users in LDAP and save them to the project
				project.recipients = [util.openRecipients(idn, ldap_user_read) for idn in iprops.get('recipients', [])]
				project.recipients = [x for x in project.recipients if x]
				MODULE.info('recipients: %s' % (project.recipients,))

			if not doUpdate:
				# set the sender (i.e., owner) of the project
				project.sender = sender

			# initiate project and validate its values
			project.validate()

			# make sure that there is no other project with the same directory name
			# if we add new projects
			if not doUpdate and project.isNameInUse():
				MODULE.error('The project name is already in use: %s' % (project.name))
				raise UMC_Error(_('The specified project directory name "%s" is already in use by a different project.') % (project.name))

			# try to save project to disk
			project.save()

			# move new files into project directory
			if self._tmpDir:
				for ifile in project.files:
					isrc = os.path.join(self._tmpDir, ifile)
					itarget = os.path.join(project.cachedir, ifile)
					if os.path.exists(isrc):
						# mv file to cachedir
						shutil.move(isrc, itarget)
						os.chown(itarget, 0, 0)

			# remove files that have been marked for removal
			if doUpdate:
				for ifile in set(orgProject.files) - set(project.files):
					itarget = os.path.join(project.cachedir, ifile)
					try:
						os.remove(itarget)
					except OSError:
						pass

			# re-distribute the project in case it has already been distributed
			if doUpdate and project.isDistributed:
				usersFailed = []
				project.distribute(usersFailed)

				if usersFailed:
					# not all files could be distributed
					MODULE.info('Failed processing the following users: %s' % usersFailed)
					usersStr = ', '.join([Display.user(i) for i in usersFailed])
					raise UMC_Error(_('The project could not distributed to the following users: %s') % usersStr)
		except (IOError, OSError, UMC_Error):  # TODO: catch only UMC_Error
			etype, exc, etraceback = sys.exc_info()
			# data not valid... create error info
			MODULE.info('data for project "%s" is not valid: %s' % (iprops.get('name'), exc))

			if not doUpdate:
				# remove eventually created project file and cache dir
				for ipath in (project.projectfile, project.cachedir):
					if os.path.basename(ipath) not in os.listdir(util.DISTRIBUTION_DATA_PATH):
						# no file / directory has been created yet
						continue
					try:
						MODULE.info('cleaning up... removing: %s' % ipath)
						shutil.rmtree(ipath)
					except (IOError, OSError):
						pass
			raise UMC_Error, exc, etraceback
		self._cleanTmpDir()
		return {'success': True, 'name': iprops.get('name')}

	@sanitize(StringSanitizer(required=True))
	@LDAP_Connection()
	def get(self, request, ldap_user_read=None, ldap_position=None):
		"""Returns the objects for the given IDs

		requests.options = [ <ID>, ... ]

		return: [ { ... }, ... ]
		"""
		# try to load all given projects
		result = []
		# list of all project properties (dicts) or None if project is not valid
		for iproject in [util.Project.load(iid) for iid in request.options]:
			# make sure that project could be loaded
			if not iproject:
				result.append(None)
				continue

			# make sure that only the project owner himself (or an admin) is able
			# to see the content of a project
			if request.flavor == 'teacher' and not compare_dn(iproject.sender.dn, self.user_dn):
				raise UMC_Error(_('Project details are only visible to the project owner himself or an administrator.'), status=403)

			# prepare date and time properties for distribution/collection of project files
			props = iproject.dict
			for jjob, jsuffix in ((iproject.atJobDistribute, 'distribute'), (iproject.atJobCollect, 'collect')):
				MODULE.info('check job: %s' % jsuffix)
				if not jjob:
					# no job is registered -> manual job distribution/collection
					MODULE.info('no existing job -> manual execution')
					props['%sType' % jsuffix] = 'manual'
					continue

				# job is registered -> prepare date and time fields
				MODULE.info('job nr #%d scheduled for %s -> automatic execution' % (jjob.nr, jjob.execTime))
				props['%sType' % jsuffix] = 'automatic'
				props['%sDate' % jsuffix] = datetime.strftime(jjob.execTime, '%Y-%m-%d')
				props['%sTime' % jsuffix] = datetime.strftime(jjob.execTime, '%H:%M')

			# adjust sender / recipients properties
			props['sender'] = props['sender'].username
			recipients = []
			for recip in props['recipients']:
				recipients.append({
					'id': recip.dn,
					'label': recip.type == util.TYPE_USER and Display.user(recip.dict) or recip.name
				})
			props['recipients'] = recipients

			# append final dict to result list
			MODULE.info('final project dict: %s' % props)
			result.append(props)
		self.finished(request.id, result)

	@sanitize(StringSanitizer(required=True))
	def distribute(self, request):
		# update the sender information of the selected projects
		result = []
		for iid in request.options:
			MODULE.info('Distribute project: %s' % iid)
			try:
				# make sure that project could be loaded
				iproject = util.Project.load(iid)
				if not iproject:
					raise IOError(_('Project "%s" could not be loaded') % iid)

				# make sure that only the project owner himself (or an admin) is able
				# to distribute a project
				if request.flavor == 'teacher' and not compare_dn(iproject.sender.dn, self.user_dn):
					raise ValueError(_('Only the owner himself or an administrator may distribute a project.'))

				# project was loaded successfully... try to distribute it
				usersFailed = []
				iproject.distribute(usersFailed)

				# raise an error in case distribution failed for some users
				if usersFailed:
					MODULE.info('Failed processing the following users: %s' % usersFailed)
					usersStr = ', '.join([Display.user(i) for i in usersFailed])
					raise IOError(_('The project could not distributed to the following users: %s') % usersStr)

				# save result
				result.append(dict(
					name=iid,
					success=True
				))
			except (ValueError, IOError) as exc:
				result.append(dict(
					name=iid,
					success=False,
					details=str(exc)
				))

		# return the results
		self.finished(request.id, result)

	@sanitize(StringSanitizer(required=True))
	def collect(self, request):
		# try to open the UDM user object of the current user
		sender = self._get_sender()

		# update the sender information of the selected projects
		result = []
		for iid in request.options:
			MODULE.info('Collect project: %s' % iid)
			try:
				# make sure that project could be loaded
				iproject = util.Project.load(iid)
				if not iproject:
					raise IOError(_('Project "%s" could not be loaded') % iid)

				# replace the projects sender with the current logged in user
				iproject.sender = sender

				# project was loaded successfully... try to distribute it
				dirsFailed = []
				iproject.collect(dirsFailed)

				# raise an error in case distribution failed for some users
				if dirsFailed:
					dirsStr = ', '.join(dirsFailed)
					MODULE.info('Failed collecting the following dirs: %s' % dirsStr)
					raise IOError(_('The following user directories could not been collected: %s') % dirsStr)

				# save result
				result.append(dict(
					name=iid,
					success=True
				))
			except (ValueError, IOError) as exc:
				result.append(dict(
					name=iid,
					success=False,
					details=str(exc)
				))

		# return the results
		self.finished(request.id, result)

	@sanitize(StringSanitizer(required=True))
	def adopt(self, request):
		# try to open the UDM user object of the current user
		sender = self._get_sender()

		# update the sender information of the selected projects
		result = []
		for iid in request.options:
			try:
				# make sure that project could be loaded
				iproject = util.Project.load(iid)
				if not iproject:
					raise IOError(_('Project "%s" could not be loaded') % iid)

				# project was loaded successfully
				iproject.sender = sender
				iproject.save()
			except (ValueError, IOError) as exc:
				result.append(dict(
					name=iid,
					success=False,
					details=str(exc)
				))

		# return the results
		self.finished(request.id, result)

	@sanitize(DictSanitizer(dict(object=StringSanitizer(required=True)), required=True))
	def remove(self, request):
		"""Removes the specified projects"""
		for iproject in [util.Project.load(ientry.get('object')) for ientry in request.options]:
			if not iproject:
				continue

			# make sure that only the project owner himself (or an admin) is able
			# to see the content of a project
			if request.flavor == 'teacher' and not compare_dn(iproject.sender.dn, self.user_dn):
				raise UMC_Error(_('Only the owner himself or an administrator may delete a project.'), status=403)

			# purge the project
			iproject.purge()

		self.finished(request.id, None)
コード例 #15
0
ファイル: __init__.py プロジェクト: bopopescu/smart-1
class Instance(umcm.Base):
    def _run_it(self, services, action):
        failed = []
        for srv in services:
            if subprocess.call(('/usr/sbin/invoke-rc.d', srv, action)):
                failed.append(srv)
        return failed

    @sanitize(pattern=PatternSanitizer(default='.*'))
    @simple_response
    def query(self, pattern):
        srvs = usi.ServiceInfo()
        ucr = univention.config_registry.ConfigRegistry()
        ucr.load()

        result = []
        for name, srv in srvs.services.items():
            entry = {}
            entry['service'] = name
            if 'description' in srv:
                try:
                    lang = _.im_self.locale.language
                    if lang in (None, 'C'):
                        lang = 'en'
                    entry['description'] = srv['description[%s]' % lang]
                except KeyError:
                    entry['description'] = srv['description']
            else:
                entry['description'] = None
            key = '%s/autostart' % name
            if 'start_type' in srv:
                key = srv['start_type']
            # default: autostart=yes
            if not ucr.get(key):
                entry['autostart'] = 'yes'
            elif ucr.get(key).lower() in ('no'):
                entry['autostart'] = 'no'
            elif ucr.get(key).lower() in ('manually'):
                entry['autostart'] = 'manually'
            else:
                entry['autostart'] = 'yes'
            # Check if service is running
            if srv.running:
                entry['isRunning'] = True
            else:
                entry['isRunning'] = False
            for value in entry.values():
                if pattern.match(str(value)):
                    result.append(entry)
                    break

        return result

    def start(self, request):
        if self.permitted('services/start', request.options):
            message = {}
            message['success'] = _('Successfully started')
            message['failed'] = _('Starting the following services failed:')
            cb = notifier.Callback(self._service_changed, request, message)
            func = notifier.Callback(self._run_it, request.options, 'start')
            thread = notifier.threads.Simple('services', func, cb)
            thread.run()
        else:
            message = _('You are not permitted to run this command.')
            request.status = MODULE_ERR
            self.finished(request.id, None, message)

    def stop(self, request):
        if self.permitted('services/stop', request.options):
            message = {}
            message['success'] = _('Successfully stopped')
            message['failed'] = _('Stopping the following services failed:')
            cb = notifier.Callback(self._service_changed, request, message)
            func = notifier.Callback(self._run_it, request.options, 'stop')
            thread = notifier.threads.Simple('services', func, cb)
            thread.run()
        else:
            message = _('You are not permitted to run this command.')
            request.status = MODULE_ERR
            self.finished(request.id, None, message)

    def restart(self, request):
        MODULE.error(str(request.arguments))
        if self.permitted('services/restart', request.options):
            message = {}
            message['success'] = _('Successfully restarted')
            message['failed'] = _('Restarting the following services failed:')
            cb = notifier.Callback(self._service_changed, request, message)
            func = notifier.Callback(self._run_it, request.options, 'restart')
            thread = notifier.threads.Simple('services', func, cb)
            thread.run()
        else:
            message = _('You are not permitted to run this command.')
            request.status = MODULE_ERR
            self.finished(request.id, None, message)

    def _service_changed(self, thread, result, request, message):
        if result:
            if len(request.options) == 1:
                error_message = '%s %s' % (message['failed'], result[0])
                request.status = MODULE_ERR
                self.finished(request.id, {'success': False}, error_message)
            else:
                request.status = SUCCESS
                self.finished(request.id, {
                    'objects': result,
                    'success': False
                })
        else:
            request.status = SUCCESS
            self.finished(request.id, {'success': True}, message['success'])

    def start_type(self, request):
        message = None
        srvs = usi.ServiceInfo()

        failed = []
        for name in request.options:
            srv = srvs.services.get(name, None)
            if srv:
                key = '%s/autostart' % name
                if 'start_type' in srv:
                    key = srv['start_type']

                value = 'yes'
                if request.arguments[0] == 'services/start_auto':
                    value = 'yes'
                if request.arguments[0] == 'services/start_manual':
                    value = 'manually'
                if request.arguments[0] == 'services/start_never':
                    value = 'no'
                univention.config_registry.handler_set(
                    ['%s=%s' % (key, value)])
            else:
                failed.append(name)

        if failed:
            if len(request.options) == 1:
                message = _('Could not change start type')
                request.status = MODULE_ERR
                self.finished(request.id, {'success': False}, message)
            else:
                request.status = SUCCESS
                self.finished(request.id, {
                    'objects': failed,
                    'success': False
                })
        else:
            message = _('Successfully changed start type')
            request.status = SUCCESS
            self.finished(request.id, {'success': True}, message)
コード例 #16
0
ファイル: __init__.py プロジェクト: bopopescu/smart-1
class Instance(umcm.Base):
    @sanitize(pattern=PatternSanitizer(default='.*'))
    def query(self, request):
        category = request.options.get('category', 'all')
        pattern = request.options.get('pattern')
        processes = []
        for process in psutil.process_iter():
            listEntry = {}
            # Temporary variables; used to calculate cpu percentage
            listEntry['timestamp'] = []
            listEntry['cpu_time'] = []
            listEntry['timestamp'].append(time.time())
            (
                user_time,
                system_time,
            ) = process.get_cpu_times()
            listEntry['cpu_time'].append(user_time + system_time)

            listEntry['user'] = process.username
            listEntry['pid'] = process.pid
            listEntry['cpu'] = 0.0
            listEntry['mem'] = process.get_memory_percent()
            listEntry['command'] = ' '.join(process.cmdline)
            if listEntry['command'] == '':
                listEntry['command'] = process.name
            if category == 'all':
                for value in listEntry.itervalues():
                    if pattern.match(str(value)):
                        processes.append(listEntry)
                        break
            else:
                if pattern.match(str(listEntry[category])):
                    processes.append(listEntry)

        # Calculate correct cpu percentage
        time.sleep(1)
        for process_entry in processes:
            try:
                process = psutil.Process(process_entry['pid'])
            except psutil.NoSuchProcess:
                pass
            else:
                process_entry['timestamp'].append(time.time())
                (
                    user_time,
                    system_time,
                ) = process.get_cpu_times()
                process_entry['cpu_time'].append(user_time + system_time)

                elapsed_time = process_entry['timestamp'][1] - process_entry[
                    'timestamp'][0]
                elapsed_cpu_time = process_entry['cpu_time'][
                    1] - process_entry['cpu_time'][0]
                cpu_percent = (elapsed_cpu_time / elapsed_time) * 100
                process_entry['cpu'] = cpu_percent
                # Cleanup request result
                del process_entry['timestamp']
                del process_entry['cpu_time']

        request.status = SUCCESS
        self.finished(request.id, processes)

    def kill(self, request):
        failed = []
        message = ''
        signal = request.options.get('signal', 'SIGTERM')
        pidList = request.options.get('pid', [])
        for pid in pidList:
            try:
                process = psutil.Process(int(pid))
                if signal == 'SIGTERM':
                    process.kill(15)
                elif signal == 'SIGKILL':
                    process.kill(9)
            except psutil.NoSuchProcess, error:
                failed.append(pid)
                MODULE.error(str(error))
        if not failed:
            request.status = SUCCESS
            success = True
        else:
            request.status = MODULE_ERR
            failed = ', '.join(failed)
            message = _('No process found with PID %s') % (failed)
            success = False
        self.finished(request.id, success, message=message)
コード例 #17
0
class Commands(object):

	@sanitize(
		partitionDevice=StringSanitizer(required=True),
		filter=PatternSanitizer(default='.*'),
	)
	def users_query(self, request):
		partitionDevice = request.options['partitionDevice']
		self._check_error(request, partitionDevice)

		callback = notifier.Callback(self._users_query, request.id, partitionDevice, request)
		tools.repquota(request.options['partitionDevice'], callback)

	def _users_query(self, pid, status, callbackResult, id, partition, request):
		'''This function is invoked when a repquota process has died and
		there is output to parse that is restructured as UMC Dialog'''
		# general information
		devs = fstab.File()
		devs.find(spec=partition)

		# skip header
		header = 0
		try:
			while not callbackResult[header].startswith('----'):
				header += 1
		except IndexError:
			pass
		quotas = tools.repquota_parse(partition, callbackResult[header + 1:])
		result = [q for q in quotas if request.options['filter'].match(q['user'])]
		self.finished(request.id, result)

	@sanitize(
		partitionDevice=StringSanitizer(required=True),
		user=StringSanitizer(required=True),
		sizeLimitSoft=LimitSanitizer(default=0, required=True),
		sizeLimitHard=LimitSanitizer(default=0, required=True),
		fileLimitSoft=LimitSanitizer(default=0, required=True),
		fileLimitHard=LimitSanitizer(default=0, required=True),
	)
	def users_set(self, request):
		def _thread(request):
			partition = request.options['partitionDevice']
			user = request.options['user']
			if isinstance(user, unicode):
				user = user.encode('utf-8')

			size_soft = request.options['sizeLimitSoft']
			size_hard = request.options['sizeLimitHard']
			file_soft = request.options['fileLimitSoft']
			file_hard = request.options['fileLimitHard']
			self._check_error(request, partition)

			if tools.setquota(partition, user, tools.byte2block(size_soft), tools.byte2block(size_hard), file_soft, file_hard):
				raise UMC_Error(_('Failed to modify quota settings for user %(user)s on partition %(partition)s.') % {'user': user, 'partition': partition})

			return {'objects': [], 'success': True}

		thread = notifier.threads.Simple('Set', notifier.Callback(_thread, request), notifier.Callback(self.thread_finished_callback, request))
		thread.run()

	def users_remove(self, request):
		def _thread(request):
			partitions = []
			success = True
			objects = []

			# Determine different partitions
			for obj in request.options:
				partitions.append(obj['object'].split('@')[-1])
			for partition in set(partitions):
				self._check_error(request, partition)

			# Remove user quota
			for obj in request.options:
				(unicode_user, partition) = obj['object'].split('@', 1)
				user = unicode_user.encode('utf-8')
				if tools.setquota(partition, user, 0, 0, 0, 0):
					objects.append({'id': obj['object'], 'success': False})
					success = False
				else:
					objects.append({'id': obj['object'], 'success': True})

			return {'objects': objects, 'success': success}

		thread = notifier.threads.Simple('Remove', notifier.Callback(_thread, request), notifier.Callback(self.thread_finished_callback, request))
		thread.run()

	def _check_error(self, request, partition_name):  # TODO
		try:
			fs = fstab.File()
			mt = mtab.File()
		except IOError as error:
			MODULE.error('Could not open %s' % error.filename)
			raise ValueError(_('Could not open %s') % error.filename)

		partition = fs.find(spec=partition_name)
		if partition:
			mounted_partition = mt.get(partition.spec)
			if mounted_partition:
				if 'usrquota' not in mounted_partition.options and 'usrjquota=aquota.user' not in mounted_partition.options:
					raise UMC_Error(_('The following partition is mounted without quota support: %s') % partition_name)
			else:
				raise UMC_Error(_('The following partition is currently not mounted: %s') % partition_name)
		else:
			raise UMC_Error(_('No partition found (%s)') % partition_name)
コード例 #18
0
class Instance(umcm.Base, ProgressMixin):

	def init(self):
		os.umask(0o022)  # umc umask is too restrictive for app center as it creates a lot of files in docker containers
		self.ucr = ucr_instance()

		self.update_applications_done = False
		util.install_opener(self.ucr)
		self._remote_progress = {}

		try:
			self.package_manager = PackageManager(
				info_handler=MODULE.process,
				step_handler=None,
				error_handler=MODULE.warn,
				lock=False,
			)
		except SystemError as exc:
			MODULE.error(str(exc))
			raise umcm.UMC_Error(str(exc), status=500)
		self.package_manager.set_finished()  # currently not working. accepting new tasks
		self.uu = UniventionUpdater(False)
		self.component_manager = util.ComponentManager(self.ucr, self.uu)
		get_package_manager._package_manager = self.package_manager

		# in order to set the correct locale for Application
		locale.setlocale(locale.LC_ALL, str(self.locale))

		try:
			log_to_logfile()
		except IOError:
			pass

		# connect univention.appcenter.log to the progress-method
		handler = ProgressInfoHandler(self.package_manager)
		handler.setLevel(logging.INFO)
		get_base_logger().addHandler(handler)

		percentage = ProgressPercentageHandler(self.package_manager)
		percentage.setLevel(logging.DEBUG)
		get_base_logger().getChild('actions.install.progress').addHandler(percentage)
		get_base_logger().getChild('actions.upgrade.progress').addHandler(percentage)
		get_base_logger().getChild('actions.remove.progress').addHandler(percentage)

	def error_handling(self, etype, exc, etraceback):
		error_handling(etype, exc, etraceback)
		return super(Instance, self).error_handling(exc, etype, etraceback)

	@simple_response
	def version(self):
		info = get_action('info')
		return info.get_compatibility()

	@simple_response
	def query(self, quick=False):
		if not quick:
			self.update_applications()
		self.ucr.load()
		reload_package_manager()
		list_apps = get_action('list')
		domain = get_action('domain')
		apps = list_apps.get_apps()
		if self.ucr.is_true('appcenter/docker', True):
			if not self._test_for_docker_service():
				raise umcm.UMC_Error(_('The docker service is not running! The App Center will not work properly.') + ' ' + _('Make sure docker.io is installed, try starting the service with "service docker start".'))
		info = domain.to_dict(apps)
		if quick:
			ret = []
			for app in info:
				if app is None:
					ret.append(None)
				else:
					short_info = {}
					for attr in ['id', 'name', 'vendor', 'maintainer', 'description', 'long_description', 'categories', 'end_of_life', 'update_available', 'logo_name', 'is_installed_anywhere', 'is_installed', 'installations']:
						short_info[attr] = app[attr]
					ret.append(short_info)
			return ret
		else:
			return info

	def update_applications(self):
		if self.ucr.is_true('appcenter/umc/update/always', True):
			update = get_action('update')
			try:
				update.call()
			except NetworkError as err:
				raise umcm.UMC_Error(str(err))
			except Abort:
				pass
			Application._all_applications = None
			self.update_applications_done = True

	def _test_for_docker_service(self):
		if docker_bridge_network_conflict():
			msg = _('A conflict between the system network settings and the docker bridge default network has been detected.') + '\n\n'
			msg += _('Please either configure a different network for the docker bridge by setting the UCR variable docker/daemon/default/opts/bip to a different network and restart the system,') + ' '
			msg += _('or disable the docker support in the AppCenter by setting appcenter/docker to false.')
			raise umcm.UMC_Error(msg)
		if not docker_is_running():
			MODULE.warn('Docker is not running! Trying to start it now...')
			call_process(['invoke-rc.d', 'docker', 'start'])
			if not docker_is_running():
				return False
		return True

	@simple_response
	def enable_docker(self):
		if self._test_for_docker_service():
			ucr_save({'appcenter/docker': 'enabled'})
		else:
			raise umcm.UMC_Error(_('Unable to start the docker service!') + ' ' + _('Make sure docker.io is installed, try starting the service with "service docker start".'))

	@require_apps_update
	@require_password
	@simple_response(with_progress=True)
	def sync_ldap(self):
		register = get_action('register')
		register.call(username=self.username, password=self.password)

	# used in updater-umc
	@simple_response
	def get_by_component_id(self, component_id):
		domain = get_action('domain')
		if isinstance(component_id, list):
			requested_apps = [Apps().find_by_component_id(cid) for cid in component_id]
			return domain.to_dict(requested_apps)
		else:
			app = Apps().find_by_component_id(component_id)
			if app:
				return domain.to_dict([app])[0]
			else:
				raise umcm.UMC_Error(_('Could not find an application for %s') % component_id)

	# used in updater-umc
	@simple_response
	def app_updates(self):
		upgrade = get_action('upgrade')
		domain = get_action('domain')
		return domain.to_dict(list(upgrade.iter_upgradable_apps()))

	@sanitize(application=StringSanitizer(minimum=1, required=True))
	@simple_response
	def get(self, application):
		domain = get_action('domain')
		app = Apps().find(application)
		if app is None:
			raise umcm.UMC_Error(_('Could not find an application for %s') % (application,))
		return domain.to_dict([app])[0]

	@sanitize(app=AppSanitizer(required=True), values=DictSanitizer({}))
	@simple_response
	def configure(self, app, autostart, values):
		configure = get_action('configure')
		configure.call(app=app, set_vars=values, autostart=autostart)

	@sanitize(app=AppSanitizer(required=True), mode=ChoicesSanitizer(['start', 'stop']))
	@simple_response
	def app_service(self, app, mode):
		service = get_action(mode)
		service.call(app=app)

	@sanitize(app=AppSanitizer(required=False), action=ChoicesSanitizer(['get', 'buy', 'search']), value=StringSanitizer())
	@simple_response
	def track(self, app, action, value):
		send_information(action, app=app, value=value)

	def invoke_dry_run(self, request):
		request.options['only_dry_run'] = True
		self.invoke(request)

	@require_password
	@sanitize(
		host=StringSanitizer(required=True),
		function=ChoicesSanitizer(['install', 'update', 'uninstall'], required=True),
		app=StringSanitizer(required=True),
		force=BooleanSanitizer(),
		values=DictSanitizer({})
	)
	@simple_response(with_progress=True)
	def invoke_remote_docker(self, host, function, app, force, values, progress):
		options = {'function': function, 'app': app, 'force': force, 'values': values}
		client = Client(host, self.username, self.password)
		result = client.umc_command('appcenter/docker/invoke', options).result
		self._remote_progress[progress.id] = client, result['id']

	@simple_response
	def remote_progress(self, progress_id):
		try:
			client, remote_progress_id = self._remote_progress[progress_id]
		except KeyError:
			# actually happens: before invoke_remote_docker is finished, remote_progress is already called
			return {}
		else:
			return client.umc_command('appcenter/docker/progress', {'progress_id': remote_progress_id}).result

	@require_apps_update
	@require_password
	@sanitize(
		function=MappingSanitizer({
			'install': 'install',
			'update': 'upgrade',
			'uninstall': 'remove',
		}, required=True),
		app=AppSanitizer(required=True),
		force=BooleanSanitizer(),
		values=DictSanitizer({})
	)
	@simple_response(with_progress=True)
	def invoke_docker(self, function, app, force, values, progress):
		if function == 'upgrade':
			app = Apps().find_candidate(app)
		serious_problems = False
		progress.title = _('%s: Running tests') % (app.name,)
		errors, warnings = app.check(function)
		can_continue = force  # "dry_run"
		if errors:
			MODULE.process('Cannot %s %s: %r' % (function, app.id, errors))
			serious_problems = True
			can_continue = False
		if warnings:
			MODULE.process('Warning trying to %s %s: %r' % (function, app.id, warnings))
		result = {
			'serious_problems': serious_problems,
			'invokation_forbidden_details': errors,
			'invokation_warning_details': warnings,
			'can_continue': can_continue,
			'software_changes_computed': False,
		}
		if can_continue:
			with self.locked():
				kwargs = {'noninteractive': True, 'skip_checks': ['shall_have_enough_ram', 'shall_only_be_installed_in_ad_env_with_password_service', 'must_not_have_concurrent_operation']}
				if function == 'install':
					progress.title = _('Installing %s') % (app.name,)
					kwargs['set_vars'] = values
				elif function == 'uninstall':
					progress.title = _('Uninstalling %s') % (app.name,)
				elif function == 'upgrade':
					progress.title = _('Upgrading %s') % (app.name,)
				action = get_action(function)
				handler = UMCProgressHandler(progress)
				handler.setLevel(logging.INFO)
				action.logger.addHandler(handler)
				try:
					result['success'] = action.call(app=app, username=self.username, password=self.password, **kwargs)
				except AppCenterError as exc:
					raise umcm.UMC_Error(str(exc), result=dict(
						display_feedback=True,
						title='%s %s' % (exc.title, exc.info)))
				finally:
					action.logger.removeHandler(handler)
		return result

	@contextmanager
	def locked(self):
		try:
			with self.package_manager.locked(reset_status=True, set_finished=True):
				yield
		except LockError:
			raise umcm.UMC_Error(_('Another package operation is in progress'))

	@require_apps_update
	@require_password
	@sanitize(
		function=ChoicesSanitizer(['install', 'uninstall', 'update', 'install-schema', 'update-schema'], required=True),
		application=StringSanitizer(minimum=1, required=True),
		force=BooleanSanitizer(),
		host=StringSanitizer(),
		only_dry_run=BooleanSanitizer(),
		dont_remote_install=BooleanSanitizer(),
		values=DictSanitizer({})
	)
	def invoke(self, request):
		# ATTENTION!!!!!!!
		# this function has to stay compatible with the very first App Center installations (Dec 2012)
		# if you add new arguments that change the behaviour
		# you should add a new method (see invoke_dry_run) or add a function name (e.g. install-schema)
		# this is necessary because newer app center may talk remotely with older one
		#   that does not understand new arguments and behaves the old way (in case of
		#   dry_run: install application although they were asked to dry_run)
		host = request.options.get('host')
		function = request.options.get('function')
		send_as = function
		if function.startswith('install'):
			function = 'install'
		if function.startswith('update'):
			function = 'update'

		application_id = request.options.get('application')
		Application.all(only_local=True)  # if not yet cached, cache. but use only local inis
		application = Application.find(application_id)
		if application is None:
			raise umcm.UMC_Error(_('Could not find an application for %s') % (application_id,))
		force = request.options.get('force')
		only_dry_run = request.options.get('only_dry_run')
		dont_remote_install = request.options.get('dont_remote_install')
		only_master_packages = send_as.endswith('schema')
		MODULE.process('Try to %s (%s) %s on %s. Force? %r. Only master packages? %r. Prevent installation on other systems? %r. Only dry run? %r.' % (function, send_as, application_id, host, force, only_master_packages, dont_remote_install, only_dry_run))

		# REMOTE invocation!
		if host and host != self.ucr.get('hostname'):
			try:
				client = Client(host, self.username, self.password)
				result = client.umc_command('appcenter/invoke', request.options).result
			except (ConnectionError, HTTPError) as exc:
				MODULE.error('Error during remote appcenter/invoke: %s' % (exc,))
				result = {
					'unreachable': [host],
					'master_unreachable': True,
					'serious_problems': True,
					'software_changes_computed': True,  # not really...
				}
			else:
				if result['can_continue']:
					def _thread_remote(_client, _package_manager):
						with _package_manager.locked(reset_status=True, set_finished=True):
							_package_manager.unlock()   # not really locked locally, but busy, so "with locked()" is appropriate
							Application._query_remote_progress(_client, _package_manager)

					def _finished_remote(thread, result):
						if isinstance(result, BaseException):
							MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result)))
					thread = notifier.threads.Simple('invoke', notifier.Callback(_thread_remote, client, self.package_manager), _finished_remote)
					thread.run()
			self.finished(request.id, result)
			return

		# make sure that the application can be installed/updated
		can_continue = True
		delayed_can_continue = True
		serious_problems = False
		result = {
			'install': [],
			'remove': [],
			'broken': [],
			'unreachable': [],
			'master_unreachable': False,
			'serious_problems': False,
			'hosts_info': {},
			'problems_with_hosts': False,
			'serious_problems_with_hosts': False,
			'invokation_forbidden_details': {},
			'invokation_warning_details': {},
			'software_changes_computed': False,
		}
		if not application:
			MODULE.process('Application not found: %s' % application_id)
			can_continue = False
		if can_continue and not only_master_packages:
			forbidden, warnings = application.check_invokation(function, self.package_manager)
			if forbidden:
				MODULE.process('Cannot %s %s: %r' % (function, application_id, forbidden))
				result['invokation_forbidden_details'] = forbidden
				can_continue = False
				serious_problems = True
			if warnings:
				MODULE.process('Warning trying to %s %s: %r' % (function, application_id, forbidden))
				result['invokation_warning_details'] = warnings
				if not force:
					# dont stop "immediately".
					#   compute the package changes!
					delayed_can_continue = False
		result['serious_problems'] = serious_problems
		result['can_continue'] = can_continue
		try:
			if can_continue:
				if self._working():
					# make it multi-tab safe (same session many buttons to be clicked)
					raise LockError()
				with self.package_manager.locked(reset_status=True):
					previously_registered_by_dry_run = False
					if can_continue and function in ('install', 'update'):
						remove_component = only_dry_run
						dry_run_result, previously_registered_by_dry_run = application.install_dry_run(self.package_manager, self.component_manager, remove_component=remove_component, username=self._username, password=self.password, only_master_packages=only_master_packages, dont_remote_install=dont_remote_install, function=function, force=force)
						result.update(dry_run_result)
						result['software_changes_computed'] = True
						serious_problems = bool(result['broken'] or result['master_unreachable'] or result['serious_problems_with_hosts'])
						if serious_problems or (not force and (result['unreachable'] or result['install'] or result['remove'] or result['problems_with_hosts'])):
							MODULE.process('Problems encountered or confirmation required. Removing component %s' % application.component_id)
							if not remove_component:
								# component was not removed automatically after dry_run
								if application.candidate:
									# operation on candidate failed. re-register original application
									application.register(self.component_manager, self.package_manager)
								else:
									# operation on self failed. unregister all
									application.unregister_all_and_register(None, self.component_manager, self.package_manager)
							can_continue = False
					elif can_continue and function in ('uninstall',) and not force:
						result['remove'] = application.uninstall_dry_run(self.package_manager)
						result['software_changes_computed'] = True
						can_continue = False
					can_continue = can_continue and delayed_can_continue and not only_dry_run
					result['serious_problems'] = serious_problems
					result['can_continue'] = can_continue

					if can_continue and not only_dry_run:
						def _thread(module, application, function):
							with module.package_manager.locked(set_finished=True):
								with module.package_manager.no_umc_restart(exclude_apache=True):
									if function in ('install', 'update'):
										# dont have to add component: already added during dry_run
										return application.install(module.package_manager, module.component_manager, add_component=only_master_packages, send_as=send_as, username=self._username, password=self.password, only_master_packages=only_master_packages, dont_remote_install=dont_remote_install, previously_registered_by_dry_run=previously_registered_by_dry_run)
									else:
										return application.uninstall(module.package_manager, module.component_manager, self._username, self.password)

						def _finished(thread, result):
							if isinstance(result, BaseException):
								MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result)))
						thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self, application, function), _finished)
						thread.run()
					else:
						self.package_manager.set_finished()  # nothing to do, ready to take new commands
			self.finished(request.id, result)
		except LockError:
			# make it thread safe: another process started a package manager
			# this module instance already has a running package manager
			raise umcm.UMC_Error(_('Another package operation is in progress'))

	def keep_alive(self, request):
		''' Fix for Bug #30611: UMC kills appcenter module
		if no request is sent for $(ucr get umc/module/timeout).
		this happens if a user logs out during a very long installation.
		this function will be run by the frontend to always have one connection open
		to prevent killing the module. '''
		def _thread():
			while not self.package_manager.progress_state._finished:
				time.sleep(1)

		def _finished(thread, result):
			success = not isinstance(result, BaseException)
			if not success:
				MODULE.warn('Exception during keep_alive: %s' % result)
			self.finished(request.id, success)
		thread = notifier.threads.Simple('keep_alive', notifier.Callback(_thread), _finished)
		thread.run()

	@simple_response
	def ping(self):
		return True

	@simple_response
	def buy(self, application):
		app = Apps().find(application)
		if not app or not app.shop_url:
			return None
		ret = {}
		ret['key_id'] = LICENSE.uuid
		ret['ucs_version'] = self.ucr.get('version/version')
		ret['app_id'] = app.id
		ret['app_version'] = app.version
		# ret['locale'] = locale.getlocale()[0] # done by frontend
		ret['user_count'] = None  # FIXME: get users and computers from license
		ret['computer_count'] = None
		return ret

	@simple_response
	def enable_disable_app(self, application, enable=True):
		app = Apps().find(application)
		if not app:
			return
		stall = get_action('stall')
		stall.call(app=app, undo=enable)

	@simple_response
	def packages_sections(self):
		""" fills the 'sections' combobox in the search form """

		sections = set()
		cache = apt.Cache()
		for package in cache:
			sections.add(package.section)

		return sorted(sections)

	@sanitize(pattern=PatternSanitizer(required=True))
	@simple_response
	def packages_query(self, pattern, section='all', key='package'):
		""" Query to fill the grid. Structure is fixed here. """
		result = []
		for package in self.package_manager.packages(reopen=True):
			if section == 'all' or package.section == section:
				toshow = False
				if pattern.pattern == '^.*$':
					toshow = True
				elif key == 'package' and pattern.search(package.name):
					toshow = True
				elif key == 'description' and package.candidate and pattern.search(package.candidate.raw_description):
					toshow = True
				if toshow:
					result.append(self._package_to_dict(package, full=False))
		return result

	@simple_response
	def packages_get(self, package):
		""" retrieves full properties of one package """

		package = self.package_manager.get_package(package)
		if package is not None:
			return self._package_to_dict(package, full=True)
		else:
			# TODO: 404?
			return {}

	@sanitize(
		function=MappingSanitizer({
			'install': 'install',
			'upgrade': 'install',
			'uninstall': 'remove',
		}, required=True),
		packages=ListSanitizer(StringSanitizer(minimum=1), required=True),
		update=BooleanSanitizer()
	)
	@simple_response
	def packages_invoke_dry_run(self, packages, function, update):
		if update:
			self.package_manager.update()
		packages = self.package_manager.get_packages(packages)
		kwargs = {'install': [], 'remove': [], 'dry_run': True}
		if function == 'install':
			kwargs['install'] = packages
		else:
			kwargs['remove'] = packages
		return dict(zip(['install', 'remove', 'broken'], self.package_manager.mark(**kwargs)))

	@sanitize(
		function=MappingSanitizer({
			'install': 'install',
			'upgrade': 'install',
			'uninstall': 'remove',
		}, required=True),
		packages=ListSanitizer(StringSanitizer(minimum=1), required=True)
	)
	def packages_invoke(self, request):
		""" executes an installer action """
		packages = request.options.get('packages')
		function = request.options.get('function')

		try:
			if self._working():
				# make it multi-tab safe (same session many buttons to be clicked)
				raise LockError()
			with self.package_manager.locked(reset_status=True):
				not_found = [pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None]
				self.finished(request.id, {'not_found': not_found})

				if not not_found:
					def _thread(package_manager, function, packages):
						with package_manager.locked(set_finished=True):
							with package_manager.no_umc_restart(exclude_apache=True):
								if function == 'install':
									package_manager.install(*packages)
								else:
									package_manager.uninstall(*packages)

					def _finished(thread, result):
						if isinstance(result, BaseException):
							MODULE.warn('Exception during %s %s: %r' % (function, packages, str(result)))
					thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self.package_manager, function, packages), _finished)
					thread.run()
				else:
					self.package_manager.set_finished()  # nothing to do, ready to take new commands
		except LockError:
			# make it thread safe: another process started a package manager
			# this module instance already has a running package manager
			raise umcm.UMC_Error(_('Another package operation is in progress'))

	def _working(self):
		return not self.package_manager.progress_state._finished

	@simple_response
	def working(self):
		# TODO: PackageManager needs is_idle() or something
		#   preferably the package_manager can tell what is currently executed:
		#   package_manager.is_working() => False or _('Installing UCC')
		return self._working()

	@simple_response
	def custom_progress(self):
		timeout = 5
		return self.package_manager.poll(timeout)

	def _package_to_dict(self, package, full):
		""" Helper that extracts properties from a 'apt_pkg.Package' object
			and stores them into a dictionary. Depending on the 'full'
			switch, stores only limited (for grid display) or full
			(for detail view) set of properties.
		"""
		installed = package.installed  # may be None
		found = True
		candidate = package.candidate
		found = candidate is not None
		if not found:
			candidate = NoneCandidate()

		result = {
			'package': package.name,
			'installed': package.is_installed,
			'upgradable': package.is_upgradable and found,
			'summary': candidate.summary,
		}

		# add (and translate) a combined status field
		# *** NOTE *** we translate it here: if we would use the Custom Formatter
		#		of the grid then clicking on the sort header would not work.
		if package.is_installed:
			if package.is_upgradable:
				result['status'] = _('upgradable')
			else:
				result['status'] = _('installed')
		else:
			result['status'] = _('not installed')

		# additional fields needed for detail view
		if full:
			# Some fields differ depending on whether the package is installed or not:
			if package.is_installed:
				result['section'] = installed.section
				result['priority'] = installed.priority or ''
				result['summary'] = installed.summary   # take the current one
				result['description'] = installed.description
				result['installed_version'] = installed.version
				result['size'] = installed.installed_size
				if package.is_upgradable:
					result['candidate_version'] = candidate.version
			else:
				del result['upgradable']  # not installed: don't show 'upgradable' at all
				result['section'] = candidate.section
				result['priority'] = candidate.priority or ''
				result['description'] = candidate.description
				result['size'] = candidate.installed_size
				result['candidate_version'] = candidate.version
			# format size to handle bytes
			size = result['size']
			byte_mods = ['B', 'kB', 'MB']
			for byte_mod in byte_mods:
				if size < 10000:
					break
				size = float(size) / 1000  # MB, not MiB
			else:
				size = size * 1000  # once too often
			if size == int(size):
				format_string = '%d %s'
			else:
				format_string = '%.2f %s'
			result['size'] = format_string % (size, byte_mod)

		return result

	@simple_response
	def components_query(self):
		"""	Returns components list for the grid in the ComponentsPage.
		"""
		# be as current as possible.
		self.uu.ucr_reinit()
		self.ucr.load()

		result = []
		for comp in self.uu.get_all_components():
			result.append(self.component_manager.component(comp))
		return result

	@sanitize_list(StringSanitizer())
	@multi_response(single_values=True)
	def components_get(self, iterator, component_id):
		# be as current as possible.
		self.uu.ucr_reinit()
		self.ucr.load()
		for component_id in iterator:
			yield self.component_manager.component(component_id)

	@sanitize_list(DictSanitizer({'object': advanced_components_sanitizer}))
	@multi_response
	def components_put(self, iterator, object):
		"""Writes back one or more component definitions.
		"""
		# umc.widgets.Form wraps the real data into an array:
		#
		#	[
		#		{
		#			'object' : { ... a dict with the real data .. },
		#			'options': None
		#		},
		#		... more such entries ...
		#	]
		#
		# Current approach is to return a similarly structured array,
		# filled with elements, each one corresponding to one array
		# element of the request:
		#
		#	[
		#		{
		#			'status'	:	a number where 0 stands for success, anything else
		#							is an error code
		#			'message'	:	a result message
		#			'object'	:	a dict of field -> error message mapping, allows
		#							the form to show detailed error information
		#		},
		#		... more such entries ...
		#	]
		with util.set_save_commit_load(self.ucr) as super_ucr:
			for object, in iterator:
				yield self.component_manager.put(object, super_ucr)
		self.package_manager.update()

	# do the same as components_put (update)
	# but dont allow adding an already existing entry
	components_add = sanitize_list(DictSanitizer({'object': add_components_sanitizer}))(components_put)
	components_add.__name__ = 'components_add'

	@sanitize_list(StringSanitizer())
	@multi_response(single_values=True)
	def components_del(self, iterator, component_id):
		for component_id in iterator:
			yield self.component_manager.remove(component_id)
		self.package_manager.update()

	@multi_response
	def settings_get(self, iterator):
		# *** IMPORTANT *** Our UCR copy must always be current. This is not only
		#	to catch up changes made via other channels (ucr command line etc),
		#	but also to reflect the changes we have made ourselves!
		self.ucr.load()

		for _ in iterator:
			yield {
				'unmaintained': self.ucr.is_true('repository/online/unmaintained', False),
				'server': self.ucr.get('repository/online/server', ''),
				'prefix': self.ucr.get('repository/online/prefix', ''),
			}

	@sanitize_list(
		DictSanitizer({'object': basic_components_sanitizer}),
		min_elements=1,
		max_elements=1  # moduleStore with one element...
	)
	@multi_response
	def settings_put(self, iterator, object):
		# FIXME: returns values although it should yield (multi_response)
		changed = False
		# Set values into our UCR copy.
		try:
			with util.set_save_commit_load(self.ucr) as super_ucr:
				for object, in iterator:
					for key, value in object.iteritems():
						MODULE.info("   ++ Setting new value for '%s' to '%s'" % (key, value))
						super_ucr.set_registry_var('%s/%s' % (constants.ONLINE_BASE, key), value)
				changed = super_ucr.changed()
		except Exception as e:
			MODULE.warn("   !! Writing UCR failed: %s" % str(e))
			return [{'message': str(e), 'status': constants.PUT_WRITE_ERROR}]

		self.package_manager.update()

		# Bug #24878: emit a warning if repository is not reachable
		try:
			updater = self.uu
			for line in updater.print_version_repositories().split('\n'):
				if line.strip():
					break
			else:
				raise ConfigurationError()
		except ConfigurationError:
			msg = _("There is no repository at this server (or at least none for the current UCS version)")
			MODULE.warn("   !! Updater error: %s" % msg)
			response = {'message': msg, 'status': constants.PUT_UPDATER_ERROR}
			# if nothing was committed, we want a different type of error code,
			# just to appropriately inform the user
			if changed:
				response['status'] = constants.PUT_UPDATER_NOREPOS
			return [response]
		except:
			info = sys.exc_info()
			emsg = '%s: %s' % info[:2]
			MODULE.warn("   !! Updater error [%s]: %s" % (emsg))
			return [{'message': str(info[1]), 'status': constants.PUT_UPDATER_ERROR}]
		return [{'status': constants.PUT_SUCCESS}]