def update_reservation_request(id): """ Updates the reservation request of a recipe. The request must be :mimetype:`application/json`. :param id: Recipe's id. :jsonparam boolean reserve: Whether the system will be reserved at the end of the recipe. If true, the system will be reserved. If false, the system will not be reserved. :jsonparam int duration: Number of seconds to reserve the system. :jsonparam string when: Circumstances under which the system will be reserved. Valid values are: onabort If the recipe status is Aborted. onfail If the recipe status is Aborted, or the result is Fail. onwarn If the recipe status is Aborted, or the result is Fail or Warn. always Unconditionally. """ recipe = _get_recipe_by_id(id) if not recipe.can_update_reservation_request(identity.current.user): raise Forbidden403('Cannot update the reservation request of recipe %s' % recipe.id) data = read_json_request(request) if 'reserve' not in data: raise BadRequest400('No reserve specified') with convert_internal_errors(): if data['reserve']: if not recipe.reservation_request: recipe.reservation_request = RecipeReservationRequest() if 'duration' in data: duration = int(data['duration']) if duration > MAX_SECONDS_PROVISION: raise BadRequest400('Reservation time exceeds maximum time of %s hours' % MAX_HOURS_PROVISION) old_duration = recipe.reservation_request.duration recipe.reservation_request.duration = duration _record_activity(recipe, u'Reservation Request', old_duration, duration) if 'when' in data: old_condition = recipe.reservation_request.when new_condition = RecipeReservationCondition.from_string(data['when']) recipe.reservation_request.when = new_condition _record_activity(recipe, u'Reservation Condition', old_condition, new_condition) session.flush() # to ensure the id is populated return jsonify(recipe.reservation_request.__json__()) else: if recipe.reservation_request: session.delete(recipe.reservation_request) _record_activity(recipe, u'Reservation Request', recipe.reservation_request.duration, None) return jsonify(RecipeReservationRequest.empty_json())
def update_group(group_name): """ Updates attributes of an existing group. The request body must be a JSON object containing one or more of the following keys. :jsonparam string group_name: New name for the group. :jsonparam string display_name: Display name of the group. :jsonparam string description: Description of the group. :jsonparam string root_password: Optional password. Can be an empty string. If empty, group jobs will use the root password preferences of the job submitter. :jsonparam string membership_type: New membership type for the group. See `POST /groups/` for more information. :status 200: Group was updated. :status 400: Invalid data was given. """ group = _get_group_by_name(group_name) if not group.can_edit(identity.current.user): raise Forbidden403('Cannot edit group') data = read_json_request(request) with convert_internal_errors(): user = identity.current.user renamed = False if 'group_name' in data: new_name = data['group_name'] if new_name != group.group_name: if Group.query.filter(Group.group_name == new_name).count(): raise Conflict409('Group %s already exists' % new_name) group.set_name(user, u'HTTP', new_name) renamed = True if 'display_name' in data: new_display_name = data['display_name'] if new_display_name != group.display_name: group.set_display_name(user, u'HTTP', new_display_name) if 'description' in data: new_description = data['description'] if new_description != group.description: group.set_description(user, u'HTTP', new_description) if 'root_password' in data: new_root_password = data['root_password'] if new_root_password != group.root_password: group.set_root_password(user, u'HTTP', new_root_password) # for backwards compatibility if data.pop('ldap', False): data['membership_type'] = 'ldap' if 'membership_type' in data: new_type = GroupMembershipType.from_string( data['membership_type']) if (new_type == GroupMembershipType.ldap and not group.can_edit_ldap(user)): raise BadRequest400('Cannot edit LDAP group %s' % group) if new_type != group.membership_type: group.membership_type = new_type response = jsonify(group.to_json()) if renamed: response.headers.add('Location', absolute_url(group.href)) return response
def update_pool(pool_name): """ Updates attributes of an existing system pool. The request body must be a JSON object containing one or more of the following keys. :param pool_name: System pool's name. :jsonparam string name: New name for the system pool. :jsonparam string description: Description of the system pool. :jsonparam object owner: JSON object containing a ``user_name`` key or ``group_name`` key identifying the new owner for the system pool. :status 200: System pool was updated. :status 400: Invalid data was given. """ pool = _get_pool_by_name(pool_name) if not pool.can_edit(identity.current.user): raise Forbidden403('Cannot edit system pool') data = read_json_request(request) # helper for recording activity below def record_activity(field, old, new, action=u'Changed'): pool.record_activity(user=identity.current.user, service=u'HTTP', action=action, field=field, old=old, new=new) with convert_internal_errors(): renamed = False if 'name' in data: new_name = data['name'] if new_name != pool.name: if SystemPool.query.filter( SystemPool.name == new_name).count(): raise Conflict409('System pool %s already exists' % new_name) record_activity(u'Name', pool.name, new_name) pool.name = new_name renamed = True if 'description' in data: new_description = data['description'] if new_description != pool.description: record_activity(u'Description', pool.description, new_description) pool.description = new_description if 'owner' in data: new_owner, owner_type = _get_owner(data['owner']) if owner_type == 'user': pool.change_owner(user=new_owner) else: pool.change_owner(group=new_owner) response = jsonify(pool.__json__()) if renamed: response.headers.add('Location', absolute_url(pool.href)) return response
def _update_recipeset(recipeset, data=None): if not data: data = {} def record_activity(field, old=None, new=None, action=u'Changed'): recipeset.record_activity(user=identity.current.user, service=u'HTTP', action=action, field=field, old=old, new=new) with convert_internal_errors(): if 'priority' in data: priority = TaskPriority.from_string(data['priority']) if priority != recipeset.priority: if not recipeset.can_change_priority(identity.current.user): raise Forbidden403('Cannot change recipe set %s priority' % recipeset.id) allowed = recipeset.allowed_priorities(identity.current.user) if priority not in allowed: raise Forbidden403( 'Cannot set recipe set %s priority to %s, ' 'permitted priorities are: %s' % (recipeset.id, priority, ' '.join( unicode(pri) for pri in allowed))) record_activity(u'Priority', old=recipeset.priority.value, new=priority.value) recipeset.priority = priority if 'waived' in data: if not isinstance(data['waived'], bool): raise ValueError('waived key must be true or false') waived = data['waived'] if waived != recipeset.waived: if not recipeset.can_waive(identity.current.user): raise Forbidden403('Cannot waive recipe set %s' % recipeset.id) record_activity(u'Waived', old=unicode(recipeset.waived), new=unicode(waived)) recipeset.waived = waived
def add_system_to_pool(pool_name): """ Add a system to a system pool :param pool_name: System pool's name. :jsonparam fqdn: System's fully-qualified domain name. """ u = identity.current.user data = read_json_request(request) pool = _get_pool_by_name(pool_name, lockmode='update') if 'fqdn' not in data: raise BadRequest400('System FQDN not specified') try: system = System.by_fqdn(data['fqdn'], u) except NoResultFound: raise BadRequest400("System '%s' does not exist" % data['fqdn']) if not pool in system.pools: if pool.can_edit(u) and system.can_edit(u): system.record_activity(user=u, service=u'HTTP', action=u'Added', field=u'Pool', old=None, new=unicode(pool)) system.pools.append(pool) system.date_modified = datetime.datetime.utcnow() pool.record_activity(user=u, service=u'HTTP', action=u'Added', field=u'System', old=None, new=unicode(system)) else: if not pool.can_edit(u): raise Forbidden403('You do not have permission to ' 'add systems to pool %s' % pool.name) if not system.can_edit(u): raise Forbidden403('You do not have permission to ' 'modify system %s' % system.fqdn) return '', 204
def remove_cc(fqdn, email): system = _get_system_by_FQDN(fqdn) if not system.can_edit(identity.current.user): raise Forbidden403('Cannot change notify cc') if email in system.cc: system.cc.remove(email) system.record_activity(user=identity.current.user, service=u'HTTP', action=u'Removed', field=u'Cc', old=email, new=None) system.date_modified = datetime.datetime.utcnow() return jsonify({'notify_cc': list(system.cc)})
def save_access_policy(pool_name): """ Updates the access policy for a system pool. :param pool_name: System pool's name. :jsonparam array rules: List of rules to include in the new policy. This replaces all existing rules in the policy. Each rule is a JSON object with ``user``, ``group``, and ``everybody`` keys. """ pool = _get_pool_by_name(pool_name) if not pool.can_edit_policy(identity.current.user): raise Forbidden403('Cannot edit system pool policy') data = read_json_request(request) _edit_access_policy_rules(pool, pool.access_policy, data['rules']) return jsonify(pool.access_policy.__json__())
def delete_ssh_public_key(username, id): """ Deletes a public SSH public key belonging to the given user account. :param username: The user's username. :param id: Database id of the SSH public key to be deleted. """ user = _get_user(username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) matching_keys = [k for k in user.sshpubkeys if k.id == id] if not matching_keys: raise NotFound404('SSH public key id %s does not belong to user %s' % (id, user)) key = matching_keys[0] session.delete(key) return '', 204
def save_system_access_policy(fqdn): system = _get_system_by_FQDN(fqdn) if not system.can_edit_policy(identity.current.user): raise Forbidden403('Cannot edit system policy') if system.custom_access_policy: policy = system.custom_access_policy else: policy = system.custom_access_policy = SystemAccessPolicy() data = read_json_request(request) # Figure out what is added, what is removed. # Rules are immutable, so if it has an id it is unchanged, # if it has no id it is new. kept_rule_ids = frozenset(r['id'] for r in data['rules'] if 'id' in r) removed = [] for old_rule in policy.rules: if old_rule.id not in kept_rule_ids: removed.append(old_rule) for old_rule in removed: system.record_activity(user=identity.current.user, service=u'HTTP', field=u'Access Policy Rule', action=u'Removed', old=repr(old_rule)) policy.rules.remove(old_rule) for rule in data['rules']: if 'id' not in rule: if rule['user']: user = User.by_user_name(rule['user']) if user is None: raise BadRequest400('No such user %r' % rule['user']) else: user = None try: group = Group.by_name(rule['group']) if rule['group'] else None except NoResultFound: raise BadRequest400('No such group %r' % rule['group']) permission = SystemPermission.from_string(rule['permission']) new_rule = policy.add_rule(user=user, group=group, everybody=rule['everybody'], permission=permission) system.record_activity(user=identity.current.user, service=u'HTTP', field=u'Access Policy Rule', action=u'Added', new=repr(new_rule)) return jsonify(policy.__json__())
def remove_system_from_pool(pool_name): """ Remove a system from a system pool :param pool_name: System pool's name. :queryparam fqdn: System's fully-qualified domain name """ if 'fqdn' not in request.args: raise MethodNotAllowed405 fqdn = request.args['fqdn'] system = _get_system_by_FQDN(fqdn) u = identity.current.user pool = _get_pool_by_name(pool_name, lockmode='update') if pool in system.pools: if pool.can_edit(u) or system.can_edit(u): if system.active_access_policy == pool.access_policy: system.active_access_policy = system.custom_access_policy system.record_activity(user=u, service=u'HTTP', field=u'Active Access Policy', action=u'Changed', old='Pool policy: %s' % pool_name, new='Custom access policy') system.pools.remove(pool) system.record_activity(user=u, service=u'HTTP', action=u'Removed', field=u'Pool', old=unicode(pool), new=None) system.date_modified = datetime.datetime.utcnow() pool.record_activity(user=u, service=u'HTTP', action=u'Removed', field=u'System', old=unicode(system), new=None) else: raise Forbidden403('You do not have permission to modify system %s' 'or remove systems from pool %s' % (system.fqdn, pool.name)) else: raise BadRequest400('System %s is not in pool %s' % (system.fqdn, pool.name)) return '', 204
def update_reservation_request(id): """ Updates the reservation request of a recipe. The request must be :mimetype:`application/json`. :param id: Recipe's id. :jsonparam boolean reserve: Whether the system will be reserved at the end of the recipe. If true, the system will be reserved. If false, the system will not be reserved. :jsonparam int duration: Number of seconds to rerserve the system. """ recipe = _get_recipe_by_id(id) if not recipe.can_update_reservation_request(identity.current.user): raise Forbidden403('Cannot update the reservation request of recipe %s' % recipe.id) data = read_json_request(request) if 'reserve' not in data: raise BadRequest400('No reserve specified') with convert_internal_errors(): if data['reserve']: if 'duration' not in data: raise BadRequest400('No duration specified') duration = data['duration'] if duration > MAX_SECONDS_PROVISION: raise BadRequest400('Reservation time exceeds maximum time of %s hours' % MAX_HOURS_PROVISION) if recipe.reservation_request: old_duration = recipe.reservation_request.duration recipe.reservation_request.duration = data['duration'] _record_activity(recipe, u'Reservation Request', old_duration, data['duration']) else: reservation_request = RecipeReservationRequest(data['duration']) recipe.reservation_request = reservation_request _record_activity(recipe, u'Reservation Request', None, reservation_request.duration, 'Changed') return jsonify(recipe.reservation_request.__json__()) else: if recipe.reservation_request: session.delete(recipe.reservation_request) _record_activity(recipe, u'Reservation Request', recipe.reservation_request.duration, None) return jsonify(RecipeReservationRequest.empty_json())
def add_access_policy_rule(pool_name): """ Adds a new rule to the access policy for a system pool. Each rule in the policy grants a permission to a single user, a group of users, or to everybody. See :ref:`system-access-policies-api` for a description of the expected JSON parameters. :param pool_name: System pool's name. """ pool = _get_pool_by_name(pool_name) if not pool.can_edit_policy(identity.current.user): raise Forbidden403('Cannot edit system pool policy') policy = pool.access_policy rule = read_json_request(request) if rule.get('user', None): user = User.by_user_name(rule['user']) if not user: raise BadRequest400("User '%s' does not exist" % rule['user']) else: user = None if rule.get('group', None): try: group = Group.by_name(rule['group']) except NoResultFound: raise BadRequest400("Group '%s' does not exist" % rule['group']) else: group = None try: permission = SystemPermission.from_string(rule['permission']) except ValueError: raise BadRequest400('Invalid permission') new_rule = policy.add_rule(user=user, group=group, everybody=rule['everybody'], permission=permission) pool.record_activity(user=identity.current.user, service=u'HTTP', field=u'Access Policy Rule', action=u'Added', new=repr(new_rule)) return '', 204
def _get_owner(data): if data is None: data = {} user_name = data.get('user_name') group_name = data.get('group_name') if user_name and group_name: raise Forbidden403('System pool can have either an user or a group as owner') if user_name: owner = User.by_user_name(user_name) if owner is None: raise BadRequest400('No such user %s' % user_name) owner_type = 'user' if group_name: try: owner = Group.by_name(group_name) except NoResultFound: raise BadRequest400('No such group %r' % group_name) owner_type = 'group' return owner, owner_type
def post_recipeset_comment(id): """ Adds a new comment to a recipe set. The request must be :mimetype:`application/json`. :param id: ID of the recipe set. :jsonparam string comment: Comment text. """ recipeset = _get_rs_by_id(id) if not recipeset.can_comment(identity.current.user): raise Forbidden403('Cannot post recipe set comment') data = read_json_request(request) if 'comment' not in data: raise BadRequest400('Missing "comment" key') with convert_internal_errors(): comment = RecipeSetComment(user=identity.current.user, comment=data['comment']) recipeset.comments.append(comment) session.flush() # to populate the id return jsonify(comment.__json__())
def delete_submission_delegate(username): """ Deletes a submission delegate for a user account. :param username: The user's username. :query string user_name: The submission delegate's username. """ user = _get_user(username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) if 'user_name' not in request.args: raise MethodNotAllowed405 submission_delegate = User.by_user_name(request.args['user_name']) if submission_delegate is None: raise NotFound404('Submission delegate %s does not exist' % request.args['user_name']) if not submission_delegate.is_delegate_for(user): raise Conflict409('User %s is not a submission delegate for %s' % (submission_delegate, user)) user.remove_submission_delegate(submission_delegate) return '', 204
def _create_keystone_trust(user): if not config.get('openstack.identity_api_url'): raise BadRequest400("OpenStack Integration is not enabled") if not user.can_edit_keystone_trust(identity.current.user): raise Forbidden403('Cannot edit Keystone trust of user %s' % user.username) data = read_json_request(request) if 'openstack_username' not in data: raise BadRequest400('No OpenStack username specified') if 'openstack_password' not in data: raise BadRequest400('No OpenStack password specified') if 'openstack_project_name' not in data: raise BadRequest400('No OpenStack project name specified') try: trust_id = dynamic_virt.create_keystone_trust(data['openstack_username'], data['openstack_password'], data['openstack_project_name']) except ValueError as err: raise BadRequest400(u'Could not authenticate with OpenStack using your credentials: %s' % unicode(err)) user.openstack_trust_id = trust_id user.record_activity(user=identity.current.user, service=u'HTTP', field=u'OpenStack Trust ID', action=u'Changed') return jsonify({'openstack_trust_id': trust_id})
def delete_keystone_trust(username): """ Deletes the Keystone trust for a user account. :param username: The user's username. """ user = _get_user(username) if not config.get('openstack.identity_api_url'): raise BadRequest400("OpenStack Integration is not enabled") if not user.can_edit_keystone_trust(identity.current.user): raise Forbidden403('Cannot edit Keystone trust of user %s' % username) if not user.openstack_trust_id: raise BadRequest400('No Keystone trust created by %s' % user) manager = dynamic_virt.VirtManager(user) manager.delete_keystone_trust() old_trust_id = user.openstack_trust_id user.openstack_trust_id = None user.record_activity(user=identity.current.user, service=u'HTTP', field=u'OpenStack Trust ID', action=u'Deleted', old=old_trust_id) return '', 204
def add_submission_delegate(username): """ Adds a submission delegate for a user account. Submission delegates are other users who are allowed to submit jobs on behalf of this user. :param username: The user's username. :jsonparam string user_name: The submission delegate's username. """ user = _get_user(username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) data = read_json_request(request) if 'user_name' not in data: raise BadRequest400('Missing "user_name" key to specify submission delegate') submission_delegate = User.by_user_name(data['user_name']) if submission_delegate is None: raise BadRequest400('Submission delegate %s does not exist' % data['user_name']) try: user.add_submission_delegate(submission_delegate, service=u'HTTP') except NoChangeException as e: raise Conflict409(unicode(e)) return 'Added', 201
def add_system_access_policy_rule(fqdn): system = _get_system_by_FQDN(fqdn) if not system.can_edit_policy(identity.current.user): raise Forbidden403('Cannot edit system policy') if system.custom_access_policy: policy = system.custom_access_policy else: policy = system.custom_access_policy = SystemAccessPolicy() rule = read_json_request(request) if rule['user']: user = User.by_user_name(rule['user']) if not user: raise BadRequest400("User '%s' does not exist" % rule['user']) else: user = None if rule['group']: try: group = Group.by_name(rule['group']) except NoResultFound: raise BadRequest400("Group '%s' does not exist" % rule['group']) else: group = None try: permission = SystemPermission.from_string(rule['permission']) except ValueError: raise BadRequest400 new_rule = policy.add_rule(user=user, group=group, everybody=rule['everybody'], permission=permission) system.record_activity(user=identity.current.user, service=u'HTTP', field=u'Access Policy Rule', action=u'Added', new=repr(new_rule)) return '', 204
def readd_user(group_name): """ Re-add a user who has been excluded from the group. :param group_name: Group's name. :queryparam string user_name: User's username. """ u = identity.current.user group = _get_group_by_name(group_name, lockmode='update') if not group.can_modify_membership(identity.current.user): raise Forbidden403('Cannot edit membership of group %s' % group_name) if 'user_name' not in request.args: raise MethodNotAllowed405 user = _get_user_by_username(request.args['user_name']) if user not in group.users: with convert_internal_errors(): group.readd_user(user, agent=identity.current.user) else: raise Conflict409('User %s is not excluded from group %s' % (user.user_name, group_name)) return '', 204
def delete_pool(pool_name): """ Deletes a system pool :param pool_name: System pool's name """ pool = _get_pool_by_name(pool_name, lockmode='update') u = identity.current.user if not pool.can_edit(u): raise Forbidden403('Cannot delete pool %s' % pool_name) systems = System.query.filter(System.pools.contains(pool)) System.record_bulk_activity(systems, user=identity.current.user, service=u'HTTP', action=u'Removed', field=u'Pool', old=unicode(pool), new=None) # Since we are deleting the pool, we will have to change the active # access policy for all systems using the pool's policy to their # custom policy systems = System.query.filter( System.active_access_policy == pool.access_policy) for system in systems: system.active_access_policy = system.custom_access_policy System.record_bulk_activity(systems, user=identity.current.user, service=u'HTTP', field=u'Active Access Policy', action=u'Changed', old='Pool policy: %s' % pool_name, new='Custom access policy') session.delete(pool) activity = Activity(u, u'HTTP', u'Deleted', u'Pool', pool_name) session.add(activity) return '', 204
def delete_submission_delegate(username): """ Deletes a public SSH public key belonging to the given user account. :param username: The user's username. :param id: Database id of the SSH public key to be deleted. """ user = User.by_user_name(username) #XXX lockmode='update' if user is None: raise NotFound404('User %s does not exist' % username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) if 'user_name' not in request.args: raise MethodNotAllowed405 submission_delegate = User.by_user_name(request.args['user_name']) if submission_delegate is None: raise NotFound404('Submission delegate %s does not exist' % request.args['user_name']) if not submission_delegate.is_delegate_for(user): raise Conflict409('User %s is not a submission delegate for %s' % (submission_delegate, user)) user.remove_submission_delegate(submission_delegate) return '', 204
def grant_ownership(group_name): """ Grant group ownership to a user. The user can either be the group member or not. If the user is not the group member, it will be added first. :param group_name: Group's name. :jsonparam string user_name: User's username. """ u = identity.current.user data = read_json_request(request) group = _get_group_by_name(group_name, lockmode='update') if not group.can_modify_ownership(identity.current.user): raise Forbidden403('Cannot edit ownership of group %s' % group_name) if 'user_name' not in data: raise BadRequest400('User not specified') user_name = data['user_name'] user = _get_user_by_username(user_name) if not group.has_owner(user): group.grant_ownership(user, agent=identity.current.user) else: raise Conflict409('User %s is already an owner of group %s' % (user.user_name, group_name)) return '', 204
def delete_access_policy_rules(pool_name): """ Deletes one or more matching rules from a system pool's access policy. See :ref:`system-access-policies-api` for description of the expected query parameters :param pool_name: System pool's name. """ pool = _get_pool_by_name(pool_name) if not pool.can_edit_policy(identity.current.user): raise Forbidden403('Cannot edit system policy') policy = pool.access_policy query = SystemAccessPolicyRule.query.filter( SystemAccessPolicyRule.policy == policy) if 'permission' in request.args: query = query.filter( SystemAccessPolicyRule.permission.in_( request.args.getlist('permission', type=SystemPermission.from_string))) else: raise MethodNotAllowed405 if 'user' in request.args: query = query.join(SystemAccessPolicyRule.user)\ .filter(User.user_name.in_(request.args.getlist('user'))) elif 'group' in request.args: query = query.join(SystemAccessPolicyRule.group)\ .filter(Group.group_name.in_(request.args.getlist('group'))) elif 'everybody' in request.args: query = query.filter(SystemAccessPolicyRule.everybody) else: raise MethodNotAllowed405 for rule in query: rule.record_deletion(service=u'HTTP') session.delete(rule) return '', 204
def remove_permission(group_name): """ Remove a permission from a group. :param group_name: Group's name. :queryparam permission_name: Permission's name. """ u = identity.current.user group = _get_group_by_name(group_name, lockmode='update') if not group.can_edit(u): raise Forbidden403('Cannot edit group %s' % group_name) if 'permission_name' not in request.args: raise MethodNotAllowed405 permission_name = request.args['permission_name'] permission = _get_permission_by_name(permission_name) if permission in group.permissions: group.permissions.remove(permission) group.record_activity(user=u, service=u'HTTP', action=u'Removed', field=u'Permission', old=unicode(permission), new=None) else: raise Conflict409('Group %s does not have permission %s' % (group_name, permission_name)) return '', 204
def provision_system(fqdn): system = _get_system_by_FQDN(fqdn) if not system.can_configure_netboot(identity.current.user): raise Forbidden403('Cannot provision system') data = read_json_request(request) with convert_internal_errors(): if not data['distro_tree'] or 'id' not in data['distro_tree']: raise ValueError('No distro tree specified') distro_tree = DistroTree.by_id(data['distro_tree']['id']) user = identity.current.user if user.rootpw_expired: raise ValueError('Your root password has expired, you must ' 'change or clear it in order to provision.') redirect(u"/view/%s" % system.fqdn) install_options = system.manual_provision_install_options(distro_tree)\ .combined_with(InstallOptions.from_strings(data.get('ks_meta'), data.get('koptions'), data.get('koptions_post'))) if 'ks' not in install_options.kernel_options: kickstart = generate_kickstart(install_options, distro_tree=distro_tree, system=system, user=user) install_options.kernel_options['ks'] = kickstart.link system.configure_netboot(distro_tree, install_options.kernel_options_str, service=u'HTTP') system.record_activity(user=identity.current.user, service=u'HTTP', action=u'Provision', field=u'Distro Tree', new=unicode(distro_tree)) if data.get('reboot'): system.action_power(action=u'reboot', service=u'HTTP') # in future "installations" will be a real thing in our model, # but for now we have nothing to return return 'Provisioned', 201
def update_user(username): """ Updates a Beaker user account. :param username: The user's username. :jsonparam string user_name: New username. If the username is changed, the response will include a Location header referring to the new URL for newly renamed user resource. :jsonparam string display_name: New display name. :jsonparam string email_address: New email address. :jsonparam string password: New password. Only valid when Beaker is not using external authentication for this account. :jsonparam string root_password: Root password to be set on systems provisioned by Beaker. :jsonparam boolean use_old_job_page: True if the user has opted to use the old, deprecated pre-Beaker-23 job page. :jsonparam boolean notify_job_completion: True if the user receives notifications upon the completion of an owned job. :jsonparam boolean notify_broken_system: True if the user receives notifications upon a system being automatically marked broken. :jsonparam boolean notify_system_loan: True if the user receives notifications when their systems are loaned or loans are returned. :jsonparam boolean notify_group_membership: True if the user receives notifications of modifications to the groups the user belongs to. :jsonparam boolean notify_reservesys: True if the user receives notifications upon reservesys being ready. :jsonparam boolean disabled: Whether the user should be temporarily disabled. Disabled users cannot log in or submit jobs, and any running jobs are cancelled when their account is disabled. :jsonparam string removed: Pass the string 'now' to remove a user account. Pass null to un-remove a removed user account. """ user = _get_user(username) data = read_json_request(request) renamed = False if data.get('password') is not None: if not user.can_change_password(identity.current.user): raise Forbidden403('Cannot change password for user %s' % user) with convert_internal_errors(): user.password = data.pop('password') if data: if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) with convert_internal_errors(): if 'user_name' in data: new_user_name = data['user_name'].strip() if user.user_name != new_user_name: if not user.can_rename(identity.current.user): raise Forbidden403('Cannot rename user %s to %s' % (user, new_user_name)) if User.by_user_name(new_user_name) is not None: raise Conflict409('User %s already exists' % new_user_name) user.user_name = new_user_name renamed = True if 'display_name' in data: user.display_name = data['display_name'].strip() if 'email_address' in data: user.email_address = data['email_address'].strip() if 'root_password' in data: new_root_password = data['root_password'] if user.root_password != new_root_password: user.root_password = new_root_password if 'use_old_job_page' in data: user.use_old_job_page = data['use_old_job_page'] if 'notify_job_completion' in data: user.notify_job_completion = data['notify_job_completion'] if 'notify_broken_system' in data: user.notify_broken_system = data['notify_broken_system'] if 'notify_system_loan' in data: user.notify_system_loan = data['notify_system_loan'] if 'notify_group_membership' in data: user.notify_group_membership = data['notify_group_membership'] if 'notify_reservesys' in data: user.notify_reservesys = data['notify_reservesys'] if 'disabled' in data: user.disabled = data['disabled'] if user.disabled: _disable(user, method=u'HTTP') if 'removed' in data: if data['removed'] is None: _unremove(user) elif data['removed'] == 'now': _remove(user, method=u'HTTP') else: raise ValueError('"removed" value must be "now" or null') session.flush() response = jsonify(user_full_json(user)) if renamed: response.headers.add('Location', absolute_url(user.href)) return response
def update_labcontroller(fqdn): """ Updates attributes of the lab controller identified by it's FQDN. The request body must be a json object or only the FQDN if that is the only value to be updated. :param string fqdn: Lab controller's new fully-qualified domain name. :jsonparam string user_name: User name associated with the lab controller. :jsonparam string email_address: Email of the user account associated with the lab controller. :jsonparam string password: Optional password for the user account used to login. :jsonparam string removed: If True, detaches all systems, cancels all running recipes and removes associated distro trees. If False, restores the lab controller. :jsonparam bool disabled: Whether the lab controller should be disabled. New recipes are not scheduled on a lab controller while it is disabled. :status 200: LabController updated. :status 400: Invalid data was given. """ labcontroller = find_labcontroller_or_raise404(fqdn) if not labcontroller.can_edit(identity.current.user): raise Forbidden403('Cannot edit lab controller') data = read_json_request(request) with convert_internal_errors(): # should the lab controller be removed? if data.get('removed', False) and not labcontroller.removed: remove_labcontroller(labcontroller) # should the controller be restored? if data.get('removed') is False and labcontroller.removed: restore_labcontroller(labcontroller) fqdn_changed = False new_fqdn = data.get('fqdn', fqdn) if labcontroller.fqdn != new_fqdn: lc = None try: lc = LabController.by_name(new_fqdn) except NoResultFound: pass if lc is not None: raise BadRequest400('FQDN %s already in use' % new_fqdn) labcontroller.record_activity(user=identity.current.user, service=u'HTTP', field=u'fqdn', action=u'Changed', old=labcontroller.fqdn, new=new_fqdn) labcontroller.fqdn = new_fqdn labcontroller.user.display_name = new_fqdn fqdn_changed = True if 'user_name' in data: user = find_user_or_create(data['user_name']) if labcontroller.user != user: user = update_user(user, display_name=new_fqdn, email_address=data.get( 'email_address', user.email_address), password=data.get('password', user.password)) labcontroller.record_activity(user=identity.current.user, service=u'HTTP', field=u'User', action=u'Changed', old=labcontroller.user.user_name, new=user.user_name) labcontroller.user = user if 'email_address' in data: new_email_address = data.get('email_address') if labcontroller.user.email_address != new_email_address: labcontroller.user.email_address = new_email_address if data.get('password') is not None: labcontroller.user.password = data.get('password') if labcontroller.disabled != data.get('disabled', labcontroller.disabled): labcontroller.record_activity(user=identity.current.user, service=u'HTTP', field=u'disabled', action=u'Changed', old=unicode(labcontroller.disabled), new=data['disabled']) labcontroller.disabled = data['disabled'] response = jsonify(labcontroller.__json__()) if fqdn_changed: response.headers.add('Location', absolute_url(labcontroller.href)) return response
def update_system(fqdn): system = _get_system_by_FQDN(fqdn) if not system.can_edit(identity.current.user): raise Forbidden403('Cannot edit system') data = read_json_request(request) # helper for recording activity below def record_activity(field, old, new, action=u'Changed'): system.record_activity(user=identity.current.user, service=u'HTTP', action=action, field=field, old=old, new=new) with convert_internal_errors(): # XXX what a nightmare... need to use a validation/conversion library, # and maybe simplify/relocate the activity recording stuff somehow changed = False renamed = False if 'fqdn' in data: new_fqdn = data['fqdn'].lower() if new_fqdn != system.fqdn: if System.query.filter(System.fqdn == new_fqdn).count(): raise Conflict409('System %s already exists' % new_fqdn) record_activity(u'FQDN', system.fqdn, new_fqdn) system.fqdn = new_fqdn changed = True renamed = True if 'owner' in data and data['owner'].get( 'user_name') != system.owner.user_name: if not system.can_change_owner(identity.current.user): raise Forbidden403('Cannot change owner') new_owner = User.by_user_name(data['owner'].get('user_name')) if new_owner is None: raise BadRequest400('No such user %s' % data['owner'].get('user_name')) record_activity(u'Owner', system.owner, new_owner) system.owner = new_owner changed = True if 'status' in data: new_status = SystemStatus.from_string(data['status']) if new_status != system.status: record_activity(u'Status', system.status, new_status) system.status = new_status if not new_status.bad and system.status_reason: # clear the status reason for "good" statuses record_activity(u'Status Reason', system.status_reason, None) system.status_reason = None changed = True if 'status_reason' in data: new_reason = data['status_reason'] or None if new_reason and not system.status.bad: raise ValueError('Cannot set status reason when status is %s' % system.status) if new_reason != system.status_reason: record_activity(u'Status Reason', system.status_reason, new_reason) system.status_reason = new_reason changed = True if 'type' in data: new_type = SystemType.from_string(data['type']) if new_type != system.type: record_activity(u'Type', system.type, new_type) system.type = new_type changed = True if 'arches' in data: new_arches = [Arch.by_name(a) for a in (data['arches'] or [])] added_arches = set(new_arches).difference(system.arch) removed_arches = set(system.arch).difference(new_arches) if added_arches or removed_arches: for added_arch in added_arches: record_activity(u'Arch', None, added_arch, u'Added') for removed_arch in removed_arches: record_activity(u'Arch', removed_arch, None, u'Removed') system.arch[:] = new_arches changed = True if 'lab_controller_id' in data: if data['lab_controller_id']: new_lc = LabController.by_id(data['lab_controller_id']) else: new_lc = None if new_lc != system.lab_controller: if system.open_reservation is not None: raise Conflict409( 'Unable to change lab controller while system ' 'is in use (return the system first)') record_activity(u'Lab Controller', system.lab_controller, new_lc) system.lab_controller = new_lc changed = True # If we're given any power-related keys, need to ensure system.power exists if not system.power and set(['power_type', 'power_address', 'power_user', 'power_password', 'power_id', 'power_quiescent_period'])\ .intersection(data.keys()): system.power = Power() if 'power_type' in data: new_power_type = PowerType.by_name(data['power_type']) if new_power_type != system.power.power_type: if not system.power.power_type: old_power_type = '' else: old_power_type = system.power.power_type.name record_activity(u'power_type', old_power_type, new_power_type.name) system.power.power_type = new_power_type changed = True if 'power_address' in data: new_power_address = data['power_address'] if not new_power_address: raise ValueError('Power address is required') if new_power_address != system.power.power_address: record_activity(u'power_address', system.power.power_address, data['power_address']) system.power.power_address = new_power_address changed = True if 'power_user' in data: new_power_user = data['power_user'] or u'' if new_power_user != (system.power.power_user or u''): record_activity(u'power_user', u'********', u'********') system.power.power_user = new_power_user changed = True if 'power_password' in data: new_power_password = data['power_password'] or u'' if new_power_password != (system.power.power_passwd or u''): record_activity(u'power_passwd', u'********', u'********') system.power.power_passwd = new_power_password changed = True if 'power_id' in data: new_power_id = data['power_id'] or u'' if new_power_id != (system.power.power_id or u''): record_activity(u'power_id', system.power.power_id, new_power_id) system.power.power_id = new_power_id changed = True if 'power_quiescent_period' in data: new_qp = int(data['power_quiescent_period']) if new_qp != system.power.power_quiescent_period: record_activity(u'power_quiescent_period', system.power.power_quiescent_period, new_qp) system.power.power_quiescent_period = new_qp changed = True if 'release_action' in data: new_release_action = ReleaseAction.from_string( data['release_action']) if new_release_action != (system.release_action or ReleaseAction.power_off): record_activity( u'release_action', (system.release_action or ReleaseAction.power_off), new_release_action) system.release_action = new_release_action changed = True if 'reprovision_distro_tree' in data: if (not data['reprovision_distro_tree'] or 'id' not in data['reprovision_distro_tree']): new_rpdt = None else: new_rpdt = DistroTree.by_id( data['reprovision_distro_tree']['id']) if new_rpdt != system.reprovision_distro_tree: record_activity(u'reprovision_distro_tree', unicode(system.reprovision_distro_tree), unicode(new_rpdt)) system.reprovision_distro_tree = new_rpdt changed = True if 'location' in data: new_location = data['location'] or None if new_location != system.location: record_activity(u'Location', system.location, new_location) system.location = new_location changed = True if 'lender' in data: new_lender = data['lender'] or None if new_lender != system.lender: record_activity(u'Lender', system.lender, new_lender) system.lender = new_lender changed = True if 'kernel_type' in data: new_kernel_type = KernelType.by_name(data['kernel_type']) if new_kernel_type != system.kernel_type: record_activity(u'Kernel Type', system.kernel_type, new_kernel_type) system.kernel_type = new_kernel_type changed = True if 'hypervisor' in data: if data['hypervisor']: new_hypervisor = Hypervisor.by_name(data['hypervisor']) else: new_hypervisor = None if new_hypervisor != system.hypervisor: record_activity(u'Hypervisor', system.hypervisor, new_hypervisor) system.hypervisor = new_hypervisor changed = True if 'vendor' in data: new_vendor = data['vendor'] or None if new_vendor != system.vendor: record_activity(u'Vendor', system.vendor, new_vendor) system.vendor = new_vendor changed = True if 'model' in data: new_model = data['model'] or None if new_model != system.model: record_activity(u'Model', system.model, new_model) system.model = new_model changed = True if 'serial_number' in data: new_serial_number = data['serial_number'] or None if new_serial_number != system.serial: record_activity(u'Serial Number', system.serial, new_serial_number) system.serial = new_serial_number changed = True if 'mac_address' in data: new_mac_address = data['mac_address'] or None if new_mac_address != system.mac_address: record_activity(u'MAC Address', system.mac_address, new_mac_address) system.mac_address = new_mac_address changed = True if 'memory' in data: new_memory = int(data['memory']) if data['memory'] else None if new_memory != system.memory: record_activity(u'Memory', system.memory, new_memory) system.memory = new_memory changed = True if 'numa_nodes' in data: new_numa_nodes = int( data['numa_nodes']) if data['numa_nodes'] else None if not system.numa: system.numa = Numa() if new_numa_nodes != system.numa.nodes: record_activity(u'NUMA/Nodes', system.numa.nodes, new_numa_nodes) system.numa.nodes = new_numa_nodes changed = True if changed: # XXX clear checksum!? system.date_modified = datetime.datetime.utcnow() response = jsonify(system.__json__()) if renamed: response.headers.add('Location', url('/view/%s' % system.fqdn)) return response