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
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 _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' % (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))
def _decorator(self, request, *args, **kwargs): if not IS_SELFSERVICE_MASTER: try: language = str(self.locale).split('.')[0].replace('_', '-') client = Client(SELFSERVICE_MASTER, language=language) client.authenticate_with_machine_account() response = client.umc_command(request.arguments[0], request.options) except (Unauthorized, ConnectionError) as exc: raise UMC_Error(_('The connection to the server could not be established. Please try again later. Error message was: %s') % (exc,), status=503) except HTTPError as exc: response = exc.response self.finished(request.id, response.result, message=response.message, status=response.status) return return func(self, request, *args, **kwargs)
def join_ad(): """ Function for joining an AD domain, mimicking a join from umc""" global client client = Client(options.host, options.username, options.password, language='en-US') if options.sync_mode: # join in sync mode: print '=== AD-JOIN SYNC MODE SELECTED ===' join_sync_mode() else: # join in read mode: print '=== AD-JOIN READ MODE SELECTED ===' join_read_mode()
def __init__(self, host, username=None, password=None, error_handler=None): # type: (str, Optional[str], Optional[str], Optional[Callable[[str], None]]) -> None self.client = Client(host, username, password) self._error_handler = error_handler self.build_data = self.client._Client__build_data
class UMCConnection(object): """ Connection to a |UMC| server. :param str hostname: The host name of the |UMC| server. :param str username: The user name. :param str password: The user password. :param error_handler: A function accepting the error message as the single argument. """ def __init__(self, host, username=None, password=None, error_handler=None): # type: (str, Optional[str], Optional[str], Optional[Callable[[str], None]]) -> None self.client = Client(host, username, password) self._error_handler = error_handler self.build_data = self.client._Client__build_data @property def _headers(self): # type: () -> Dict[str, str] """ Return |HTTP| request headers. :returns: A dictionary containing the |HTTP| headers. :rtype: dict """ return self._client._headers @property def _host(self): # type: () -> str """ Return the host name of the |UMC| server, :returns: the host name. :rtype: str """ return self.client.hostname def get_connection(self): # type: () -> HTTPSConnection """ Return the underlying connection object. :returns: The connection. :rtype: HTTPSConnection """ return self._client._get_connection() @classmethod def get_machine_connection(cls, error_handler=None): # type: (Optional[Callable[[str], None]]) -> Optional[UMCConnection] """ Creates a connection with the credentials of the local host to the DC Master. :param error_handler: A function accepting the error message as the single argument. :returns: The connection object or None in case of errors. :rtype: UMCConnection """ try: connection = cls(ucr.get('ldap/master')) connection.client.authenticate_with_machine_account() return connection except ConnectionError as exc: if error_handler: error_handler('Could not connect to UMC on %s: %s' % (ucr.get('ldap/master'), exc.reason)) def auth(self, username, password, auth_type=None): # type: (str, str, Optional[str]) -> None """ Tries to authenticate against the host and preserves the cookie. Has to be done only once (but keep in mind that the session probably expires after 10 minutes of inactivity) :param str username: The user name. :param str password: The user password. :param str auth_type: The authentication type, e.g. `???`. """ try: self.client.umc_auth(username, password, auth_type=auth_type) except HTTPError as exc: raise HTTPException(str(exc)) def request(self, url, data=None, flavor=None, command='command'): # type: (str, Any, Optional[str], str) -> str """ Sends a request and returns the data from the response. :param str url: The partial |URL| of the |UMC| function to invoke. See the |XML| file of the |UMC| module. :param data: The data to send. :param flavor: Some |UMC| modules support flavors, e.g. the |UDM| module for sub-types. :param str command: may be anything that |UMCP| understands, especially: * `command` (default) * `get` (and `url` could be `ucr` then) * `set` (and `url` would be `` and `data` could be `{'locale':'de_DE'}`) * `upload` (`url` could be `udm/license/import`) :returns: the result of the response. :rtype: str """ if data is None: data = {} try: if command in ('command', 'upload'): response = self.client.umc_command(url, data, flavor) elif command == 'get': response = self.client.umc_get(url) elif command == 'set': response = self.client.umc_set(data) except Forbidden: raise NotImplementedError('command forbidden: %s' % url) except HTTPError as exc: if self._error_handler: self._error_handler(str(exc)) raise HTTPException(str(exc)) return response.result
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 behavior # 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 = 'upgrade' if function == 'uninstall': function = 'remove' app_id = request.options.get('application') app = Apps().find(app_id) if app is None: raise umcm.UMC_Error(_('Could not find an application for %s') % (app_id,)) force = request.options.get('force') values = request.options.get('values') 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, app_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): with self.is_working(): self._query_remote_progress(_client) def _finished_remote(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, app_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread_remote, client), _finished_remote) thread.run() self.finished(request.id, result) return # make sure that the application can be installed/updated action = get_action(function)() args = action._build_namespace(app=app, username=self.username, password=self.password, noninteractive=True, skip_checks=['shall_have_enough_ram', 'shall_only_be_installed_in_ad_env_with_password_service', 'must_not_have_concurrent_operation'], send_info=not only_master_packages, set_vars=values, dry_run=True, install_master_packages_remotely=False, only_master_packages=only_master_packages) 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 app: MODULE.process('Application not found: %s' % app_id) can_continue = False if can_continue and not only_master_packages: if function == 'upgrade': app = Apps().find_candidate(app) if app is None: forbidden, warnings = {'must_have_candidate': False}, {} else: forbidden, warnings = app.check(function) if forbidden: MODULE.process('Cannot %s %s: %r' % (function, app_id, forbidden)) result['invokation_forbidden_details'] = forbidden can_continue = False serious_problems = True if warnings: MODULE.process('Warning trying to %s %s: %r' % (function, app_id, forbidden)) result['invokation_warning_details'] = warnings if not force: # don't stop "immediately". # compute the package changes! delayed_can_continue = False result['serious_problems'] = serious_problems result['can_continue'] = can_continue if can_continue: with self.locked(): if can_continue and function in ('install', 'upgrade'): result.update(self._install_dry_run_remote(app, function, dont_remote_install, force)) serious_problems = bool(result['master_unreachable'] or result['serious_problems_with_hosts']) if serious_problems: args.dry_run = True result.update(action.dry_run(app, args)) result['software_changes_computed'] = True serious_problems = bool(result['broken'] or serious_problems) if serious_problems or (not force and (result['unreachable'] or result['install'] or result['remove'] or result['problems_with_hosts'])): can_continue = False elif can_continue and function in ('remove',) and not force: result.update(action.dry_run(app, args)) 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, app, function): with module.is_working(): if not dont_remote_install and function != 'remove': self._install_master_packages_on_hosts(app, function) with module.package_manager.no_umc_restart(exclude_apache=True): try: args.dry_run = False args.install_master_packages_remotely = False return action.call_with_namespace(args) except AppCenterError as exc: raise umcm.UMC_Error(str(exc), result=dict( display_feedback=True, title='%s %s' % (exc.title, exc.info))) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, app_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self, app, function), _finished) thread.run() self.finished(request.id, result)
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']
def _thread(): # make sure that a project with the same name does not exist directory = request.options['directory'] # get absolute path of project file and test for existance fn_test_project = util.distribution.Project.sanitize_project_filename( directory) if os.path.exists(fn_test_project): raise UMC_Error( _('An exam with the name "%s" already exists. Please choose a different name for the exam.' ) % (directory, )) # validate the project data and save project my.project = util.distribution.Project( dict( name=directory, description=request.options['name'], files=request.options.get('files'), sender=sender, )) my.project.validate() my.project.save() # copy files into project directory if self._tmpDir: for ifile in my.project.files: isrc = os.path.join(self._tmpDir, ifile) itarget = os.path.join(my.project.cachedir, ifile) if os.path.exists(isrc): # copy file to cachedir shutil.move(isrc, itarget) os.chown(itarget, 0, 0) # open a new connection to the master UMC try: master = ucr['ldap/master'] client = Client(master) client.authenticate_with_machine_account() except (ConnectionError, HTTPError) as exc: MODULE.error('Could not connect to UMC on %s: %s' % (master, exc)) raise UMC_Error( _('Could not connect to master server %s.') % ucr.get('ldap/master')) # mark the computer room for exam mode progress.component( _('Preparing the computer room for exam mode...')) client.umc_command( 'schoolexam-master/set-computerroom-exammode', dict( school=request.options['school'], roomdn=request.options['room'], )).result # FIXME: no error handling progress.add_steps(5) # read all recipients and fetch all user objects users = [] for idn in request.options['recipients']: ientry = util.distribution.openRecipients(idn, ldap_user_read) if not ientry: continue # recipients can in theory be users or groups members = [] if isinstance(ientry, util.distribution.User): members = [ientry] elif isinstance(ientry, util.distribution.Group): members = ientry.members for entry in members: # ignore exam users user = User.from_dn(entry.dn, None, ldap_user_read) if not user.is_exam_student(ldap_user_read): users.append(entry) # start to create exam user accounts progress.component(_('Preparing exam accounts')) percentPerUser = 25.0 / (1 + len(users)) examUsers = set() student_dns = set() usersReplicated = set() for iuser in users: progress.info( '%s, %s (%s)' % (iuser.lastname, iuser.firstname, iuser.username)) try: ires = client.umc_command( 'schoolexam-master/create-exam-user', dict( school=request.options['school'], userdn=iuser.dn, )).result examuser_dn = ires.get('examuserdn') examUsers.add(examuser_dn) student_dns.add(iuser.dn) MODULE.info('Exam user has been created: %r' % examuser_dn) except (ConnectionError, HTTPError) as exc: MODULE.warn( 'Could not create exam user account for %r: %s' % (iuser.dn, exc)) # indicate the the user has been processed progress.add_steps(percentPerUser) client.umc_command( 'schoolexam-master/add-exam-users-to-groups', dict( users=list(student_dns), school=request.options['school'], )) progress.add_steps(percentPerUser) # wait for the replication of all users to be finished progress.component(_('Preparing user home directories')) recipients = [] # list of User objects for all exam users openAttempts = 30 * 60 # wait max. 30 minutes for replication while (len(examUsers) > len(usersReplicated)) and (openAttempts > 0): openAttempts -= 1 MODULE.info( 'waiting for replication to be finished, %s user objects missing' % (len(examUsers) - len(usersReplicated))) for idn in examUsers - usersReplicated: try: ldap_user_read.get(idn, required=True) except ldap.NO_SUCH_OBJECT: continue # not replicated yet iuser = util.distribution.openRecipients( idn, ldap_user_read) if not iuser: continue # not a users/user object MODULE.info('user has been replicated: %s' % idn) # call hook scripts if 0 != subprocess.call([ '/bin/run-parts', CREATE_USER_POST_HOOK_DIR, '--arg', iuser.username, '--arg', iuser.dn, '--arg', iuser.homedir ]): raise ValueError( 'failed to run hook scripts for user %r' % (iuser.username)) # store User object in list of final recipients recipients.append(iuser) # mark the user as replicated usersReplicated.add(idn) progress.info( '%s, %s (%s)' % (iuser.lastname, iuser.firstname, iuser.username)) progress.add_steps(percentPerUser) # wait a second time.sleep(1) progress.add_steps(percentPerUser) if openAttempts <= 0: MODULE.error( 'replication timeout - %s user objects missing: %r ' % ((len(examUsers) - len(usersReplicated)), (examUsers - usersReplicated))) raise UMC_Error( _('Replication timeout: could not create all exam users')) # update the final list of recipients my.project.recipients = recipients my.project.save() # update local NSS group cache if ucr.is_true('nss/group/cachefile', True): cmd = ['/usr/lib/univention-pam/ldap-group-to-file.py'] if ucr.is_true('nss/group/cachefile/check_member', False): cmd.append('--check_member') MODULE.info('Updating local nss group cache...') if subprocess.call(cmd): MODULE.error('Updating local nss group cache failed: %s' % ' '.join(cmd)) else: MODULE.info( 'Update of local nss group cache finished successfully.' ) # distribute exam files progress.component(_('Distributing exam files')) progress.info('') my.project.distribute() progress.add_steps(20) # prepare room settings via UMCP... # first step: acquire room # second step: adjust room settings progress.component(_('Prepare room settings')) try: user_client = Client(None, self.username, self.password) except (ConnectionError, HTTPError) as exc: MODULE.warn('Authentication failed: %s' % (exc, )) raise UMC_Error(_('Could not connect to local UMC server.')) room = request.options['room'] MODULE.info('Acquire room: %s' % (room, )) user_client.umc_command('computerroom/room/acquire', dict( room=request.options['room'], )).result progress.add_steps(1) MODULE.info('Adjust room settings:\n%s' % '\n'.join( [' %s=%s' % (k, v) for k, v in request.options.iteritems()])) user_client.umc_command( 'computerroom/exam/start', dict( room=room, examDescription=request.options['name'], exam=directory, examEndTime=request.options.get('examEndTime'), )).result progress.add_steps(4) user_client.umc_command( 'computerroom/settings/set', dict( room=room, internetRule=request.options['internetRule'], customRule=request.options.get('customRule'), shareMode=request.options['shareMode'], printMode='default', )).result progress.add_steps(5)
def __init__(self, host, username=None, password=None, error_handler=None): self.client = Client(host, username, password) self._error_handler = error_handler self.build_data = self.client._Client__build_data
class UMCConnection(object): def __init__(self, host, username=None, password=None, error_handler=None): self.client = Client(host, username, password) self._error_handler = error_handler self.build_data = self.client._Client__build_data @property def _headers(self): return self._client._headers @property def _host(self): return self.client.hostname def get_connection(self): return self._client._get_connection() @classmethod def get_machine_connection(cls, error_handler=None): '''Creates a connection with the credentials of the local host to the DC Master''' try: connection = cls(ucr.get('ldap/master')) connection.client.authenticate_with_machine_account() return connection except ConnectionError as exc: if error_handler: error_handler('Could not connect to UMC on %s: %s' % (ucr.get('ldap/master'), exc.reason)) def auth(self, username, password, auth_type=None): '''Tries to authenticate against the host and preserves the cookie. Has to be done only once (but keep in mind that the session probably expires after 10 minutes of inactivity)''' try: self.client.umc_auth(username, password, auth_type=auth_type) except HTTPError as exc: raise HTTPException(str(exc)) def request(self, url, data=None, flavor=None, command='command'): '''Sends a request and returns the data from the response. url as in the XML file of that UMC module. command may be anything that UMCP understands, especially: * command (default) * get (and url could be 'ucr' then) * set (and url would be '' and data could be {'locale':'de_DE'}) * upload (url could be 'udm/license/import') ''' if data is None: data = {} try: if command in ('command', 'upload'): response = self.client.umc_command(url, data, flavor) elif command == 'get': response = self.client.umc_get(url) elif command == 'set': response = self.client.umc_set(data) except Forbidden: raise NotImplementedError('command forbidden: %s' % url) except HTTPError as exc: if self._error_handler: self._error_handler(str(exc)) raise HTTPException(str(exc)) return response.result
def umc(username, password, master, path, options=None, flavor=None): MODULE.info('Executing on %r as %r: %r flavor=%r options=%r' % (master, username, path, flavor, options)) client = Client(master, username, password) return client.umc_command(path, options, flavor)
def _thread(): # perform all actions inside a thread... # collect files progress.component(_('Collecting exam files...')) if project: project.collect() progress.add_steps(10) # open a new connection to the master UMC master = ucr['ldap/master'] try: client = Client(master) client.authenticate_with_machine_account() except (ConnectionError, HTTPError) as exc: MODULE.error('Could not connect to UMC on %s: %s' % (master, exc)) raise UMC_Error( _('Could not connect to master server %s.') % (master, )) school = SchoolSearchBase.getOU(request.options['room']) # unset exam mode for the given computer room progress.component(_('Configuring the computer room...')) client.umc_command( 'schoolexam-master/unset-computerroom-exammode', dict( roomdn=request.options['room'], school=school, )).result progress.add_steps(5) # delete exam users accounts if project: # get a list of user accounts in parallel exams parallelUsers = dict([ (iuser.username, iproject.description) for iproject in util.distribution.Project.list() if iproject.name != project.name for iuser in iproject.recipients ]) progress.component(_('Removing exam accounts')) percentPerUser = 25.0 / (1 + len(project.recipients)) for iuser in project.recipients: progress.info( '%s, %s (%s)' % (iuser.lastname, iuser.firstname, iuser.username)) try: if iuser.username not in parallelUsers: # remove first the home directory shutil.rmtree(iuser.unixhome, ignore_errors=True) # remove LDAP user entry client.umc_command( 'schoolexam-master/remove-exam-user', dict( userdn=iuser.dn, school=school, )).result MODULE.info('Exam user has been removed: %s' % iuser.dn) else: MODULE.process( 'Cannot remove the user account %s as it is registered for the running exam "%s", as well' % (iuser.dn, parallelUsers[iuser.username])) except (ConnectionError, HTTPError) as e: MODULE.warn( 'Could not remove exam user account %s: %s' % (iuser.dn, e)) # indicate the user has been processed progress.add_steps(percentPerUser) progress.add_steps(percentPerUser)
if options.samba not in ('3', '4'): parser.error('Samba version needs to be either 3 or 4!') if ucr['server/role'] != 'domaincontroller_master' and not options.master: parser.error('Please specify a master host (-m)!') if not options.username or not options.password: parser.error('Please specify username (-u) and password (-p)!') if not options.ou: if ucr['server/role'] == 'domaincontroller_slave' or options.setup == 'singlemaster': parser.error('Please specify a school OU (-o)!') options.ou = '' client = Client(options.host, options.username, options.password, language='en-US') params = { 'setup': options.setup, 'username': options.username, 'password': options.password, 'master': options.master, 'samba': options.samba, 'schoolOU': options.ou, } if options.server_type: params['server_type'] = options.server_type if options.name_edu_server: params['nameEduServer'] = options.name_edu_server
try: result = client.umc_command(path).result except univention.lib.umc.ConnectionError: print('... Apache down? Ignoring...') continue print(result) if result.get('finished', False): break else: raise Exception("wait timeout") print(result) assert not result['errors'] client = Client(options.host, options.username, options.password, language='en-US') request_options = { "ip": options.domain_host, "username": options.domain_admin, "password": options.domain_password } print('starting connect') response = client.umc_command("adtakeover/connect", request_options) print(response.result) assert response.status == 200 try: print('starting copy') response = client.umc_command("adtakeover/run/copy", request_options)
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'))