예제 #1
0
class Watchdogs(RPCRoot):
    exposed = True

    @expose('bkr.server.templates.grid')
    @paginate('list', limit=50, max_limit=None)
    def index(self, *args, **kw):
        query = Watchdog.by_status(status=u'active')\
                .join(Watchdog.recipe).join(Recipe.recipeset).join(RecipeSet.job)\
                .order_by(Job.id)\
                .options(
                    joinedload_all(Watchdog.recipe, Recipe.recipeset, RecipeSet.job),
                    joinedload_all(Watchdog.recipe, Recipe.recipeset, RecipeSet.lab_controller),
                    joinedload_all(Watchdog.recipetask, RecipeTask.task))

        col = myPaginateDataGrid.Column
        fields = [
            col(name='job_id',
                getter=lambda x: x.recipe.recipeset.job.link,
                title="Job ID"),
            col(name='system_name',
                getter=lambda x: x.recipe.resource.link,
                title="System"),
            col(name='lab_controller',
                getter=lambda x: x.recipe.recipeset.lab_controller,
                title="Lab Controller"),
            col(name='task_name',
                getter=lambda x: x.recipetask.name_markup
                if x.recipetask is not None else None,
                title="Task Name"),
            col(name='kill_time',
                getter=lambda x: x.kill_time,
                title="Kill Time",
                options=dict(datetime=True))
        ]

        watchdog_grid = myPaginateDataGrid(fields=fields)
        return dict(title="Watchdogs",
                    grid=watchdog_grid,
                    search_bar=None,
                    list=query)

    # TODO: future cleanup so that the correct error message
    # is given to the client code.
    @identity.require(identity.in_group('admin'))
    @cherrypy.expose
    def extend(self, time):
        '''Allow admins to push watchdog times out after an outage'''

        watchdogs = []
        for w in Watchdog.by_status(status=u'active'):
            n_kill_time = w.kill_time + timedelta(seconds=time)
            watchdogs.append("R:%s watchdog moved from %s to %s" %
                             (w.recipe_id, w.kill_time, n_kill_time))
            w.kill_time = n_kill_time

        if watchdogs:
            return "\n".join(watchdogs)
        else:
            return 'No active watchdogs found'
예제 #2
0
파일: user.py 프로젝트: walbon/beaker
class Users(RPCRoot):
    # For XMLRPC methods in this class.
    exposed = True

    @cherrypy.expose
    @identity.require(identity.in_group('admin'))
    def remove_account(self, username, newowner=None):
        """
        Removes a Beaker user account. When the account is removed:

        * it is removed from all groups and access policies
        * any running jobs owned by the account are cancelled
        * any systems reserved by or loaned to the account are returned
        * any systems and system pools owned by the account are transferred to
          the admin running this command, or some other user if specified using
          the *newowner* parameter
        * the account is disabled for further login

        :param username: An existing username
        :param newowner: An optional username to assign all systems to.
        :type username: string
        """
        kwargs = {}

        try:
            user = User.by_user_name(username)
        except InvalidRequestError:
            raise BX(_('Invalid User %s ' % username))

        if newowner:
            owner = User.by_user_name(newowner)
            if owner is None:
                raise BX(_('Invalid user name for owner %s' % newowner))
            kwargs['newowner'] = owner

        if user.removed:
            raise BX(_('User already removed %s' % username))

        _remove(user=user, method='XMLRPC', **kwargs)
예제 #3
0
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()
예제 #4
0
            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(".")

    @identity.require(identity.in_group("admin"))
    @expose(template="bkr.server.templates.admin_grid")
    @paginate('list')
    def index(self, *args, **kw):
        configitems = session.query(ConfigItem)
        list_by_letters = set([elem.name[0].capitalize() for elem in configitems])
        results = self.process_search(**kw)
        if results:
            configitems = results
        configitems_grid = myPaginateDataGrid(fields=[
                                  ('Setting', lambda x: make_edit_link(x.name, x.id)),
                                  ('Description', lambda x: x.description),
                                  ('Current Value', lambda x: x.current_value()),
                                  ('Next Value', lambda x: x.next_value() and u'"%s" from %s' % (x.next_value().value, x.next_value().valid_from)),
                                  (' ', lambda x: (x.readonly or x.current_value() is None) and " " or
                                        make_link(url='remove?id=%s' % x.id,
예제 #5
0
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:
예제 #6
0
            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:
            flash(_(u'User %s removed') % user.user_name)
            redirect('.')

    @identity.require(identity.in_group("admin"))
    @expose()
    def unremove(self, id, **kw):
        try:
            user = User.by_id(id)
        except InvalidRequestError:
            flash(_(u'Invalid user id %s' % id))
            raise redirect('.')
        flash(_(u'%s Re-Added') % user.display_name)
        try:
            self._unremove(user=user)
        except BX, e:
            flash(_(u'Failed to Re-Add User %s, due to %s' % e))
        raise redirect('.')

    @cherrypy.expose
예제 #7
0
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
예제 #8
0
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

    @identity.require(identity.in_group("admin"))
    @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,
        )

    @identity.require(identity.in_group("admin"))
    @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,
        )

    @identity.require(identity.in_group("admin"))
    @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)
        can_edit = identity.current.user and identity.current.user.is_admin()
        keytypes_grid = myPaginateDataGrid(
            fields=[
                ('Key', lambda x: make_edit_link(x.key_name, x.id)
                 if can_edit else x.key_name),
                ('Numeric', lambda x: x.numeric),
                (' ', lambda x: make_remove_link(x.id) if can_edit else None),
            ],
            add_action='./new' if can_edit else None)
        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)

    @identity.require(identity.in_group("admin"))
    @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)
예제 #9
0
class LabControllers(RPCRoot):
    # For XMLRPC methods in this class.
    exposed = True

    labcontroller_form = LabControllerForm()

    @identity.require(identity.in_group("admin"))
    @expose(template='bkr.server.templates.form-post')
    def new(self, **kw):
        return dict(form=self.labcontroller_form,
                    action='./save',
                    options={},
                    value=kw,
                    title='New Lab Controller')

    @identity.require(identity.in_group("admin"))
    @expose(template='bkr.server.templates.form-post')
    def edit(self, id, **kw):
        options = {}
        if id:
            labcontroller = LabController.by_id(id)
            options.update({'user': labcontroller.user})
        else:
            labcontroller = None

        return dict(form=self.labcontroller_form,
                    action='./save',
                    options=options,
                    value=labcontroller,
                    title='Edit Lab Controller')

    @identity.require(identity.in_group("admin"))
    @expose()
    @validate(form=labcontroller_form)
    @error_handler(edit)
    def save(self, **kw):
        if kw.get('id'):
            labcontroller = LabController.by_id(kw['id'])
        else:
            labcontroller = LabController()
            session.add(labcontroller)
        if labcontroller.fqdn != kw['fqdn']:
            activity = LabControllerActivity(identity.current.user, 'WEBUI',
                                             'Changed', 'FQDN',
                                             labcontroller.fqdn, kw['fqdn'])
            labcontroller.fqdn = kw['fqdn']
            labcontroller.write_activity.append(activity)

        # labcontroller.user is used by the lab controller to login here
        try:
            # pick up an existing user if it exists.
            luser = User.query.filter_by(user_name=kw['lusername']).one()
        except InvalidRequestError:
            # Nope, create from scratch
            luser = User()
        if labcontroller.user != luser:
            if labcontroller.user is None:
                old_user_name = None
            else:
                old_user_name = labcontroller.user.user_name
            activity = LabControllerActivity(identity.current.user, 'WEBUI',
                                             'Changed', 'User', old_user_name,
                                             unicode(kw['lusername']))
            labcontroller.user = luser
            labcontroller.write_activity.append(activity)

        # Make sure user is a member of lab_controller group
        group = Group.by_name(u'lab_controller')
        if group not in luser.groups:
            luser.groups.append(group)
        luser.display_name = kw['fqdn']
        luser.email_address = kw['email']
        luser.user_name = kw['lusername']

        if kw['lpassword']:
            luser.password = kw['lpassword']
        if labcontroller.disabled != kw['disabled']:
            activity = LabControllerActivity(identity.current.user, 'WEBUI',
                                             'Changed', 'Disabled',
                                             unicode(labcontroller.disabled),
                                             unicode(kw['disabled']))
            labcontroller.disabled = kw['disabled']
            labcontroller.write_activity.append(activity)

        flash(_(u"%s saved" % labcontroller.fqdn))
        redirect(".")

    @cherrypy.expose
    @identity.require(identity.in_group("lab_controller"))
    def add_distro_tree(self, new_distro):
        lab_controller = identity.current.user.lab_controller

        variant = new_distro.get('variant')
        arch = Arch.lazy_create(arch=new_distro['arch'])

        osmajor = OSMajor.lazy_create(osmajor=new_distro['osmajor'])
        try:
            osmajor = OSMajor.by_alias(new_distro['osmajor'])
        except NoResultFound:
            pass
        else:
            raise BX(
                _('Cannot import distro as %s: it is configured as an alias for %s'
                  % (new_distro['osmajor'], osmajor.osmajor)))

        osversion = OSVersion.lazy_create(osmajor=osmajor,
                                          osminor=new_distro['osminor'])
        if 'arches' in new_distro:
            for arch_name in new_distro['arches']:
                osversion.add_arch(Arch.lazy_create(arch=arch_name))
        osversion.add_arch(arch)

        distro = Distro.lazy_create(name=new_distro['name'],
                                    osversion=osversion)
        # Automatically tag the distro if tags exists
        if 'tags' in new_distro:
            for tag in new_distro['tags']:
                distro.add_tag(tag)
        distro.date_created = datetime.utcfromtimestamp(
            float(new_distro['tree_build_time']))

        distro_tree = DistroTree.lazy_create(distro=distro,
                                             variant=variant,
                                             arch=arch)
        distro_tree.date_created = datetime.utcfromtimestamp(
            float(new_distro['tree_build_time']))

        if 'repos' in new_distro:
            for repo in new_distro['repos']:
                dtr = DistroTreeRepo.lazy_create(distro_tree=distro_tree,
                                                 repo_id=repo['repoid'],
                                                 repo_type=repo['type'],
                                                 path=repo['path'])

        if 'kernel_options' in new_distro:
            distro_tree.kernel_options = new_distro['kernel_options']

        if 'kernel_options_post' in new_distro:
            distro_tree.kernel_options_post = new_distro['kernel_options_post']

        if 'ks_meta' in new_distro:
            distro_tree.ks_meta = new_distro['ks_meta']

        if 'images' in new_distro:
            for image in new_distro['images']:
                try:
                    image_type = ImageType.from_string(image['type'])
                except ValueError:
                    continue  # ignore
                if 'kernel_type' not in image:
                    image['kernel_type'] = 'default'
                try:
                    kernel_type = KernelType.by_name(image['kernel_type'])
                except ValueError:
                    continue  # ignore
                dti = DistroTreeImage.lazy_create(distro_tree=distro_tree,
                                                  image_type=image_type,
                                                  kernel_type=kernel_type,
                                                  path=image['path'])

        new_urls_by_scheme = dict(
            (urlparse.urlparse(url).scheme, url) for url in new_distro['urls'])
        if None in new_urls_by_scheme:
            raise ValueError('URL %r is not absolute' %
                             new_urls_by_scheme[None])
        for lca in distro_tree.lab_controller_assocs:
            if lca.lab_controller == lab_controller:
                scheme = urlparse.urlparse(lca.url).scheme
                new_url = new_urls_by_scheme.pop(scheme, None)
                if new_url != None and lca.url != new_url:
                    distro_tree.activity.append(
                        DistroTreeActivity(user=identity.current.user,
                                           service=u'XMLRPC',
                                           action=u'Changed',
                                           field_name=u'lab_controller_assocs',
                                           old_value=u'%s %s' %
                                           (lca.lab_controller, lca.url),
                                           new_value=u'%s %s' %
                                           (lca.lab_controller, new_url)))
                    lca.url = new_url
        for url in new_urls_by_scheme.values():
            distro_tree.lab_controller_assocs.append(
                LabControllerDistroTree(lab_controller=lab_controller,
                                        url=url))
            distro_tree.activity.append(
                DistroTreeActivity(user=identity.current.user,
                                   service=u'XMLRPC',
                                   action=u'Added',
                                   field_name=u'lab_controller_assocs',
                                   old_value=None,
                                   new_value=u'%s %s' % (lab_controller, url)))

        return distro_tree.id

    @cherrypy.expose
    @identity.require(identity.in_group("lab_controller"))
    def remove_distro_trees(self, distro_tree_ids):
        lab_controller = identity.current.user.lab_controller
        for distro_tree_id in distro_tree_ids:
            distro_tree = DistroTree.by_id(distro_tree_id)
            distro_tree.expire(lab_controller=lab_controller)
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def get_queued_command_details(self):
        lab_controller = identity.current.user.lab_controller
        max_running_commands = config.get('beaker.max_running_commands')
        if max_running_commands:
            running_commands = CommandActivity.query\
                    .join(CommandActivity.system)\
                    .filter(System.lab_controller == lab_controller)\
                    .filter(CommandActivity.status == CommandStatus.running)\
                    .count()
            if running_commands >= max_running_commands:
                return []
        query = CommandActivity.query\
                .join(CommandActivity.system)\
                .options(contains_eager(CommandActivity.system))\
                .filter(System.lab_controller == lab_controller)\
                .filter(CommandActivity.status == CommandStatus.queued)\
                .order_by(CommandActivity.id)
        if max_running_commands:
            query = query.limit(max_running_commands - running_commands)
        result = []
        for cmd in query:
            d = {
                'id': cmd.id,
                'action': cmd.action,
                'fqdn': cmd.system.fqdn,
                'delay': 0,
                'quiescent_period': cmd.quiescent_period
            }
            if cmd.delay_until:
                d['delay'] = max(
                    0, total_seconds(cmd.delay_until - datetime.utcnow()))
            # Fill in details specific to the type of command
            if cmd.action in (u'on', u'off', u'reboot', u'interrupt'):
                if not cmd.system.power:
                    cmd.abort(u'Power control unavailable for %s' % cmd.system)
                    continue
                d['power'] = {
                    'type': cmd.system.power.power_type.name,
                    'address': cmd.system.power.power_address,
                    'id': cmd.system.power.power_id,
                    'user': cmd.system.power.power_user,
                    'passwd': cmd.system.power.power_passwd,
                }
            elif cmd.action == u'configure_netboot':
                distro_tree_url = cmd.distro_tree.url_in_lab(
                    lab_controller, scheme=['http', 'ftp'])
                if not distro_tree_url:
                    cmd.abort(
                        u'No usable URL found for distro tree %s in lab %s' %
                        (cmd.distro_tree.id, lab_controller.fqdn))
                    continue

                if cmd.system.kernel_type.uboot:
                    by_kernel = ImageType.uimage
                    by_initrd = ImageType.uinitrd
                else:
                    by_kernel = ImageType.kernel
                    by_initrd = ImageType.initrd

                kernel = cmd.distro_tree.image_by_type(by_kernel,
                                                       cmd.system.kernel_type)
                if not kernel:
                    cmd.abort(u'Kernel image not found for distro tree %s' %
                              cmd.distro_tree.id)
                    continue
                initrd = cmd.distro_tree.image_by_type(by_initrd,
                                                       cmd.system.kernel_type)
                if not initrd:
                    cmd.abort(u'Initrd image not found for distro tree %s' %
                              cmd.distro_tree.id)
                    continue
                d['netboot'] = {
                    'arch': cmd.distro_tree.arch.arch,
                    'distro_tree_id': cmd.distro_tree.id,
                    'kernel_url': urlparse.urljoin(distro_tree_url,
                                                   kernel.path),
                    'initrd_url': urlparse.urljoin(distro_tree_url,
                                                   initrd.path),
                    'kernel_options': cmd.kernel_options or '',
                }
            result.append(d)
        return result

    @cherrypy.expose
    def get_last_netboot_for_system(self, fqdn):
        """
        This is only a temporary API for bz828927 until installation tracking 
        is properly fleshed out. At that point beaker-proxy should be updated 
        to use the new API.
        """
        cmd = CommandActivity.query\
                .join(CommandActivity.system)\
                .options(contains_eager(CommandActivity.system))\
                .filter(System.fqdn == fqdn)\
                .filter(CommandActivity.action == u'configure_netboot')\
                .filter(CommandActivity.status == CommandStatus.completed)\
                .order_by(CommandActivity.id.desc())\
                .first()
        if not cmd:
            raise ValueError('System %s has never been provisioned' % fqdn)
        distro_tree_url = cmd.distro_tree.url_in_lab(cmd.system.lab_controller,
                                                     'http')
        if not distro_tree_url:
            raise ValueError(
                'No usable URL found for distro tree %s in lab %s' %
                (cmd.distro_tree.id, cmd.system.lab_controller.fqdn))

        if cmd.system.kernel_type.uboot:
            by_kernel = ImageType.uimage
            by_initrd = ImageType.uinitrd
        else:
            by_kernel = ImageType.kernel
            by_initrd = ImageType.initrd

        kernel = cmd.distro_tree.image_by_type(by_kernel,
                                               cmd.system.kernel_type)
        if not kernel:
            raise ValueError('Kernel image not found for distro tree %s' %
                             cmd.distro_tree.id)
        initrd = cmd.distro_tree.image_by_type(by_initrd,
                                               cmd.system.kernel_type)
        if not initrd:
            raise ValueError('Initrd image not found for distro tree %s' %
                             cmd.distro_tree.id)
        return {
            'kernel_url':
            urlparse.urljoin(distro_tree_url, kernel.path),
            'initrd_url':
            urlparse.urljoin(distro_tree_url, initrd.path),
            'kernel_options':
            cmd.kernel_options or '',
            'distro_tree_urls': [
                lca.url for lca in cmd.distro_tree.lab_controller_assocs
                if lca.lab_controller == cmd.system.lab_controller
            ],
        }

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_running(self, command_id):
        lab_controller = identity.current.user.lab_controller
        cmd = CommandActivity.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.queued:
            raise ValueError('Command %s already run' % command_id)
        cmd.change_status(CommandStatus.running)
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_completed(self, command_id):
        lab_controller = identity.current.user.lab_controller
        cmd = CommandActivity.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.running:
            raise ValueError('Command %s not running' % command_id)
        cmd.change_status(CommandStatus.completed)
        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def add_completed_command(self, fqdn, action):
        # Reports completion of a command that was executed
        # synchronously by the lab controller
        user = identity.current.user
        system = System.by_fqdn(fqdn, user)
        cmd = CommandActivity(user=user,
                              service=u"XMLRPC",
                              action=action,
                              status=CommandStatus.completed)
        system.command_queue.append(cmd)
        session.flush()  # Populates cmd.system (needed for next call)
        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_failed(self, command_id, message=None):
        lab_controller = identity.current.user.lab_controller
        cmd = CommandActivity.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.running:
            raise ValueError('Command %s not running' % command_id)
        cmd.change_status(CommandStatus.failed)
        cmd.new_value = message
        if cmd.system.status == SystemStatus.automated:
            cmd.system.mark_broken(reason=u'Power command failed: %s' %
                                   message)
        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def clear_running_commands(self, message=None):
        """
        Called by beaker-provision on startup. Any commands which are Running
        at this point must be left over from an earlier crash.
        """
        # If the connection between the LCs and the main server is unreliable
        # commands may end up stuck in "running" state. We mitigate the
        # effects of this by purging all stale commands (those more than a
        # day old) whenever a lab controller restarts and tries to clear the
        # possibly interrupted commands for that lab.
        # We deliberately bypass the callbacks on these old commands, since
        # the affected systems may now be running unrelated recipes. Longer
        # term, we'll likely update the command system to remember the
        # associated recipe, not just the associated system
        # See https://bugzilla.redhat.com/show_bug.cgi?id=974319 and
        # https://bugzilla.redhat.com/show_bug.cgi?id=974352 for more
        # details.
        lab_controller = identity.current.user.lab_controller
        purged = (CommandActivity.__table__.update().where(
            CommandActivity.status == CommandStatus.running).where(
                CommandActivity.updated < datetime.utcnow() -
                timedelta(days=1)).values(
                    status=CommandStatus.aborted).execute())
        if purged.rowcount:
            msg = ("Aborted %d stale commands before aborting "
                   "recent running commands for %s")
            log.warn(msg, purged.rowcount, lab_controller.fqdn)
        running_commands = CommandActivity.query\
                .join(CommandActivity.system)\
                .filter(System.lab_controller == lab_controller)\
                .filter(CommandActivity.status == CommandStatus.running)
        for cmd in running_commands:
            cmd.abort(message)
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def get_distro_trees(self, filter=None):
        """
        Called by beaker-proxy. returns all active distro_trees
        for the lab controller that made the call.
        We have the lab controller do this because it may have access to 
        distros that the scheduler can't reach.
        """
        lab_controller = identity.current.user.lab_controller
        if filter is None:
            filter = {}
        if 'labcontroller' in filter and filter[
                'labcontroller'] != lab_controller.fqdn:
            raise ValueError(
                'Cannot filter on lab controller other than the currnet one')
        filter['labcontroller'] = lab_controller.fqdn
        distro_trees = DistroTrees().filter(filter)
        for dt in distro_trees:
            dt['available'] = [(lc, url) for lc, url in dt['available']
                               if lc == lab_controller.fqdn]
        return distro_trees

    def make_lc_remove_link(self, lc):
        if lc.removed is not None:
            return XML('<a class="btn" href="unremove?id=%s">'
                       '<i class="fa fa-plus"/> Re-Add</a>' % lc.id)
        else:
            return XML(
                '<a class="btn" href="#" onclick="has_watchdog(\'%s\')">'
                '<i class="fa fa-times"/> Remove</a>' % lc.id)

    @identity.require(identity.in_group("admin"))
    @expose(template="bkr.server.templates.grid")
    @paginate('list', limit=None)
    def index(self):
        labcontrollers = session.query(LabController)

        labcontrollers_grid = LabControllerDataGrid(fields=[
            ('FQDN', lambda x: make_edit_link(x.fqdn, x.id)),
            ('Disabled', lambda x: x.disabled),
            ('Removed', lambda x: x.removed),
            (' ', lambda x: self.make_lc_remove_link(x)),
        ],
                                                    add_action='./new')
        return dict(title="Lab Controllers",
                    grid=labcontrollers_grid,
                    search_bar=None,
                    list=labcontrollers)

    @identity.require(identity.in_group("admin"))
    @expose()
    def unremove(self, id):
        labcontroller = LabController.by_id(id)
        labcontroller.removed = None
        labcontroller.disabled = False

        labcontroller.record_activity(user=identity.current.user,
                                      service=u'WEBUI',
                                      field=u'Disabled',
                                      action=u'Changed',
                                      old=unicode(True),
                                      new=unicode(False))
        labcontroller.record_activity(user=identity.current.user,
                                      service=u'WEBUI',
                                      field=u'Removed',
                                      action=u'Changed',
                                      old=unicode(True),
                                      new=unicode(False))
        flash('Successfully re-added %s' % labcontroller.fqdn)
        redirect(url('.'))

    @expose('json')
    def has_active_recipes(self, id):
        labcontroller = LabController.by_id(id)
        count = Watchdog.by_status(labcontroller=labcontroller,
                                   status='active').count()
        if count:
            return {'has_active_recipes': True}
        else:
            return {'has_active_recipes': False}

    @identity.require(identity.in_group("admin"))
    @expose()
    def remove(self, id, *args, **kw):
        labcontroller = LabController.by_id(id)
        labcontroller.removed = datetime.utcnow()
        # de-associate systems
        systems = System.query.filter(System.lab_controller == labcontroller)
        System.record_bulk_activity(systems,
                                    user=identity.current.user,
                                    service=u'WEBUI',
                                    action=u'Changed',
                                    field=u'lab_controller',
                                    old=labcontroller.fqdn,
                                    new=None)
        systems.update({'lab_controller_id': None}, synchronize_session=False)
        # cancel running recipes
        watchdogs = Watchdog.by_status(labcontroller=labcontroller,
                                       status='active')
        for w in watchdogs:
            w.recipe.recipeset.job.cancel(
                msg='LabController %s has been deleted' % labcontroller.fqdn)
        # remove distro trees
        distro_tree_assocs = LabControllerDistroTree.query\
            .filter(LabControllerDistroTree.lab_controller == labcontroller)\
            .join(LabControllerDistroTree.distro_tree)
        DistroTree.record_bulk_activity(distro_tree_assocs,
                                        user=identity.current.user,
                                        service=u'WEBUI',
                                        action=u'Removed',
                                        field=u'lab_controller_assocs',
                                        old=labcontroller.fqdn,
                                        new=None)
        distro_tree_assocs.delete(synchronize_session=False)
        labcontroller.disabled = True
        labcontroller.record_activity(user=identity.current.user,
                                      service=u'WEBUI',
                                      field=u'Disabled',
                                      action=u'Changed',
                                      old=unicode(False),
                                      new=unicode(True))
        labcontroller.record_activity(user=identity.current.user,
                                      service=u'WEBUI',
                                      field=u'Removed',
                                      action=u'Changed',
                                      old=unicode(False),
                                      new=unicode(True))

        flash(_(u"%s removed") % labcontroller.fqdn)
        raise redirect(".")
예제 #10
0
파일: tasks.py 프로젝트: omps/beaker
            tasks = tasks.filter(RecipeTask.status == kw['status'])
        if kw.get('is_failed'):
            tasks = tasks.filter(RecipeTask.is_failed())
        elif kw.get('result'):
            tasks = tasks.filter(RecipeTask.result == kw['result'])
        if kw.get('osmajor_id'):
            tasks = tasks.join(RecipeTask.recipe, Recipe.distro_tree,
                    DistroTree.distro, Distro.osversion, OSVersion.osmajor)\
                    .filter(OSMajor.id == kw.get('osmajor_id'))
        if kw.get('whiteboard'):
            tasks = tasks.join('recipe').filter(Recipe.whiteboard==kw.get('whiteboard'))
        return dict(tasks = tasks,
                    hidden = hidden,
                    task_widget = self.task_widget)

    @identity.require(identity.in_group('admin'))
    @expose()
    def disable_from_ui(self, t_id, *args, **kw):
        to_return = dict( t_id = t_id )
        try:
            self._disable(t_id)
            to_return['success'] = True
        except Exception, e:
            log.exception('Unable to disable task:%s' % t_id)
            to_return['success'] = False
            to_return['err_msg'] = unicode(e)
            session.rollback()
        return to_return

    def _disable(self, t_id, *args, **kw):
        """
예제 #11
0
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(".")
예제 #12
0
class ExternalReportsController(RPCRoot):

    id = HiddenField(name='id')
    name = TextField(name='name',
                     label=_(u'Report Name'),
                     attrs={'maxlength': 100},
                     validator=validators.NotEmpty())
    url = TextField(name='url',
                    label=_(u'URL'),
                    attrs={'maxlength': 10000},
                    validator=validators.NotEmpty())
    description = TextArea(name='description',
                           label=_(u'Description'),
                           attrs={
                               'cols': '27',
                               'rows': '10'
                           })
    external_report_form = TableForm(
        fields=[id, name, url, description],
        submit_text=_(u'Save'),
    )

    delete_link = DeleteLinkWidgetForm()

    @expose('bkr.server.templates.external_reports')
    def index(self):
        all_reports = ExternalReport.query.all()
        if not all_reports:
            all_reports = []
        return dict(
            action=url('new'),
            delete_link=self.delete_link,
            title='External Reports',
            value=all_reports,
        )

    @identity.require(identity.in_group('admin'))
    @expose(template='bkr.server.templates.form-post')
    def new(self, **kw):
        return dict(
            form=self.external_report_form,
            action=url('save'),
            options=None,
            value=kw,
            title='New External Report',
        )

    @identity.require(identity.in_group("admin"))
    @expose('bkr.server.templates.form-post')
    def edit(self, id=None, **kw):
        return dict(
            form=self.external_report_form,
            action=url('save'),
            options={},
            title='Edit External Report',
            value=id and ExternalReport.by_id(id) or kw,
        )

    @identity.require(identity.in_group("admin"))
    @expose()
    def delete(self, id):
        report = ExternalReport.by_id(id)
        report_name = report.name
        session.delete(report)
        flash(_(u'Deleted report %s' % report_name))
        redirect('.')

    @identity.require(identity.in_group("admin"))
    @expose()
    @validate(external_report_form)
    @error_handler(edit)
    def save(self, **kw):
        if kw.get('id'):
            report = ExternalReport.by_id(kw['id'])
        else:
            report = ExternalReport()
        report.name = kw.get('name')
        report.url = kw.get('url')
        report.description = kw.get('description')
        session.add(report)
        flash(_(u"%s saved" % report.name))
        redirect(".")
예제 #13
0
class CSV(RPCRoot):
    # For XMLRPC methods in this class.
    exposed = False

    export_help_text = XML(
        u'<span>Refer to the <a href="http://beaker-project.org/docs/'
        'admin-guide/interface.html#export" target="_blank">'
        'documentation</a> to learn more about the exported data.</span>'
    ).expand()
    import_help_text = XML(
        u'<span>Refer to the <a href="http://beaker-project.org/docs/'
        'admin-guide/interface.html#import" target="_blank">'
        'documentation</a> for details about the supported CSV format.</span>'
    ).expand()

    upload     = widgets.FileField(name='csv_file', label='Import CSV', \
                                   help_text = import_help_text)
    download = RadioButtonList(name='csv_type',
                               label='CSV Type',
                               options=[
                                   ('system', 'Systems'),
                                   ('system_id', 'Systems (for modification)'),
                                   ('labinfo', 'System LabInfo'),
                                   ('power', 'System Power'),
                                   ('exclude', 'System Excluded Families'),
                                   ('install', 'System Install Options'),
                                   ('keyvalue', 'System Key/Values'),
                                   ('system_pool', 'System Pools'),
                                   ('user_group', 'User Groups')
                               ],
                               default='system',
                               help_text=export_help_text)

    importform = HorizontalForm(
        'import',
        fields=[upload],
        action='import data',
        submit_text=_(u'Import CSV'),
    )

    exportform = HorizontalForm(
        'export',
        fields=[download],
        action='export data',
        submit_text=_(u'Export CSV'),
    )

    @expose(template='bkr.server.templates.form')
    @identity.require(identity.not_anonymous())
    def index(self, **kw):
        return dict(
            form=self.exportform,
            title=_(u'CSV Export'),
            action='./action_export',
            options={},
            value=kw,
        )

    @expose(template='bkr.server.templates.form-post')
    @identity.require(identity.in_group('admin'))
    def csv_import(self, **kw):
        return dict(
            form=self.importform,
            title=_(u'CSV Import'),
            action='./action_import',
            options={},
            value=kw,
        )

    @expose()
    @identity.require(identity.not_anonymous())
    def action_export(self, csv_type, *args, **kw):
        file = NamedTemporaryFile()
        log = self.to_csv(file, csv_type)
        file.seek(0)

        return serve_file(file.name,
                          contentType="text/csv",
                          disposition="attachment",
                          name="%s.csv" % csv_type)

    def _import_row(self, data, log):
        if data['csv_type'] in system_types and ('fqdn' in data
                                                 or 'id' in data):
            if data.get('id', None):
                try:
                    system = System.query.filter(System.id == data['id']).one()
                except InvalidRequestError as e:
                    raise ValueError('Non-existent system id')
            else:
                try:
                    system = System.query.filter(
                        System.fqdn == data['fqdn']).one()
                except InvalidRequestError:
                    # Create new system with some defaults
                    # Assume the system is broken until proven otherwise.
                    # Also assumes its a machine.  we have to pick something
                    system = System(fqdn=data['fqdn'],
                                    owner=identity.current.user,
                                    type=SystemType.machine,
                                    status=SystemStatus.broken)
                    session.add(system)
                    # new systems are visible to everybody by default
                    system.custom_access_policy = SystemAccessPolicy()
                    system.custom_access_policy.add_rule(SystemPermission.view,
                                                         everybody=True)
            if not system.can_edit(identity.current.user):
                raise ValueError('You are not the owner of %s' % system.fqdn)
            # we change the FQDN only when a valid system id is supplied
            if not data.get('id', None):
                data.pop('fqdn')
            self.from_csv(system, data, log)
        elif data['csv_type'] == 'user_group' and 'user' in data:
            user = User.by_user_name(data['user'])
            if user is None:
                raise ValueError('%s is not a valid user' % data['user'])
            CSV_GroupUser.from_csv(user, data, log)
        else:
            raise ValueError('Invalid csv_type %s or missing required fields' %
                             data['csv_type'])

    @expose(template='bkr.server.templates.csv_import')
    @identity.require(identity.in_group('admin'))
    def action_import(self, csv_file, *args, **kw):
        """
        TurboGears method to import data from csv
        """

        log = []
        try:
            # ... process CSV file contents here ...
            missing = object()
            reader = UnicodeDictReader(csv_file.file,
                                       restkey=missing,
                                       restval=missing)
            is_empty = True

            for data in reader:

                is_empty = False

                if missing in data:
                    log.append('Too many fields on line %s (expecting %s)' %
                               (reader.line_num, len(reader.fieldnames)))
                    continue
                if any(value is missing for value in data.itervalues()):
                    missing_fields = [
                        field for field, value in data.iteritems()
                        if value is missing
                    ]
                    log.append('Missing fields on line %s: %s' %
                               (reader.line_num, ', '.join(missing_fields)))
                    continue
                if 'csv_type' not in data:
                    log.append('Missing csv_type on line %s' % reader.line_num)
                    continue
                try:
                    with session.begin_nested():
                        self._import_row(data, log)
                except Exception, e:
                    # log and continue processing more rows
                    log.append('Error importing line %s: %s' %
                               (reader.line_num, e))

            if is_empty:
                log.append('Empty CSV file supplied')

        except csv.Error, e:
            session.rollback()
            log.append('Error parsing CSV file: %s' % e)
예제 #14
0
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
예제 #15
0
class LabControllers(RPCRoot):
    # For XMLRPC methods in this class.
    exposed = True

    @cherrypy.expose
    @identity.require(identity.in_group("lab_controller"))
    def add_distro_tree(self, new_distro):
        lab_controller = identity.current.user.lab_controller

        variant = new_distro.get('variant')
        arch = Arch.lazy_create(arch=new_distro['arch'])

        osmajor = OSMajor.lazy_create(osmajor=new_distro['osmajor'])
        try:
            osmajor = OSMajor.by_alias(new_distro['osmajor'])
        except NoResultFound:
            pass
        else:
            raise BX(
                _('Cannot import distro as %s: it is configured as an alias for %s'
                  % (new_distro['osmajor'], osmajor.osmajor)))

        osversion = OSVersion.lazy_create(osmajor=osmajor,
                                          osminor=new_distro['osminor'])
        if 'arches' in new_distro:
            for arch_name in new_distro['arches']:
                osversion.add_arch(Arch.lazy_create(arch=arch_name))
        osversion.add_arch(arch)

        distro = Distro.lazy_create(name=new_distro['name'],
                                    osversion=osversion)
        # Automatically tag the distro if tags exists
        if 'tags' in new_distro:
            for tag in new_distro['tags']:
                distro.add_tag(tag)
        distro.date_created = datetime.utcfromtimestamp(
            float(new_distro['tree_build_time']))

        distro_tree = DistroTree.lazy_create(distro=distro,
                                             variant=variant,
                                             arch=arch)
        distro_tree.date_created = datetime.utcfromtimestamp(
            float(new_distro['tree_build_time']))

        if 'repos' in new_distro:
            for repo in new_distro['repos']:
                dtr = DistroTreeRepo.lazy_create(distro_tree=distro_tree,
                                                 repo_id=repo['repoid'],
                                                 repo_type=repo['type'],
                                                 path=repo['path'])

        if 'kernel_options' in new_distro:
            distro_tree.kernel_options = new_distro['kernel_options']

        if 'kernel_options_post' in new_distro:
            distro_tree.kernel_options_post = new_distro['kernel_options_post']

        if 'ks_meta' in new_distro:
            distro_tree.ks_meta = new_distro['ks_meta']

        if 'images' in new_distro:
            for image in new_distro['images']:
                try:
                    image_type = ImageType.from_string(image['type'])
                except ValueError:
                    continue  # ignore
                if 'kernel_type' not in image:
                    image['kernel_type'] = 'default'
                try:
                    kernel_type = KernelType.by_name(image['kernel_type'])
                except ValueError:
                    continue  # ignore
                dti = DistroTreeImage.lazy_create(distro_tree=distro_tree,
                                                  image_type=image_type,
                                                  kernel_type=kernel_type,
                                                  path=image['path'])

        DistroTrees.add_distro_urls(distro_tree, lab_controller,
                                    new_distro['urls'])

        return distro_tree.id

    @cherrypy.expose
    @identity.require(identity.in_group("lab_controller"))
    def remove_distro_trees(self, distro_tree_ids):
        lab_controller = identity.current.user.lab_controller
        for distro_tree_id in distro_tree_ids:
            distro_tree = DistroTree.by_id(distro_tree_id)
            distro_tree.expire(lab_controller=lab_controller)
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def get_running_command_ids(self):
        lab_controller = identity.current.user.lab_controller
        running_commands = Command.query \
            .join(Command.system) \
            .filter(System.lab_controller == lab_controller) \
            .filter(Command.status == CommandStatus.running) \
            .values(Command.id)
        return [id for id, in running_commands]

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def get_queued_command_details(self):
        lab_controller = identity.current.user.lab_controller
        max_running_commands = config.get('beaker.max_running_commands')
        if max_running_commands:
            running_commands = Command.query\
                    .join(Command.system)\
                    .filter(System.lab_controller == lab_controller)\
                    .filter(Command.status == CommandStatus.running)\
                    .count()
            if running_commands >= max_running_commands:
                return []
        query = Command.query\
                .join(Command.system)\
                .options(contains_eager(Command.system))\
                .filter(System.lab_controller == lab_controller)\
                .filter(Command.status == CommandStatus.queued)\
                .order_by(Command.id)
        if max_running_commands:
            query = query.limit(max_running_commands - running_commands)
        result = []
        for cmd in query:
            d = {
                'id': cmd.id,
                'action': cmd.action,
                'fqdn': cmd.system.fqdn,
                'delay': 0,
                'quiescent_period': cmd.quiescent_period
            }
            if cmd.delay_until:
                d['delay'] = max(
                    0, total_seconds(cmd.delay_until - datetime.utcnow()))
            # Fill in details specific to the type of command
            if cmd.action in (u'on', u'off', u'reboot', u'interrupt'):
                if not cmd.system.power:
                    cmd.abort(u'Power control unavailable for %s' % cmd.system)
                    continue
                d['power'] = {
                    'type': cmd.system.power.power_type.name,
                    'address': cmd.system.power.power_address,
                    'id': cmd.system.power.power_id,
                    'user': cmd.system.power.power_user,
                    'passwd': cmd.system.power.power_passwd,
                }
            elif cmd.action == u'configure_netboot':
                installation = cmd.installation
                distro_tree = cmd.installation.distro_tree
                if distro_tree:
                    schemes = ['http', 'ftp']
                    if distro_tree.arch.arch == 's390' or distro_tree.arch.arch == 's390x':
                        # zPXE needs FTP URLs for the images, it has no HTTP client.
                        # It would be nicer if we could leave this decision up to
                        # beaker-provision, but the API doesn't work like that...
                        schemes = ['ftp']
                    distro_tree_url = distro_tree.url_in_lab(lab_controller,
                                                             scheme=schemes)
                else:
                    distro_tree_url = installation.tree_url
                if not distro_tree_url:
                    cmd.abort(
                        u'No usable URL found for distro tree %s in lab %s' %
                        (distro_tree.id, lab_controller.fqdn))
                    continue

                d['netboot'] = {
                    'kernel_url':
                    urlparse.urljoin(distro_tree_url,
                                     installation.kernel_path),
                    'initrd_url':
                    urlparse.urljoin(distro_tree_url,
                                     installation.initrd_path),
                    'kernel_options':
                    installation.kernel_options or '',
                }
                if distro_tree:
                    d['netboot']['distro_tree_id'] = distro_tree.id
                else:
                    d['netboot']['distro_tree_id'] = None
                if installation.arch:
                    d['netboot']['arch'] = installation.arch.arch
                else:
                    # It must be a queued command left over after migrating from Beaker < 25.
                    d['netboot']['arch'] = distro_tree.arch.arch
            result.append(d)
        return result

    @cherrypy.expose
    def get_installation_for_system(self, fqdn):
        system = System.by_fqdn(fqdn, identity.current.user)
        if not system.installations:
            raise ValueError('System %s has never been provisioned' % fqdn)
        installation = system.installations[0]
        distro_tree = installation.distro_tree
        distro_tree_url = distro_tree.url_in_lab(system.lab_controller, 'http')
        if not distro_tree_url:
            raise ValueError(
                'No usable URL found for distro tree %s in lab %s' %
                (distro_tree.id, system.lab_controller.fqdn))

        if system.kernel_type.uboot:
            by_kernel = ImageType.uimage
            by_initrd = ImageType.uinitrd
        else:
            by_kernel = ImageType.kernel
            by_initrd = ImageType.initrd

        kernel = distro_tree.image_by_type(by_kernel, system.kernel_type)
        if not kernel:
            raise ValueError('Kernel image not found for distro tree %s' %
                             distro_tree.id)
        initrd = distro_tree.image_by_type(by_initrd, system.kernel_type)
        if not initrd:
            raise ValueError('Initrd image not found for distro tree %s' %
                             distro_tree.id)
        return {
            'kernel_url':
            urlparse.urljoin(distro_tree_url, kernel.path),
            'initrd_url':
            urlparse.urljoin(distro_tree_url, initrd.path),
            'kernel_options':
            installation.kernel_options or '',
            'distro_tree_urls': [
                lca.url for lca in distro_tree.lab_controller_assocs
                if lca.lab_controller == system.lab_controller
            ],
        }

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_running(self, command_id):
        lab_controller = identity.current.user.lab_controller
        cmd = Command.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.queued:
            raise ValueError('Command %s already run' % command_id)
        cmd.change_status(CommandStatus.running)
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_completed(self, command_id):
        lab_controller = identity.current.user.lab_controller
        cmd = Command.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.running:
            raise ValueError('Command %s not running' % command_id)
        cmd.change_status(CommandStatus.completed)
        if cmd.action == u'on' and cmd.installation:
            cmd.installation.rebooted = datetime.utcnow()
            recipe = cmd.installation.recipe
            if recipe:
                recipe.initial_watchdog()
        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def add_completed_command(self, fqdn, action):
        # Reports completion of a command that was executed
        # synchronously by the lab controller
        user = identity.current.user
        system = System.by_fqdn(fqdn, user)
        cmd = Command(user=user,
                      service=u"XMLRPC",
                      action=action,
                      status=CommandStatus.completed)
        cmd.start_time = cmd.finish_time = datetime.utcnow()
        system.command_queue.append(cmd)
        session.flush()  # Populates cmd.system (needed for next call)
        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_aborted(self, command_id, message=None):
        lab_controller = identity.current.user.lab_controller
        cmd = Command.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.running:
            raise ValueError('Command %s not running' % command_id)
        cmd.change_status(CommandStatus.aborted)
        cmd.error_message = message
        if cmd.installation and cmd.installation.recipe:
            cmd.installation.recipe.abort('Command %s aborted' % cmd.id)
        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def mark_command_failed(self,
                            command_id,
                            message=None,
                            system_broken=True):
        lab_controller = identity.current.user.lab_controller
        cmd = Command.query.get(command_id)
        if cmd.system.lab_controller != lab_controller:
            raise ValueError('%s cannot update command for %s in wrong lab' %
                             (lab_controller, cmd.system))
        if cmd.status != CommandStatus.running:
            raise ValueError('Command %s not running' % command_id)
        cmd.change_status(CommandStatus.failed)
        cmd.error_message = message
        # Ignore failures for 'interrupt' commands because most power types
        # don't support it and will report a "failure" in that case.
        if system_broken and cmd.action != 'interrupt' and cmd.system.status == SystemStatus.automated:
            cmd.system.mark_broken(reason=u'Power command failed: %s' %
                                   message)
        if cmd.installation:
            if cmd.installation.recipe:
                cmd.installation.recipe.abort('Command %s failed' % cmd.id)
            queued_commands = [
                c for c in cmd.installation.commands
                if c.status == CommandStatus.queued
            ]
            for q in queued_commands:
                q.abort('Command %s failed' % cmd.id)

        cmd.log_to_system_history()
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def clear_running_commands(self, message=None):
        """
        Called by beaker-provision on startup. Any commands which are Running
        at this point must be left over from an earlier crash.
        """
        # If the connection between the LCs and the main server is unreliable
        # commands may end up stuck in "running" state. We mitigate the
        # effects of this by purging all stale commands (those more than a
        # day old) whenever a lab controller restarts and tries to clear the
        # possibly interrupted commands for that lab.
        # See https://bugzilla.redhat.com/show_bug.cgi?id=974319 and
        # https://bugzilla.redhat.com/show_bug.cgi?id=974352 for more
        # details.
        lab_controller = identity.current.user.lab_controller
        purged = (Command.__table__.update().where(
            Command.status == CommandStatus.running).where(
                Command.queue_time < datetime.utcnow() -
                timedelta(days=1)).values(
                    status=CommandStatus.aborted).execute())
        if purged.rowcount:
            msg = ("Aborted %d stale commands before aborting "
                   "recent running commands for %s")
            log.warn(msg, purged.rowcount, lab_controller.fqdn)
        running_commands = Command.query\
                .join(Command.system)\
                .filter(System.lab_controller == lab_controller)\
                .filter(Command.status == CommandStatus.running)
        for cmd in running_commands:
            cmd.abort(message)
        return True

    @cherrypy.expose
    @identity.require(identity.in_group('lab_controller'))
    def get_distro_trees(self, filter=None):
        """
        Called by beaker-proxy. returns all active distro_trees
        for the lab controller that made the call.
        We have the lab controller do this because it may have access to
        distros that the scheduler can't reach.
        """
        lab_controller = identity.current.user.lab_controller
        if filter is None:
            filter = {}
        if 'labcontroller' in filter and filter[
                'labcontroller'] != lab_controller.fqdn:
            raise ValueError(
                'Cannot filter on lab controller other than the currnet one')
        filter['labcontroller'] = lab_controller.fqdn
        distro_trees = DistroTrees().filter(filter)
        for dt in distro_trees:
            dt['available'] = [(lc, url) for lc, url in dt['available']
                               if lc == lab_controller.fqdn]
        return distro_trees
예제 #16
0
class DistroTrees(RPCRoot):
    # For XMLRPC methods in this class.
    exposed = True
    delete_link = DeleteLinkWidgetForm()

    @expose(template='bkr.server.templates.grid')
    @paginate('list', default_order='-date_created', limit=50)
    def index(self, **kwargs):
        query = DistroTree.query.join(DistroTree.distro, Distro.osversion, OSVersion.osmajor)\
                .filter(DistroTree.lab_controller_assocs.any())
        options = {}
        if 'simplesearch' in kwargs:
            kwargs['search'] = [{
                'table': 'Name',
                'operation': 'contains',
                'value': kwargs['simplesearch']
            }]
            options['simplesearch'] = kwargs['simplesearch']
        if 'search' in kwargs:
            search = search_utility.DistroTree.search(query)
            for row in kwargs['search']:
                search.append_results(row['value'], row['table'],
                                      row['operation'])
            query = search.return_results()

        grid = myPaginateDataGrid(fields=[
            myPaginateDataGrid.Column(
                name='id',
                title=u'ID',
                getter=lambda x: make_link(url=str(x.id), text=str(x.id)),
                options=dict(sortable=True)),
            myPaginateDataGrid.Column(name='distro.name',
                                      title=u'Distro',
                                      getter=lambda x: x.distro.link,
                                      options=dict(sortable=True)),
            myPaginateDataGrid.Column(
                name='variant', title=u'Variant', options=dict(sortable=True)),
            myPaginateDataGrid.Column(
                name='arch.arch', title=u'Arch', options=dict(sortable=True)),
            myPaginateDataGrid.Column(name='distro.osversion.osmajor.osmajor',
                                      title=u'OS Major Version',
                                      options=dict(sortable=True)),
            myPaginateDataGrid.Column(name='distro.osversion.osminor',
                                      title=u'OS Minor Version',
                                      options=dict(sortable=True)),
            myPaginateDataGrid.Column(name='date_created',
                                      title=u'Date Created',
                                      options=dict(sortable=True,
                                                   datetime=True)),
            Utility.direct_column(title=u'Provision',
                                  getter=self._provision_system_link),
        ])

        search_bar = SearchBar(
            name='search',
            label=_(u'Distro Tree Search'),
            table=search_utility.DistroTree.search.
            create_complete_search_table(),
            search_controller=None,
            date_picker=['created'],
        )

        return dict(title=u'Distro Trees',
                    action='.',
                    grid=grid,
                    search_bar=search_bar,
                    searchvalue=kwargs.get('search'),
                    options=options,
                    list=query)

    def _provision_system_link(self, distro_tree):
        return make_link('/reserveworkflow/?distro_tree_id=%s' %
                         distro_tree.id,
                         'Provision',
                         elem_class='btn')

    @expose(template='bkr.server.templates.distrotree')
    def default(self, id, *args, **kwargs):
        try:
            distro_tree = DistroTree.by_id(int(id))
        except (ValueError, NoResultFound):
            raise cherrypy.NotFound(id)
        form_task = TaskSearchForm(action='/tasks/do_search',
                                   hidden=dict(arch_id=True,
                                               distro=True,
                                               osmajor_id=True),
                                   options=dict())
        lab_controllers = LabController.query.filter_by(removed=None)\
                .order_by(LabController.fqdn).all()
        lab_controller_assocs = dict(
            (lab_controller,
             sorted((lca for lca in distro_tree.lab_controller_assocs
                     if lca.lab_controller == lab_controller),
                    key=lambda lca: lca.url))
            for lab_controller in lab_controllers)
        is_admin = identity.current.user and identity.current.user.is_admin(
        ) or False
        return dict(title='Distro Tree',
                    value=distro_tree,
                    install_options_widget=DistroTreeInstallOptionsWidget(),
                    form_task=form_task,
                    delete_link=self.delete_link,
                    lab_controllers=lab_controllers,
                    lab_controller_assocs=lab_controller_assocs,
                    readonly=not is_admin)

    @expose(content_type='text/plain')
    def yum_config(self, distro_tree_id, *args, **kwargs):
        # Ignore positional args, so that we can make nice URLs like
        # /distrotrees/yum_config/12345/RHEL-6.2-Server-i386.repo
        try:
            distro_tree = DistroTree.by_id(int(distro_tree_id))
        except (ValueError, NoResultFound):
            raise cherrypy.NotFound(distro_tree_id)
        if not kwargs.get('lab'):
            lc = distro_tree.lab_controller_assocs[0].lab_controller
        else:
            try:
                lc = LabController.by_name(kwargs['lab'])
            except NoResultFound:
                raise cherrypy.HTTPError(status=400,
                                         message='No such lab controller %r' %
                                         kwargs['lab'])
        base = distro_tree.url_in_lab(lc, scheme='http')
        if not base:
            raise cherrypy.HTTPError(status=404,
                                     message='%s is not present in lab %s' %
                                     (distro_tree, lc))
        if not distro_tree.repos:
            return '# No repos for %s' % distro_tree
        sections = []
        for repo in distro_tree.repos:
            sections.append('''[%s]
name=%s
baseurl=%s
enabled=1
gpgcheck=0
''' % (repo.repo_id, repo.repo_id, urlparse.urljoin(base, repo.path)))
        return '\n'.join(sections)

    @expose()
    @identity.require(identity.in_group('admin'))
    def lab_controller_add(self, distro_tree_id, lab_controller_id, url):
        try:
            distro_tree = DistroTree.by_id(distro_tree_id)
        except NoResultFound:
            flash(_(u'Invalid distro tree id %s') % distro_tree_id)
            redirect('.')
        try:
            lab_controller = LabController.by_id(lab_controller_id)
        except ValueError:
            flash(_(u'Invalid lab controller id %s') % lab_controller_id)
            redirect(str(distro_tree.id))

        # make sure the url ends with /
        url = os.path.join(url.strip(), '')
        try:
            self.add_distro_urls(distro_tree, lab_controller, [url])
        except ValueError as e:
            flash(_(u'%s') % e)
            redirect(str(distro_tree.id))

        flash(_(u'Changed/Added %s %s') % (lab_controller, url))
        redirect(str(distro_tree.id))

    @expose()
    @identity.require(identity.in_group('admin'))
    @restrict_http_method('post')
    def lab_controller_remove(self, id):
        try:
            lca = LabControllerDistroTree.by_id(id)
        except NoResultFound:
            flash(_(u'Invalid lab_controller_assoc id %s') % id)
            redirect('.')

        session.delete(lca)
        lca.distro_tree.activity.append(
            DistroTreeActivity(user=identity.current.user,
                               service=u'WEBUI',
                               action=u'Removed',
                               field_name=u'lab_controller_assocs',
                               old_value=u'%s %s' %
                               (lca.lab_controller, lca.url),
                               new_value=None))
        flash(_(u'Deleted %s %s') % (lca.lab_controller, lca.url))
        redirect(str(lca.distro_tree.id))

    @expose()
    @identity.require(identity.in_group('admin'))
    def install_options(self, distro_tree_id, **kwargs):
        try:
            distro_tree = DistroTree.by_id(distro_tree_id)
        except NoResultFound:
            flash(_(u'Invalid distro tree id %s') % distro_tree_id)
            redirect('.')
        if 'ks_meta' in kwargs:
            distro_tree.activity.append(
                DistroTreeActivity(user=identity.current.user,
                                   service=u'WEBUI',
                                   action=u'Changed',
                                   field_name=u'InstallOption:ks_meta',
                                   old_value=distro_tree.ks_meta,
                                   new_value=kwargs['ks_meta']))
            distro_tree.ks_meta = kwargs['ks_meta']
        if 'kernel_options' in kwargs:
            distro_tree.activity.append(
                DistroTreeActivity(user=identity.current.user,
                                   service=u'WEBUI',
                                   action=u'Changed',
                                   field_name=u'InstallOption:kernel_options',
                                   old_value=distro_tree.kernel_options,
                                   new_value=kwargs['kernel_options']))
            distro_tree.kernel_options = kwargs['kernel_options']
        if 'kernel_options_post' in kwargs:
            distro_tree.activity.append(
                DistroTreeActivity(
                    user=identity.current.user,
                    service=u'WEBUI',
                    action=u'Changed',
                    field_name=u'InstallOption:kernel_options_post',
                    old_value=distro_tree.kernel_options_post,
                    new_value=kwargs['kernel_options_post']))
            distro_tree.kernel_options_post = kwargs['kernel_options_post']
        flash(_(u'Updated install options'))
        redirect(str(distro_tree.id))

    @staticmethod
    def add_distro_urls(distro_tree, lab_controller, urls):
        """
        Adds supplied URLs to specific distro tree under specific lab controller.
        Old URL will be replaced if new URL uses the same scheme
        """
        new_urls_by_scheme = dict(
            (urlparse.urlparse(url, scheme=None).scheme, url) for url in urls)
        if None in new_urls_by_scheme:
            raise ValueError('URL %r is not absolute' %
                             new_urls_by_scheme[None])
        for lca in distro_tree.lab_controller_assocs:
            if lca.lab_controller == lab_controller:
                scheme = urlparse.urlparse(lca.url).scheme
                new_url = new_urls_by_scheme.pop(scheme, None)
                # replace old url if it's the same scheme
                if new_url is not None and lca.url != new_url:
                    distro_tree.activity.append(
                        DistroTreeActivity(user=identity.current.user,
                                           service=u'XMLRPC',
                                           action=u'Changed',
                                           field_name=u'lab_controller_assocs',
                                           old_value=u'%s %s' %
                                           (lca.lab_controller, lca.url),
                                           new_value=u'%s %s' %
                                           (lca.lab_controller, new_url)))
                    lca.url = new_url
        for url in new_urls_by_scheme.values():
            distro_tree.lab_controller_assocs.append(
                LabControllerDistroTree(lab_controller=lab_controller,
                                        url=url))
            distro_tree.activity.append(
                DistroTreeActivity(user=identity.current.user,
                                   service=u'XMLRPC',
                                   action=u'Added',
                                   field_name=u'lab_controller_assocs',
                                   old_value=None,
                                   new_value=u'%s %s' % (lab_controller, url)))

    # XMLRPC method for listing distro trees
    @cherrypy.expose
    def filter(self, filter):
        """
        Returns a list of details for distro trees filtered by the given criteria.

        The *filter* argument must be an XML-RPC structure (dict) specifying
        filter criteria. The following keys are recognised:

            'name'
                Distro name. May include % SQL wildcards, for example
                ``'%20101121.nightly'``.
            'family'
                Distro family name, for example ``'RedHatEnterpriseLinuxServer5'``.
                Matches are exact.
            'tags'
                List of distro tags, for example ``['STABLE', 'RELEASED']``. All given
                tags must be present on the distro for it to match.
            'arch'
                Architecture name, for example ``'x86_64'``.
            'treepath'
                Tree path (on any lab controller). May include % SQL wildcards, for
                example ``'nfs://nfs.example.com:%'``.
            'labcontroller'
                FQDN of lab controller. Limit to distro trees which are
                available on this lab controller. May include % SQL wildcards.
            'distro_id'
                Distro id.
                Matches are exact.
            'distro_tree_id'
                Distro Tree id.
                Matches are exact.
            'xml'
                XML filter criteria in the same format allowed inside
                ``<distroRequires/>`` in a job, for example
                ``<or><distro_tag value="RELEASED"/><distro_tag value="STABLE"/></or>``.
            'limit'
                Integer limit to number of distro trees returned.

        The return value is an array with one element per distro (up to the
        maximum number of distros given by 'limit'). Each element is an XML-RPC
        structure (dict) describing a distro tree.

        .. versionadded:: 0.9
        """
        query = DistroTree.query\
                .join(DistroTree.distro, Distro.osversion, OSVersion.osmajor)\
                .join(DistroTree.arch)\
                .options(contains_eager(DistroTree.distro),
                    contains_eager(DistroTree.arch))
        name = filter.get('name', None)
        family = filter.get('family', None)
        tags = filter.get('tags', None) or []
        arch = filter.get('arch', None)
        distro_id = filter.get('distro_id', None)
        distro_tree_id = filter.get('distro_tree_id', None)
        treepath = filter.get('treepath', None)
        labcontroller = filter.get('labcontroller', None)
        xml = filter.get('xml', None)
        limit = filter.get('limit', None)
        for tag in tags:
            query = query.filter(Distro._tags.any(DistroTag.tag == tag))
        if name:
            query = query.filter(Distro.name.like('%s' % name))
        if family:
            query = query.filter(OSMajor.osmajor == '%s' % family)
        if arch:
            if isinstance(arch, list):
                query = query.filter(Arch.arch.in_(arch))
            else:
                query = query.filter(Arch.arch == '%s' % arch)
        if distro_id:
            query = query.filter(Distro.id == int(distro_id))
        if distro_tree_id:
            query = query.filter(DistroTree.id == int(distro_tree_id))
        if treepath:
            query = query.filter(
                DistroTree.lab_controller_assocs.any(
                    LabControllerDistroTree.url.like('%s' % treepath)))
        elif labcontroller:
            query = query.filter(
                exists([1],
                       from_obj=[
                           LabControllerDistroTree.__table__.join(
                               LabController.__table__)
                       ]).where(LabControllerDistroTree.distro_tree_id ==
                                DistroTree.id).where(
                                    LabController.fqdn.like(labcontroller)))
        else:
            # we only want distro trees that are active in at least one lab controller
            query = query.filter(DistroTree.lab_controller_assocs.any())
        if xml:
            query = needpropertyxml.apply_distro_filter(
                '<and>%s</and>' % xml, query)
        query = query.order_by(DistroTree.date_created.desc())
        if limit:
            query = query[:limit]
        return [{
            'distro_tree_id':
            dt.id,
            'distro_id':
            dt.distro.id,
            'distro_name':
            dt.distro.name,
            'distro_osversion':
            unicode(dt.distro.osversion),
            'distro_osmajor':
            unicode(dt.distro.osversion.osmajor),
            'distro_tags': [unicode(tag) for tag in dt.distro.tags],
            'arch':
            unicode(dt.arch),
            'variant':
            dt.variant,
            'images': [(unicode(image.image_type), image.path)
                       for image in dt.images],
            'kernel_options':
            dt.kernel_options or u'',
            'kernel_options_post':
            dt.kernel_options_post or u'',
            'ks_meta':
            dt.ks_meta or u'',
            'available': [(lca.lab_controller.fqdn, lca.url)
                          for lca in dt.lab_controller_assocs],
        } for dt in query]
예제 #17
0
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(".")