class PrivateTitleFields(widgets.WidgetsList): import operator preferred_distributor = widgets.TextField() title_id = widgets.HiddenField() all_sections = [(l.id, l.sectionName) for l in list(Section.select())] all_sections.sort(key=operator.itemgetter(1)) sections = widgets.CheckBoxList(options=all_sections, field_class='inline')
class RatingFields(widgets.WidgetsList): rater = widgets.TextField(validators=validators.NotEmpty) score = widgets.RadioButtonList(options=[(1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "5")], default=5, attrs={'class': 'ratingsform'}) comments = widgets.TextArea() title = widgets.HiddenField()
class JobForm(widgets.Form): template = 'bkr.server.templates.job_form' name = 'job' submit_text = _(u'Queue') fields = [widgets.TextArea(name='textxml')] hidden_fields = [ widgets.HiddenField(name='confirmed', validator=validators.StringBool()) ] params = ['xsd_errors'] xsd_errors = None def update_params(self, d): super(JobForm, self).update_params(d) if 'xsd_errors' in d['options']: d['xsd_errors'] = d['options']['xsd_errors'] d['submit_text'] = _(u'Queue despite validation errors')
class GroupForm(HorizontalForm): fields = [ widgets.HiddenField(name='group_id'), widgets.TextField(name='group_name', label=_(u'Group Name')), widgets.TextField(name='display_name', label=_(u'Display Name')), widgets.PasswordField(name='root_password', label=_(u'Root Password'), validator=StrongPassword()), widgets.CheckBox(name='ldap', label=_(u'LDAP'), help_text=_(u'Populate group membership from LDAP?')), ] name = 'Group' action = 'save_data' submit_text = _(u'Save') validator = GroupFormSchema() def update_params(self, d): if not identity.current.user.is_admin() or \ not config.get('identity.ldap.enabled', False): d['disabled_fields'] = ['ldap'] super(GroupForm, self).update_params(d)
class Users(AdminPage): # For XMLRPC methods in this class. exposed = True user_id = widgets.HiddenField(name='user_id') user_name = widgets.TextField(name='user_name', label=_(u'Login')) display_name = widgets.TextField(name='display_name', label=_(u'Display Name')) email_address = widgets.TextField(name='email_address', label=_(u'Email Address')) password = widgets.PasswordField(name='password', label=_(u'Password')) disabled = widgets.CheckBox(name='disabled', label=_(u'Disabled')) user_form = HorizontalForm( 'User', fields=[ user_id, user_name, display_name, email_address, password, disabled ], action='save_data', submit_text=_(u'Save'), ) def __init__(self, *args, **kw): kw['search_url'] = url("/users/by_name?anywhere=1&ldap=0") kw['search_name'] = 'user' super(Users, self).__init__(*args, **kw) self.search_col = User.user_name self.search_mapper = User @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.form') def new(self, **kw): return dict( form=self.user_form, action='./save', options={}, value=kw, ) @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.user_edit_form') def edit(self, id=None, **kw): if id: user = User.by_id(id) title = _(u'User %s') % user.user_name value = user else: user = None title = _(u'New user') value = kw return_vals = dict(form=self.user_form, action='./save', title=title, options={}, value=value) if id: return_vals['groupsgrid'] = self.show_groups() else: return_vals['groupsgrid'] = None return return_vals @identity.require(identity.in_group("admin")) @expose() @validate(user_form, validators=UserFormSchema()) @error_handler(edit) def save(self, **kw): if kw.get('user_id'): user = User.by_id(kw['user_id']) else: user = User() session.add(user) user.display_name = kw['display_name'] user.user_name = kw['user_name'] user.email_address = kw['email_address'] if kw.get('disabled') != user.disabled: user.disabled = kw.get('disabled') if user.disabled: self._disable(user, method="WEBUI") if kw['password'] != user.password: user.password = kw['password'] flash(_(u"%s saved" % user.display_name)) redirect(".") def make_remove_link(self, user): if user.removed is not None: return XML('<a class="btn" href="unremove?id=%s">' '<i class="fa fa-plus"/> Re-Add</a>' % user.user_id) else: return XML('<a class="btn" href="remove?id=%s">' '<i class="fa fa-times"/> Remove</a>' % user.user_id) @expose(template="bkr.server.templates.admin_grid") @paginate('list', default_order='user_name', limit=20) def index(self, *args, **kw): users = session.query(User) list_by_letters = set( [elem.user_name[0].capitalize() for elem in users]) result = self.process_search(**kw) if result: users = result users_grid = myPaginateDataGrid(fields=[ ('Login', lambda x: make_edit_link(x.user_name, x.user_id)), ('Display Name', lambda x: x.display_name), ('Disabled', lambda x: x.disabled), ('', lambda x: self.make_remove_link(x)), ], add_action='./new') return dict(title="Users", grid=users_grid, alpha_nav_bar=AlphaNavBar(list_by_letters, 'user'), search_widget=self.search_widget_form, list=users) @identity.require(identity.in_group("admin")) @expose() def remove(self, id, **kw): try: user = User.by_id(id) except InvalidRequestError: flash(_(u'Invalid user id %s' % id)) raise redirect('.') try: self._remove(user=user, method='WEBUI') except BX, e: flash( _(u'Failed to remove User %s, due to %s' % (user.user_name, e))) raise redirect('.') else:
class OSVersions(AdminPage): # For XMLRPC methods in this class. exposed = False id = widgets.HiddenField(name="id") alias = widgets.TextField(name="alias", validator=validators.UnicodeString(if_empty=None)) arches = CheckBoxList(name="arches", label="Arches", options=lambda: [(arch.id, arch.arch) for arch in Arch.query], validator=validators.Int()) osmajor_form = HorizontalForm( fields = [id, alias], submit_text = _(u"Edit OSMajor"), ) osversion_form = HorizontalForm( fields = [id, arches], action = "edit osversion", submit_text = _(u"Edit OSVersion"), ) def __init__(self,*args,**kw): kw['search_name'] = 'osversion' kw['search_url'] = url("/osversions/by_name?anywhere=1") super(OSVersions,self).__init__(*args,**kw) self.search_col = OSMajor.osmajor self.join = [OSVersion.osmajor] self.search_mapper = OSVersion self.add = False @identity.require(identity.in_group("admin")) @expose(template="bkr.server.templates.form") def edit(self, id=None, *args, **kw): try: osversion = OSVersion.by_id(id) except InvalidRequestError: flash(_(u"Invalid OSVersion ID %s" % id)) redirect(".") return dict(title = unicode(osversion), value = dict(id = osversion.id, arches = [arch.id for arch in osversion.arches]), form = self.osversion_form, action = "./save", options = None) @identity.require(identity.in_group("admin")) @expose(template="bkr.server.templates.osmajor") def edit_osmajor(self, id=None, *args, **kw): try: osmajor = OSMajor.by_id(id) except InvalidRequestError: flash(_(u"Invalid OSMajor ID %s" % id)) redirect(".") return dict(title = "OSMajor", value = osmajor, form = self.osmajor_form, action = "./save_osmajor", options = None) @identity.require(identity.in_group("admin")) @expose() @validate(form=osmajor_form) def save_osmajor(self, id=None, alias=None, *args, **kw): try: osmajor = OSMajor.by_id(id) except InvalidRequestError: flash(_(u"Invalid OSMajor ID %s" % id)) redirect(".") if osmajor.alias != alias: if alias: try: existing = OSMajor.by_name_alias(alias) except NoResultFound: pass else: flash(_(u'Cannot save alias %s, it is already used by %s') % (alias, existing)) redirect('.') osmajor.alias = alias flash(_(u"Changes saved for %s" % osmajor)) else: flash(_(u"No changes for %s" % osmajor)) redirect(".") @identity.require(identity.in_group('admin')) @expose() def save_osmajor_installopts(self, osmajor_id=None, installopts=None): try: osmajor = OSMajor.by_id(osmajor_id) except InvalidRequestError: flash(_(u"Invalid OSMajor ID %s" % id)) redirect(".") for arch, options in installopts.iteritems(): # arch=None means applied to all arches io = OSMajorInstallOptions.lazy_create(osmajor_id=osmajor.id, arch_id=Arch.by_name(arch).id if arch else None) io.ks_meta = options['ks_meta'] io.kernel_options = options['kernel_options'] io.kernel_options_post = options['kernel_options_post'] flash(_(u'Install options saved for %s') % osmajor) redirect('.') @identity.require(identity.in_group("admin")) @expose() @validate(form=osversion_form) def save(self, id=None, arches=None, *args, **kw): try: osversion = OSVersion.by_id(id) except InvalidRequestError: flash(_(u"Invalid OSVersion ID %s" % id)) redirect(".") arch_objects = [Arch.by_id(arch) for arch in arches] if osversion.arches != arch_objects: osversion.arches = arch_objects flash(_(u"Changes Saved for %s" % osversion)) else: flash(_(u"No Changes for %s" % osversion)) redirect(".") @expose(format='json') def by_name(self, input,*args,**kw): input = input.lower() if 'anywhere' in kw: search = OSVersion.list_osmajor_by_name(input, find_anywhere=True) else: search = OSVersion.list_osmajor_by_name(input) osmajors = ["%s" % (match.osmajor.osmajor) for match in search] osmajors = list(set(osmajors)) return dict(matches=osmajors) @expose(template="bkr.server.templates.admin_grid") @paginate('list',limit=50, default_order='osmajor.osmajor') def index(self,*args,**kw): osversions = self.process_search(*args,**kw) list_by_letters = [] for elem in osversions: osmajor_name = elem.osmajor.osmajor if osmajor_name: list_by_letters.append(osmajor_name[0].capitalize()) alpha_nav_data = set(list_by_letters) template_data = self.osversions(osversions,*args, **kw) nav_bar = self._build_nav_bar(alpha_nav_data,self.search_name) template_data['alpha_nav_bar'] = nav_bar template_data['search_widget'] = self.search_widget_form return template_data def osversions(self, osversions=None, *args, **kw): q = session.query(self.search_mapper) # This line +3 dupes the start of process_search if osversions is None: for j in self.join: q = q.join(j) osversions = q osversions_grid = myPaginateDataGrid(fields=[ myPaginateDataGrid.Column(name='osmajor.osmajor', getter=lambda x: make_link(url = './edit_osmajor?id=%s' % x.osmajor.id, text = x.osmajor), title='OS Major', options=dict(sortable=True)), myPaginateDataGrid.Column(name='osmajor.alias', getter=lambda x: x.osmajor.alias, title='Alias', options=dict(sortable=True)), myPaginateDataGrid.Column(name='osminor', getter=lambda x: make_link(url = './edit?id=%s' % x.id, text = x.osminor), title='OS Minor', options=dict(sortable=True)), myPaginateDataGrid.Column(name='arches', getter=lambda x: " ".join([arch.arch for arch in x.arches]), title='Arches', options=dict(sortable=True)), ]) return dict(title="OS Versions", grid = osversions_grid, addable = False, list = osversions) default = index
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]
def default(self, id): try: job = Job.by_id(id) except InvalidRequestError: flash(_(u"Invalid job id %s" % id)) redirect(".") if job.counts_as_deleted(): flash(_(u'Invalid %s, has been deleted' % job.t_id)) redirect(".") recipe_set_history = [ RecipeSetActivity.query.with_parent(elem, "activity") for elem in job.recipesets ] recipe_set_data = [] for query in recipe_set_history: for d in query: recipe_set_data.append(d) recipe_set_data += job.activity recipe_set_data = sorted(recipe_set_data, key=lambda x: x.created, reverse=True) job_history_grid = BeakerDataGrid( name='job_history_datagrid', fields=[ BeakerDataGrid.Column(name='user', getter=lambda x: x.user, title='User', options=dict(sortable=True)), BeakerDataGrid.Column(name='service', getter=lambda x: x.service, title='Via', options=dict(sortable=True)), BeakerDataGrid.Column(name='created', title='Created', getter=lambda x: x.created, options=dict(sortable=True)), BeakerDataGrid.Column(name='object_name', getter=lambda x: x.object_name(), title='Object', options=dict(sortable=True)), BeakerDataGrid.Column(name='field_name', getter=lambda x: x.field_name, title='Field Name', options=dict(sortable=True)), BeakerDataGrid.Column(name='action', getter=lambda x: x.action, title='Action', options=dict(sortable=True)), BeakerDataGrid.Column(name='old_value', getter=lambda x: x.old_value, title='Old value', options=dict(sortable=True)), BeakerDataGrid.Column(name='new_value', getter=lambda x: x.new_value, title='New value', options=dict(sortable=True)), ]) return_dict = dict( title='Job', recipeset_widget=self.recipeset_widget, recipe_widget=self.recipe_widget, hidden_id=widgets.HiddenField(name='job_id', value=job.id), job_history=recipe_set_data, job_history_grid=job_history_grid, whiteboard_widget=self.whiteboard_widget, action_widget=self.job_page_action_widget, delete_action=url('delete_job_page'), job=job, product_widget=self.product_widget, retention_tag_widget=self.retention_tag_widget, ) return return_dict
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 Configuration(AdminPage): exposed = False id = widgets.HiddenField(name='id') value_str = widgets.TextArea(name='value', label=_(u'Value')) value_int = widgets.TextField(name='value', label=_(u'Value'), validator=validators.Int()) valid_from = widgets.TextField( name='valid_from', label=_(u'Effective from date'), help_text= u"Enter date and time (YYYY-MM-DD HH:MM) in the future or leave blank for setting to take immediate effect" ) string_form = HorizontalForm( 'configitem', fields=[id, value_str, valid_from], action='save_data', submit_text=_(u'Save'), ) int_form = HorizontalForm( 'configitem', fields=[id, value_int, valid_from], action='save_data', submit_text=_(u'Save'), ) value_grid = BeakerDataGrid(fields=[ ('Value', lambda x: x.value), ('Effective from', lambda x: x.valid_from, {'datetime': True}), ('Set by', lambda x: x.user), ('Date set', lambda x: x.modified, {'datetime': True}), ('', lambda x: x.valid_from <= datetime.utcnow() and " " or \ make_link(url = 'delete?item=%s&id=%s' % (x.config_item.id, x.id), text = 'Delete')), ]) def __init__(self, *args, **kw): kw['search_url'] = url("/configuration/by_name?anywhere=1"), kw['search_name'] = 'name' super(Configuration, self).__init__(*args, **kw) self.search_col = ConfigItem.name self.search_mapper = ConfigItem @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.config_edit') def edit(self, **kw): if kw.get('id'): item = ConfigItem.by_id(kw['id']) form_values = dict(id=item.id, numeric=item.numeric, value=item.current_value()) else: flash(_(u"Error: No item ID specified")) raise redirect(".") # Show all future values, and the previous five config_values = item.values().filter(item.value_class.valid_from > datetime.utcnow()).order_by(item.value_class.valid_from.desc()).all() \ + item.values().filter(item.value_class.valid_from <= datetime.utcnow()).order_by(item.value_class.valid_from.desc())[:5] if item.readonly: form = None elif item.numeric: form = self.int_form else: form = self.string_form return dict( title=item.name, subtitle=item.description, form=form, action='./save', options={}, value=form_values, list=config_values, grid=self.value_grid, warn_msg=item.readonly and "This item is read-only", ) @expose() @error_handler(edit) @identity.require(identity.in_group("admin")) def save(self, **kw): if 'id' in kw and kw['id']: item = ConfigItem.by_id(kw['id']) else: flash(_(u"Error: No item ID")) raise redirect(".") if kw['valid_from']: try: valid_from = datetime.strptime(kw['valid_from'], '%Y-%m-%d %H:%M') except ValueError: flash( _(u"Invalid date and time specification, use: YYYY-MM-DD HH:MM" )) raise redirect("/configuration/edit?id=%d" % item.id) else: valid_from = None try: item.set(kw['value'], valid_from, identity.current.user) except Exception, msg: flash(_(u"Failed to save setting: %s" % msg)) raise redirect("/configuration/edit?id=%d" % item.id) flash(_(u"%s saved" % item.name)) redirect(".")
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()
class RetentionTag(AdminPage): exposed = False tag = widgets.TextField(name='tag', label=_(u'Tag')) default = widgets.SingleSelectField(name='default', label=(u'Default'), options=[(0,'False'),(1,'True')]) id = widgets.HiddenField(name='id') expire_in_days = widgets.TextField(name='expire_in_days', label=_(u'Expire In Days'), help_text=_(u'Number of days after which jobs will expire')) needs_product = widgets.CheckBox('needs_product', label=u'Needs Product') tag_form = HorizontalForm( 'Retention Tag', fields = [tag, default, expire_in_days, needs_product, id], action = 'save_data', submit_text = _(u'Save'), ) def __init__(self,*args,**kw): kw['search_url'] = url("/retentiontag/by_tag") kw['search_name'] = 'tag' kw['widget_action'] = './admin' super(RetentionTag,self).__init__(*args,**kw) self.search_col = Tag.tag self.search_mapper = Tag @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.form') def new(self, **kw): return dict( form = self.tag_form, action = './save', options = {}, value = kw, ) @identity.require(identity.in_group("admin")) @expose() @validate(form=tag_form, validators=TagFormSchema()) @error_handler(new) def save(self, id=None, **kw): retention_tag = Tag(kw['tag'], kw['default'], kw['needs_product']) retention_tag.expire_in_days = kw['expire_in_days'] session.add(retention_tag) flash(_(u"OK")) redirect("./admin") @expose(format='json') def by_tag(self, input, *args, **kw): input = input.lower() search = Tag.list_by_tag(input) tags = [match.tag for match in search] return dict(matches=tags) @expose(template="bkr.server.templates.admin_grid") @identity.require(identity.in_group('admin')) @paginate('list', default_order='tag', limit=20) def admin(self, *args, **kw): tags = self.process_search(*args, **kw) alpha_nav_data = set([elem.tag[0].capitalize() for elem in tags]) nav_bar = self._build_nav_bar(alpha_nav_data,'tag') template_data = self.tags(tags, identity.current.user, *args, **kw) template_data['alpha_nav_bar'] = nav_bar template_data['addable'] = True return template_data @identity.require(identity.in_group('admin')) @expose() def delete(self, id): tag = Tag.by_id(id) if not tag.can_delete(): # Trying to be funny... flash(u'%s is not applicable for deletion' % tag.tag) redirect('/retentiontag/admin') session.delete(tag) flash(u'Successfully deleted %s' % tag.tag) redirect('/retentiontag/admin') @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.form') def edit(self, id, **kw): tag = Tag.by_id(id) return dict( form = self.tag_form, title=_(u'Retention tag %s' % tag.tag), action = './save_edit', options = {}, value = tag, disabled_fields = ['tag'] ) @identity.require(identity.in_group("admin")) @expose() @validate(form=tag_form, validators=TagFormSchema()) @error_handler(edit) def save_edit(self, id=None, **kw): retention_tag = Tag.by_id(id) retention_tag.tag = kw['tag'] retention_tag.default = kw['default'] retention_tag.expire_in_days = kw['expire_in_days'] retention_tag.needs_product = kw['needs_product'] flash(_(u"OK")) redirect("./admin") @expose(template="bkr.server.templates.grid") @paginate('list', default_order='tag', limit=20) def index(self, *args, **kw): return self.tags() def tags(self, tags=None, user=None, *args, **kw): if tags is None: tags = Tag.get_all() def show_delete(x): if x.can_delete(): return XML('<a class="btn" href="./delete/%s">' '<i class="fa fa-times"/> Delete</a>' % x.id) else: return None def show_tag(x): if x.is_default: #If we are the default, we can't change to not default return x.tag elif user and user.is_admin(): return make_edit_link(x.tag,x.id) else: #no perms to edit return x.tag my_fields = [myPaginateDataGrid.Column(name='tag', title='Tags', getter=lambda x: show_tag(x),options=dict(sortable=True)), myPaginateDataGrid.Column(name='default', title='Default', getter=lambda x: x.default,options=dict(sortable=True)), myPaginateDataGrid.Column(name='delete', title='Delete', getter=lambda x: show_delete(x))] tag_grid = myPaginateDataGrid(fields=my_fields, add_action='./new') return_dict = dict(title='Tags', grid = tag_grid, search_bar = None, search_widget = self.search_widget_form, list = tags) return return_dict
class Groups(AdminPage): # For XMLRPC methods in this class. exposed = True group_id = widgets.HiddenField(name='group_id') auto_users = AutoCompleteField(name='user', search_controller=url("/users/by_name"), search_param="input", result_name="matches") auto_systems = AutoCompleteField(name='system', search_controller=url("/by_fqdn"), search_param="input", result_name="matches") search_groups = AutoCompleteField( name='group', search_controller=url("/groups/by_name?anywhere=1"), search_param="name", result_name="groups") search_permissions = AutoCompleteField( name='permissions', search_controller=url("/groups/get_permissions"), search_param="input", result_name="matches") group_form = GroupForm() permissions_form = InlineRemoteForm( 'Permissions', fields=[search_permissions, group_id], submit_text=_(u'Add'), on_success='add_group_permission_success(http_request.responseText)', on_failure='add_group_permission_failure(http_request.responseText)', before='before_group_permission_submit()', after='after_group_permission_submit()', ) group_user_form = InlineForm( 'GroupUser', fields=[group_id, auto_users], action='save_data', submit_text=_(u'Add'), ) group_system_form = InlineForm( 'GroupSystem', fields=[group_id, auto_systems], action='save_data', submit_text=_(u'Add'), ) delete_link = DeleteLinkWidgetForm() def __init__(self, *args, **kw): kw['search_url'] = url("/groups/by_name?anywhere=1") kw['search_name'] = 'group' kw['widget_action'] = '' super(Groups, self).__init__(*args, **kw) self.search_col = Group.group_name self.search_mapper = Group @expose(format='json') def by_name(self, input, *args, **kw): input = input.lower() if 'anywhere' in kw: search = Group.list_by_name(input, find_anywhere=True) else: search = Group.list_by_name(input) groups = [match.group_name for match in search] return dict(matches=groups) @expose(format='json') @identity.require(identity.not_anonymous()) def remove_group_permission(self, group_id, permission_id): try: group = Group.by_id(group_id) except DatabaseLookupError: log.exception('Group id %s is not a valid Group to remove' % group_id) return ['0'] if not group.can_edit(identity.current.user): log.exception( 'User %d does not have edit permissions for Group id %s' % (identity.current.user.user_id, group_id)) response.status = 403 return ['You are not an owner of group %s' % group] try: permission = Permission.by_id(permission_id) except NoResultFound: log.exception( 'Permission id %s is not a valid Permission to remove' % permission_id) return ['0'] group.permissions.remove(permission) return ['1'] @expose(format='json') def get_permissions(self, input): results = Permission.by_name(input, anywhere=True) permission_names = [result.permission_name for result in results] return dict(matches=permission_names) @identity.require(identity.not_anonymous()) @expose(template='bkr.server.templates.form') def new(self, **kw): return dict( form=self.group_form, title='New Group', action='./save_new', options={}, value=kw, ) def show_members(self, group): can_edit = False if identity.current.user: can_edit = group.can_modify_membership(identity.current.user) def show_ownership_status(member): is_owner = member.is_owner if can_edit: if is_owner: return XML('<a class="btn change_ownership_remove" ' 'href="revoke_owner?group_id=%s&id=%s">' '<i class="fa fa-times"/> Remove</a>' % (group.group_id, member.user_id)) else: return XML('<a class="btn change_ownership_add" ' 'href="grant_owner?group_id=%s&id=%s">' '<i class="fa fa-plus"/> Add</a>' % (group.group_id, member.user_id)) else: is_owner = 'Yes' if is_owner else 'No' return is_owner def remove_button(member): return XML( '<a class="btn" href="removeUser?group_id=%s&id=%s">' '<i class="fa fa-times"/> Remove</a>' % (group.group_id, member.user_id)) user_fields = [('User', lambda x: x.user.user_name)] user_fields.append(('Group Ownership', show_ownership_status)) if can_edit: user_fields.append(('Group Membership', remove_button)) return BeakerDataGrid(name='group_members_grid', fields=user_fields) @expose(template='bkr.server.templates.grid') @paginate('list', default_order='fqdn', limit=20, max_limit=None) def systems(self, group_id=None, *args, **kw): try: group = Group.by_id(group_id) except DatabaseLookupError: log.exception('Group id %s is not a valid group id' % group_id) flash(_(u'Need a valid group to search on')) redirect('../groups/mine') systems = System.all(identity.current.user). \ filter(System.groups.contains(group)). \ filter(System.status != SystemStatus.removed) title = 'Systems in Group %s' % group.group_name from bkr.server.controllers import Root return Root()._systems(systems, title, group_id=group_id, **kw) @expose(template='bkr.server.templates.group_form') def edit(self, group_id=None, group_name=None, **kw): # Not just for editing, also provides a read-only view if group_id is not None: try: group = Group.by_id(group_id) except DatabaseLookupError: log.exception('Group id %s is not a valid group id' % group_id) flash(_(u'Need a valid group to search on')) redirect('../groups/mine') elif group_name is not None: try: group = Group.by_name(group_name) except NoResultFound: log.exception('Group name %s is not a valid group name' % group_name) flash(_(u'Need a valid group to search on')) redirect('../groups/mine') else: redirect('../groups/mine') usergrid = self.show_members(group) can_edit = False if identity.current.user: can_edit = group.can_edit(identity.current.user) systems_fields = [('System', lambda x: x.link)] if can_edit: system_remove_widget = DeleteLinkWidgetForm( action='removeSystem', hidden_fields=[ widgets.HiddenField(name='group_id'), widgets.HiddenField(name='id') ], action_text=u'Remove') systems_fields.append((' ', lambda x: system_remove_widget.display( dict(group_id=group_id, id=x.id)))) systemgrid = BeakerDataGrid(fields=systems_fields) permissions_fields = [('Permission', lambda x: x.permission_name)] if can_edit: permissions_fields.append((' ', lambda x: XML( '<a class="btn" href="#" id="remove_permission_%s">' '<i class="fa fa-times"/> Remove</a>' % x.permission_id))) group_permissions_grid = BeakerDataGrid(name='group_permission_grid', fields=permissions_fields) group_permissions = GroupPermissions() return dict( form=self.group_form, system_form=self.group_system_form, user_form=self.group_user_form, group_edit_js=LocalJSLink('bkr', '/static/javascript/group_users_v2.js'), action='./save', system_action='./save_system', user_action='./save_user', options={}, value=group, group_pw=group.root_password, usergrid=usergrid, systemgrid=systemgrid, disabled_fields=[], group_permissions=group_permissions, group_form=self.permissions_form, group_permissions_grid=group_permissions_grid, ) def _new_group(self, group_id, display_name, group_name, ldap, root_password): user = identity.current.user if ldap and not user.is_admin(): flash(_(u'Only admins can create LDAP groups')) redirect('.') try: Group.by_name(group_name) except NoResultFound: pass else: flash(_(u"Group %s already exists." % group_name)) redirect(".") group = Group() session.add(group) group.record_activity(user=user, service=u'WEBUI', field=u'Group', action=u'Created') group.display_name = display_name group.group_name = group_name group.ldap = ldap if group.ldap: group.refresh_ldap_members() group.root_password = root_password if not ldap: # LDAP groups don't have owners group.user_group_assocs.append(UserGroup(user=user, is_owner=True)) group.activity.append( GroupActivity(user, service=u'WEBUI', action=u'Added', field_name=u'User', old_value=None, new_value=user.user_name)) group.activity.append( GroupActivity(user, service=u'WEBUI', action=u'Added', field_name=u'Owner', old_value=None, new_value=user.user_name)) return group @expose() @validate(form=group_form) @error_handler(new) @identity.require(identity.not_anonymous()) def save_new(self, group_id=None, display_name=None, group_name=None, ldap=False, root_password=None, **kwargs): # save_new() is needed because 'edit' is not a viable # error handler for new groups. self._new_group(group_id, display_name, group_name, ldap, root_password) flash(_(u"OK")) redirect("mine") @expose() @validate(form=group_form) @error_handler(edit) @identity.require(identity.not_anonymous()) def save(self, group_id=None, display_name=None, group_name=None, ldap=False, root_password=None, **kwargs): user = identity.current.user if ldap and not user.is_admin(): flash(_(u'Only admins can create LDAP groups')) redirect('mine') try: group = Group.by_id(group_id) except DatabaseLookupError: flash(_(u"Group %s does not exist." % group_id)) redirect('mine') try: Group.by_name(group_name) except NoResultFound: pass else: if group_name != group.group_name: flash( _(u'Failed to update group %s: Group name already exists: %s' % (group.group_name, group_name))) redirect('mine') if not group.can_edit(user): flash(_(u'You are not an owner of group %s' % group)) redirect('../groups/mine') try: group.set_name(user, u'WEBUI', group_name) group.set_display_name(user, u'WEBUI', display_name) group.ldap = ldap group.set_root_password(user, u'WEBUI', root_password) except BeakerException, err: session.rollback() flash(_(u'Failed to update group %s: %s' % (group.group_name, err))) redirect('.') flash(_(u"OK")) redirect("mine")
def edit(self, group_id=None, group_name=None, **kw): # Not just for editing, also provides a read-only view if group_id is not None: try: group = Group.by_id(group_id) except DatabaseLookupError: log.exception('Group id %s is not a valid group id' % group_id) flash(_(u'Need a valid group to search on')) redirect('../groups/mine') elif group_name is not None: try: group = Group.by_name(group_name) except NoResultFound: log.exception('Group name %s is not a valid group name' % group_name) flash(_(u'Need a valid group to search on')) redirect('../groups/mine') else: redirect('../groups/mine') usergrid = self.show_members(group) can_edit = False if identity.current.user: can_edit = group.can_edit(identity.current.user) systems_fields = [('System', lambda x: x.link)] if can_edit: system_remove_widget = DeleteLinkWidgetForm( action='removeSystem', hidden_fields=[ widgets.HiddenField(name='group_id'), widgets.HiddenField(name='id') ], action_text=u'Remove') systems_fields.append((' ', lambda x: system_remove_widget.display( dict(group_id=group_id, id=x.id)))) systemgrid = BeakerDataGrid(fields=systems_fields) permissions_fields = [('Permission', lambda x: x.permission_name)] if can_edit: permissions_fields.append((' ', lambda x: XML( '<a class="btn" href="#" id="remove_permission_%s">' '<i class="fa fa-times"/> Remove</a>' % x.permission_id))) group_permissions_grid = BeakerDataGrid(name='group_permission_grid', fields=permissions_fields) group_permissions = GroupPermissions() return dict( form=self.group_form, system_form=self.group_system_form, user_form=self.group_user_form, group_edit_js=LocalJSLink('bkr', '/static/javascript/group_users_v2.js'), action='./save', system_action='./save_system', user_action='./save_user', options={}, value=group, group_pw=group.root_password, usergrid=usergrid, systemgrid=systemgrid, disabled_fields=[], group_permissions=group_permissions, group_form=self.permissions_form, group_permissions_grid=group_permissions_grid, )
class KeyTypes(AdminPage): # For XMLRPC methods in this class. exposed = False id = widgets.HiddenField(name='id') key_name = widgets.TextField(name='key_name', label=_(u'Name')) numeric = widgets.CheckBox(name='numeric', label=_(u'Numeric')) form = HorizontalForm( 'keytypes', fields = [id, key_name, numeric], action = 'save_data', submit_text = _(u'Submit Data'), ) def __init__(self,*args,**kw): kw['search_url'] = url("/keytypes/by_name?anywhere=1"), kw['search_name'] = 'key' super(KeyTypes,self).__init__(*args,**kw) self.search_col = Key.key_name self.search_mapper = Key @expose(template='bkr.server.templates.form') def new(self, **kw): return dict( title=_(u'New Key Type'), form = self.form, action = './save', options = {}, value = kw, ) @expose(template='bkr.server.templates.form') def edit(self,**kw): values = [] if kw.get('id'): key = Key.by_id(kw['id']) values = dict( id = key.id, key_name = key.key_name, numeric = key.numeric ) return dict( form = self.form, action = './save', options = {}, value = values, ) @expose() @error_handler(edit) def save(self, **kw): if kw['id']: key = Key.by_id(kw['id']) key.key_name = kw['key_name'] else: key = Key(key_name=kw['key_name']) session.add(key) if 'numeric' in kw: key.numeric = kw['numeric'] flash( _(u"OK") ) redirect(".") @expose(template="bkr.server.templates.admin_grid") @paginate('list') def index(self,*args,**kw): keytypes = session.query(Key) list_by_letters = set([elem.key_name[0].capitalize() for elem in keytypes]) results = self.process_search(**kw) if results: keytypes = results.order_by(Key.key_name) keytypes_grid = myPaginateDataGrid(fields=[ ('Key', lambda x: make_edit_link(x.key_name, x.id)), ('Numeric', lambda x: x.numeric), (' ', lambda x: make_remove_link(x.id)), ], add_action='./new') return dict(title="Key Types", grid = keytypes_grid, search_widget = self.search_widget_form, alpha_nav_bar = AlphaNavBar(list_by_letters,self.search_name), list = keytypes) @expose() def remove(self, **kw): remove = Key.by_id(kw['id']) session.delete(remove) flash( _(u"%s Deleted") % remove.key_name ) raise redirect(".") @expose(format='json') def by_name(self,input,*args,**kw): if 'anywhere' in kw: search = Key.list_by_name(input,find_anywhere=True) else: search = Key.list_by_name(input) keys = [elem.key_name for elem in search] return dict(matches=keys)
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 PowerTypes(AdminPage): # For XMLRPC methods in this class. exposed = False id = widgets.HiddenField(name='id') name = widgets.TextField(name='name', label=_(u'Name')) form = HorizontalForm( 'powertypes', fields=[id, name], action='save_data', submit_text=_(u'Save'), ) def __init__(self, *args, **kw): kw['search_url'] = url("/powertypes/by_name?anywhere=1"), kw['search_name'] = 'power' super(PowerTypes, self).__init__(*args, **kw) self.search_col = PowerType.name self.search_mapper = PowerType @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.form') def new(self, **kw): return dict( form=self.form, title=_(u'New Power Type'), action='./save', options={}, value=kw, ) @identity.require(identity.in_group("admin")) @expose(template='bkr.server.templates.form') def edit(self, **kw): title = _(u'New Power Type') values = [] if kw.get('id'): powertype = PowerType.by_id(kw['id']) title = powertype.name values = dict( id=powertype.id, name=powertype.name, ) return dict( form=self.form, title=title, action='./save', options={}, value=values, ) @identity.require(identity.in_group("admin")) @expose() @error_handler(edit) def save(self, **kw): if kw['id']: edit = PowerType.by_id(kw['id']) edit.name = kw['name'] elif kw.get('name'): new = PowerType(name=kw['name']) session.add(new) else: flash(_(u"Invalid Power Type entry")) redirect(".") flash(_(u"OK")) redirect(".") @expose(format='json') def by_name(self, input, *args, **kw): if 'anywhere' in kw: search = PowerType.list_by_name(input, find_anywhere=True) else: search = PowerType.list_by_name(input) powers = [elem.name for elem in search] return dict(matches=powers) @expose(template="bkr.server.templates.admin_grid") @paginate('list', default_order='name', limit=20) def index(self, *args, **kw): powertypes = session.query(PowerType) list_by_letters = set( [elem.name[0].capitalize() for elem in powertypes if elem.name]) results = self.process_search(**kw) if results: powertypes = results can_edit = identity.current.user and identity.current.user.is_admin() powertypes_grid = myPaginateDataGrid( fields=[ ('Power Type', lambda x: make_edit_link(x.name, x.id) if can_edit else x.name), (' ', lambda x: make_remove_link(x.id) if can_edit else None), ], add_action='./new' if can_edit else None) return dict(title="Power Types", grid=powertypes_grid, search_widget=self.search_widget_form, alpha_nav_bar=AlphaNavBar(list_by_letters, 'power'), list=powertypes) @identity.require(identity.in_group("admin")) @expose() def remove(self, **kw): remove = PowerType.by_id(kw['id']) session.delete(remove) flash(_(u"%s Deleted") % remove.name) raise redirect(".")