def test_template_overridal(): """Tests that we can override an instances template at construction time and get it automatically compiled.""" new_template = """ <label xmlns:py="http://purl.org/kid/ns#" for="${name}" class="${field_class}" py:content="value" custom_template="True" /> """ l = widgets.Label(template=new_template) output = l.render(format='xhtml') assert 'custom_template="True"' in output
class Recipes(RPCRoot): # For XMLRPC methods in this class. exposed = True hidden_id = widgets.HiddenField(name='id') confirm = widgets.Label( name='confirm', default="Are you sure you want to release the system?") return_reservation_form = widgets.TableForm( 'end_recipe_reservation', fields=[hidden_id, confirm], action='./really_return_reservation', submit_text=_(u'Yes')) tasks = RecipeTasks() recipe_widget = RecipeWidget() log_types = dict( R=LogRecipe, T=LogRecipeTask, E=LogRecipeTaskResult, ) @cherrypy.expose @identity.require(identity.not_anonymous()) def by_log_server(self, server, limit=50): """ Returns a list of recipe IDs which have logs stored on the given server. By default, returns at most 50 at a time. Only returns recipes where the whole recipe set has completed. Also excludes recently completed recipe sets, since the system may continue uploading logs for a short while until beaker-provision powers it off. """ finish_threshold = datetime.utcnow() - timedelta(minutes=2) recipes = Recipe.query.join(Recipe.recipeset)\ .join(RecipeSet.job)\ .filter(not_(Job.is_deleted))\ .filter(RecipeSet.status.in_([s for s in TaskStatus if s.finished]))\ .filter(not_(RecipeSet.recipes.any(Recipe.finish_time >= finish_threshold)))\ .filter(Recipe.log_server == server)\ .limit(limit) return [recipe_id for recipe_id, in recipes.values(Recipe.id)] @cherrypy.expose @identity.require(identity.not_anonymous()) def register_file(self, server, recipe_id, path, filename, basepath): """ register file and return path to store """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) if recipe.is_finished(): raise BX('Cannot register file for finished recipe %s' % recipe.t_id) # Add the log to the DB if it hasn't been recorded yet. log_recipe = LogRecipe.lazy_create( recipe_id=recipe.id, path=path, filename=filename, ) log_recipe.server = server log_recipe.basepath = basepath # Pull log_server out of server_url. recipe.log_server = urlparse.urlparse(server)[1] return '%s' % recipe.filepath @cherrypy.expose @identity.require(identity.not_anonymous()) def files(self, recipe_id): """ Return an array of logs for the given recipe. :param recipe_id: id of recipe :type recipe_id: integer .. deprecated:: 0.9.4 Use :meth:`taskactions.files() <bkr.server.task_actions.taskactions.files>` instead. """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) # Build a list of logs excluding duplicate paths, to mitigate: # https://bugzilla.redhat.com/show_bug.cgi?id=963492 logdicts = [] seen_paths = set() for log in recipe.all_logs(): logdict = log.dict # The path we care about here is the path which beaker-transfer # will move the file to. # Don't be tempted to use os.path.join() here since log['path'] # is often '/' which does not give the result you would expect. path = os.path.normpath( '%s/%s/%s' % (logdict['filepath'], logdict['path'], logdict['filename'])) if path in seen_paths: logger.warn('%s contains duplicate log %s', log.parent.t_id, path) else: seen_paths.add(path) logdicts.append(logdict) return logdicts @cherrypy.expose @identity.require(identity.in_group('lab_controller')) def change_files(self, recipe_id, server, basepath): """ Change the server and basepath where the log files lives, Usually used to move from lab controller cache to archive storage. """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) for mylog in recipe.all_logs(): mylog.server = '%s/%s/' % (server, mylog.parent.filepath) mylog.basepath = '%s/%s/' % (basepath, mylog.parent.filepath) recipe.log_server = urlparse.urlparse(server)[1] return True @cherrypy.expose @identity.require(identity.not_anonymous()) def extend(self, recipe_id, kill_time): """ Extend recipe watchdog by kill_time seconds """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) return recipe.extend(kill_time) @cherrypy.expose def console_output(self, recipe_id, output_length=None, offset=None): """ Get text console log output from OpenStack """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) manager = dynamic_virt.VirtManager(recipe.recipeset.job.owner) return manager.get_console_output(recipe.resource.instance_id, output_length) @cherrypy.expose def watchdog(self, recipe_id): try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) return recipe.status_watchdog() @cherrypy.expose @identity.require(identity.not_anonymous()) def stop(self, recipe_id, stop_type, msg=None): """ Set recipe status to Completed """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) if stop_type not in recipe.stop_types: raise BX( _('Invalid stop_type: %s, must be one of %s' % (stop_type, recipe.stop_types))) kwargs = dict(msg=msg) return getattr(recipe, stop_type)(**kwargs) @cherrypy.expose @identity.require(identity.not_anonymous()) def install_start(self, recipe_id=None): """ Records the start of a recipe's installation. The watchdog is extended by 3 hours to allow the installation to complete. """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % recipe_id)) if not recipe.installation: raise BX(_('Recipe %s not provisioned yet') % recipe_id) installation = recipe.installation if not installation.install_started: installation.install_started = datetime.utcnow() # extend watchdog by 3 hours 60 * 60 * 3 kill_time = 10800 logger.debug('Extending watchdog for %s', recipe.t_id) recipe.extend(kill_time) return True else: logger.debug('Already recorded install_started for %s', recipe.t_id) return False @cherrypy.expose @identity.require(identity.not_anonymous()) def postinstall_done(self, recipe_id=None): """ Report completion of postinstallation """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_(u'Invalid Recipe ID %s' % recipe_id)) if not recipe.installation: raise BX(_('Recipe %s not provisioned yet') % recipe_id) recipe.installation.postinstall_finished = datetime.utcnow() return True @cherrypy.expose @identity.require(identity.not_anonymous()) def install_done(self, recipe_id=None, fqdn=None): """ Report completion of installation with current FQDN """ if not recipe_id: raise BX(_("No recipe id provided!")) try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % recipe_id)) if not recipe.installation: raise BX(_('Recipe %s not provisioned yet') % recipe_id) recipe.installation.install_finished = datetime.utcnow() # We don't want to change an existing FQDN, just set it # if it hasn't been set already (see BZ#879146) configured = recipe.resource.fqdn if configured is None and fqdn: recipe.resource.fqdn = configured = fqdn elif configured != fqdn: # We use eager formatting here to make this easier to test logger.info("Configured FQDN (%s) != reported FQDN (%s) in R:%s" % (configured, fqdn, recipe_id)) return configured @identity.require(identity.not_anonymous()) @expose() def really_return_reservation(self, id, msg=None): try: recipe = Recipe.by_id(id) except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % id)) recipe.return_reservation() flash(_(u"Successfully released reserved system for %s" % recipe.t_id)) redirect('/jobs/mine') @expose(template="bkr.server.templates.form") @identity.require(identity.not_anonymous()) def return_reservation(self, recipe_id=None): """ End recipe reservation """ if not recipe_id: raise BX(_("No recipe id provided!")) return dict( title='Release reserved system for Recipe %s' % recipe_id, form=self.return_reservation_form, action='./really_return_reservation', options={}, value=dict(id=recipe_id), ) @cherrypy.expose @identity.require(identity.not_anonymous()) def postreboot(self, recipe_id=None): # Backwards compat only, delete this after 0.10: # the recipe_id arg used to be hostname try: int(recipe_id) except ValueError: system = System.by_fqdn(recipe_id, identity.current.user) system.action_power('reboot', service=u'XMLRPC', delay=30) return system.fqdn try: recipe = Recipe.by_id(int(recipe_id)) except (InvalidRequestError, NoResultFound, ValueError): raise BX(_('Invalid recipe ID %s') % recipe_id) if isinstance(recipe.resource, SystemResource): recipe.resource.system.action_power('reboot', service=u'XMLRPC', delay=30) return True @cherrypy.expose def to_xml(self, recipe_id=None): """ Pass in recipe id and you'll get that recipe's xml """ if not recipe_id: raise BX(_("No recipe id provided!")) try: recipexml = etree.tostring(Recipe.by_id(recipe_id).to_xml(), pretty_print=True, encoding='utf8') except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % recipe_id)) return recipexml def _recipe_search(self, recipe, **kw): recipe_search = search_utility.Recipe.search(recipe) for search in kw['recipesearch']: col = search['table'] try: recipe_search.append_results(search['value'], col, search['operation'], **kw) except KeyError, e: logger.error(e) return recipe_search.return_results() return recipe_search.return_results()
class Jobs(RPCRoot): # For XMLRPC methods in this class. exposed = True job_list_action_widget = JobActionWidget() job_page_action_widget = JobPageActionWidget() recipeset_widget = RecipeSetWidget() recipe_widget = RecipeWidget() priority_widget = PriorityWidget( ) #FIXME I have a feeling we don't need this as the RecipeSet widget declares an instance of it product_widget = ProductWidget() retention_tag_widget = RetentionTagWidget() job_type = {'RS': RecipeSet, 'J': Job} whiteboard_widget = JobWhiteboard() hidden_id = widgets.HiddenField(name='id') confirm = widgets.Label(name='confirm', default="Are you sure you want to cancel?") message = widgets.TextArea(name='msg', label=_(u'Reason?'), help_text=_(u'Optional')) _upload = widgets.FileField(name='filexml', label='Job XML') form = HorizontalForm('jobs', fields=[_upload], action='save_data', submit_text=_(u'Submit Data')) del _upload cancel_form = widgets.TableForm('cancel_job', fields=[hidden_id, message, confirm], action='really_cancel', submit_text=_(u'Yes')) job_form = JobForm() job_schema_doc = lxml.etree.parse( pkg_resources.resource_stream('bkr.common', 'schema/beaker-job.rng')) @classmethod def success_redirect(cls, id, url='/jobs/mine', *args, **kw): flash(_(u'Success! job id: %s' % id)) redirect('%s' % url) @expose(template='bkr.server.templates.form-post') @identity.require(identity.not_anonymous()) def new(self, **kw): return dict( title='New Job', form=self.form, action='./clone', options={}, value=kw, ) def _check_job_deletability(self, t_id, job): if not isinstance(job, Job): raise TypeError('%s is not of type %s' % (t_id, Job.__name__)) if not job.can_delete(identity.current.user): raise BeakerException( _(u'You do not have permission to delete %s' % t_id)) def _delete_job(self, t_id): job = TaskBase.get_by_t_id(t_id) self._check_job_deletability(t_id, job) Job.delete_jobs([job]) return [t_id] @expose() @identity.require(identity.not_anonymous()) @restrict_http_method('post') def delete_job_page(self, t_id): try: self._delete_job(t_id) flash(_(u'Succesfully deleted %s' % t_id)) except (BeakerException, TypeError): flash(_(u'Unable to delete %s' % t_id)) redirect('.') redirect('./mine') @expose() @identity.require(identity.not_anonymous()) @restrict_http_method('post') def delete_job_row(self, t_id): try: self._delete_job(t_id) return [t_id] except (BeakerException, TypeError), e: log.debug(str(e)) response.status = 400 return ['Unable to delete %s' % t_id]
class RecipeSets(RPCRoot): # For XMLRPC methods in this class. exposed = True hidden_id = widgets.HiddenField(name='id') confirm = widgets.Label(name='confirm', default="Are you sure you want to cancel?") message = widgets.TextArea(name='msg', label=_(u'Reason?'), help_text=_(u'Optional')) cancel_form = widgets.TableForm('cancel_recipeset', fields=[hidden_id, message, confirm], action='really_cancel', submit_text=_(u'Yes')) @identity.require(identity.not_anonymous()) @expose(template="bkr.server.templates.form") def cancel(self, id): """ Confirm cancel recipeset """ try: recipeset = RecipeSet.by_id(id) except InvalidRequestError: flash(_(u"Invalid recipeset id %s" % id)) redirect("/jobs/%s" % recipeset.job.id) if not recipeset.can_cancel(identity.current.user): flash( _(u"You don't have permission to cancel recipeset id %s" % id)) redirect("/jobs/%s" % recipeset.job.id) return dict( title='Cancel RecipeSet %s' % id, form=self.cancel_form, action='./really_cancel', options={}, value=dict(id=recipeset.id, confirm='really cancel recipeset %s?' % id), ) @identity.require(identity.not_anonymous()) @expose() def really_cancel(self, id, msg=None): """ Confirm cancel recipeset """ try: recipeset = RecipeSet.by_id(id) except InvalidRequestError: flash(_(u"Invalid recipeset id %s" % id)) redirect("/jobs/%s" % recipeset.job.id) if not recipeset.can_cancel(identity.current.user): flash( _(u"You don't have permission to cancel recipeset id %s" % id)) redirect("/jobs/%s" % recipeset.job.id) recipeset.cancel(msg) recipeset.record_activity(user=identity.current.user, service=u'WEBUI', field=u'Status', action=u'Cancelled', old='', new='') flash(_(u"Successfully cancelled recipeset %s" % id)) redirect("/jobs/%s" % recipeset.job.id) @cherrypy.expose @identity.require(identity.not_anonymous()) def stop(self, recipeset_id, stop_type, msg=None): """ Set recipeset status to Completed """ try: recipeset = RecipeSet.by_id(recipeset_id) except InvalidRequestError: raise BX(_('Invalid recipeset ID: %s' % recipeset_id)) if stop_type not in recipeset.stop_types: raise BX( _('Invalid stop_type: %s, must be one of %s' % (stop_type, recipeset.stop_types))) kwargs = dict(msg=msg) return getattr(recipeset, stop_type)(**kwargs)
class Recipes(RPCRoot): # For XMLRPC methods in this class. exposed = True hidden_id = widgets.HiddenField(name='id') confirm = widgets.Label( name='confirm', default="Are you sure you want to release the system?") return_reservation_form = widgets.TableForm( 'end_recipe_reservation', fields=[hidden_id, confirm], action='./really_return_reservation', submit_text=_(u'Yes')) tasks = RecipeTasks() recipe_widget = RecipeWidget() log_types = dict( R=LogRecipe, T=LogRecipeTask, E=LogRecipeTaskResult, ) @cherrypy.expose @identity.require(identity.not_anonymous()) def by_log_server(self, server, limit=50): """ Returns a list of recipe IDs which have logs stored on the given server. By default, returns at most 50 at a time. Only returns recipes where the whole recipe set has completed. Also excludes recently completed recipe sets, since the system may continue uploading logs for a short while until beaker-provision powers it off. """ finish_threshold = datetime.utcnow() - timedelta(minutes=2) recipes = Recipe.query.join(Recipe.recipeset)\ .filter(RecipeSet.status.in_([s for s in TaskStatus if s.finished]))\ .filter(not_(RecipeSet.recipes.any(Recipe.finish_time >= finish_threshold)))\ .filter(Recipe.log_server == server)\ .limit(limit) return [recipe_id for recipe_id, in recipes.values(Recipe.id)] @cherrypy.expose @identity.require(identity.not_anonymous()) def register_file(self, server, recipe_id, path, filename, basepath): """ register file and return path to store """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) if recipe.is_finished(): raise BX('Cannot register file for finished recipe %s' % recipe.t_id) # Add the log to the DB if it hasn't been recorded yet. log_recipe = LogRecipe.lazy_create( recipe_id=recipe.id, path=path, filename=filename, ) log_recipe.server = server log_recipe.basepath = basepath # Pull log_server out of server_url. recipe.log_server = urlparse.urlparse(server)[1] return '%s' % recipe.filepath @cherrypy.expose @identity.require(identity.not_anonymous()) def files(self, recipe_id): """ Return an array of logs for the given recipe. :param recipe_id: id of recipe :type recipe_id: integer .. deprecated:: 0.9.4 Use :meth:`taskactions.files` instead. """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) return [log for log in recipe.all_logs] @cherrypy.expose @identity.require(identity.not_anonymous()) def change_files(self, recipe_id, server, basepath): """ Change the server and basepath where the log files lives, Usually used to move from lab controller cache to archive storage. """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) for mylog in recipe.all_logs: myserver = '%s/%s/' % (server, mylog['filepath']) mybasepath = '%s/%s/' % (basepath, mylog['filepath']) self.change_file(mylog['tid'], myserver, mybasepath) recipe.log_server = urlparse.urlparse(server)[1] return True @cherrypy.expose @identity.require(identity.not_anonymous()) def change_file(self, tid, server, basepath): """ Change the server and basepath where the log file lives, Usually used to move from lab controller cache to archive storage. """ log_type, log_id = tid.split(":") if log_type.upper() in self.log_types.keys(): try: mylog = self.log_types[log_type.upper()].by_id(log_id) except InvalidRequestError: raise BX(_("Invalid %s" % tid)) mylog.server = server mylog.basepath = basepath return True @cherrypy.expose @identity.require(identity.not_anonymous()) def extend(self, recipe_id, kill_time): """ Extend recipe watchdog by kill_time seconds """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) return recipe.extend(kill_time) @cherrypy.expose def console_output(self, recipe_id, output_length=None, offset=None): """ Get text console log output from OpenStack """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) manager = dynamic_virt.VirtManager(recipe.recipeset.job.owner) return manager.get_console_output(recipe.resource.instance_id, output_length) @cherrypy.expose def watchdog(self, recipe_id): try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) return recipe.status_watchdog() @cherrypy.expose @identity.require(identity.not_anonymous()) def stop(self, recipe_id, stop_type, msg=None): """ Set recipe status to Completed """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_('Invalid recipe ID: %s' % recipe_id)) if stop_type not in recipe.stop_types: raise BX( _('Invalid stop_type: %s, must be one of %s' % (stop_type, recipe.stop_types))) kwargs = dict(msg=msg) return getattr(recipe, stop_type)(**kwargs) @cherrypy.expose @identity.require(identity.not_anonymous()) def install_start(self, recipe_id=None): """ Report comencement of provisioning of a recipe's resource, extend first task's watchdog, and report 'Install Started' against it. """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % recipe_id)) first_task = recipe.first_task if not recipe.resource.install_started: recipe.resource.install_started = datetime.utcnow() # extend watchdog by 3 hours 60 * 60 * 3 kill_time = 10800 # XXX In future releases where 'Provisioning' # is a valid recipe state, we will no longer # need the following block. log.debug('Extending watchdog for %s', first_task.t_id) first_task.extend(kill_time) log.debug('Recording /start for %s', first_task.t_id) first_task.pass_(path=u'/start', score=0, summary=u'Install Started') return True else: log.debug('Already recorded /start for %s', first_task.t_id) return False @cherrypy.expose @identity.require(identity.not_anonymous()) def postinstall_done(self, recipe_id=None): """ Report completion of postinstallation """ try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_(u'Invalid Recipe ID %s' % recipe_id)) recipe.resource.postinstall_finished = datetime.utcnow() return True @cherrypy.expose @identity.require(identity.not_anonymous()) def install_done(self, recipe_id=None, fqdn=None): """ Report completion of installation with current FQDN """ if not recipe_id: raise BX(_("No recipe id provided!")) if not fqdn: raise BX(_("No fqdn provided!")) try: recipe = Recipe.by_id(recipe_id) except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % recipe_id)) recipe.resource.install_finished = datetime.utcnow() # We don't want to change an existing FQDN, just set it # if it hasn't been set already (see BZ#879146) configured = recipe.resource.fqdn if configured is None: recipe.resource.fqdn = configured = fqdn elif configured != fqdn: # We use eager formatting here to make this easier to test log.info("Configured FQDN (%s) != reported FQDN (%s) in R:%s" % (configured, fqdn, recipe_id)) return configured @identity.require(identity.not_anonymous()) @expose() def really_return_reservation(self, id, msg=None): try: recipe = Recipe.by_id(id) except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % id)) recipe.return_reservation() flash(_(u"Successfully released reserved system for %s" % recipe.t_id)) redirect('/jobs/mine') @expose(template="bkr.server.templates.form") @identity.require(identity.not_anonymous()) def return_reservation(self, recipe_id=None): """ End recipe reservation """ if not recipe_id: raise BX(_("No recipe id provided!")) return dict( title='Release reserved system for Recipe %s' % recipe_id, form=self.return_reservation_form, action='./really_return_reservation', options={}, value=dict(id=recipe_id), ) @cherrypy.expose @identity.require(identity.not_anonymous()) def postreboot(self, recipe_id=None): # Backwards compat only, delete this after 0.10: # the recipe_id arg used to be hostname try: int(recipe_id) except ValueError: system = System.by_fqdn(recipe_id, identity.current.user) system.action_power('reboot', service=u'XMLRPC', delay=30) return system.fqdn try: recipe = Recipe.by_id(int(recipe_id)) except (InvalidRequestError, NoResultFound, ValueError): raise BX(_('Invalid recipe ID %s') % recipe_id) if isinstance(recipe.resource, SystemResource): recipe.resource.system.action_power('reboot', service=u'XMLRPC', delay=30) return True @cherrypy.expose def to_xml(self, recipe_id=None): """ Pass in recipe id and you'll get that recipe's xml """ if not recipe_id: raise BX(_("No recipe id provided!")) try: recipexml = Recipe.by_id(recipe_id).to_xml().toprettyxml() except InvalidRequestError: raise BX(_("Invalid Recipe ID %s" % recipe_id)) return recipexml def _recipe_search(self, recipe, **kw): recipe_search = search_utility.Recipe.search(recipe) for search in kw['recipesearch']: col = search['table'] try: recipe_search.append_results(search['value'], col, search['operation'], **kw) except KeyError, e: log.error(e) return recipe_search.return_results() return recipe_search.return_results()
def test_label(): """Tests simple labels""" label = widgets.Label("foo") rendered = label.render("The Foo", format='xhtml') assert """<label id="foo" class="label">The Foo</label>""" == rendered