def remove_group_membership(group_name): """ Remove a user from a group. If the user has the group ownership, it will be revoked. :param group_name: Group's name. :jsonparam 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 not group.can_remove_member(u, user.id): raise Forbidden403('Cannot remove user %s from group %s' % (user, group_name)) if user in group.users: group.remove_member(user, agent=identity.current.user) mail.group_membership_notify(user, group, agent=identity.current.user, action='Removed') else: raise Conflict409('User %s is not a member of group %s' % (user.user_name, group_name)) return '', 204
def exclude_user(group_name): """ Exclude a user from an inverted group. Then the user will not have the group membership. :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_membership(identity.current.user): raise Forbidden403('Cannot edit membership of group %s' % group_name) if group.membership_type == GroupMembershipType.normal: raise NotFound404('Normal group %s do not have excluded users' % group_name) if 'user_name' not in data: raise BadRequest400('User not specified') user = _get_user_by_username(data['user_name']) if user in group.users: if not group.can_exclude_member(u, user.id): raise Forbidden403('Cannot exclude user %s from group %s' % (user, group_name)) with convert_internal_errors(): group.exclude_user(user, agent=identity.current.user) else: raise Conflict409('User %s is already excluded from group %s' % (user.user_name, group_name)) return '', 204
def add_system(): # We accept JSON or form-encoded for convenience if request.json: if 'fqdn' not in request.json: raise BadRequest400('Missing fqdn key') new_fqdn = request.json['fqdn'] elif request.form: if 'fqdn' not in request.form: raise BadRequest400('Missing fqdn parameter') new_fqdn = request.form['fqdn'] else: raise UnsupportedMediaType415 with convert_internal_errors(): if System.query.filter(System.fqdn == new_fqdn).count() != 0: raise Conflict409('System with fqdn %r already exists' % new_fqdn) system = System(fqdn=new_fqdn, owner=identity.current.user) 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) # XXX this should be 201 with Location: /systems/FQDN/ but 302 is more # convenient because it lets us use a traditional browser form without AJAX # handling, and for now we're redirecting to /view/FQDN until that is moved # to /systems/FQDN/ return flask_redirect(url(u'/view/%s#essentials' % system.fqdn))
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 create_pool(): """ Creates a new system pool in Beaker. The request must be :mimetype:`application/x-www-form-urlencoded` or :mimetype:`application/json`. :jsonparam string name: 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 owner for the system pool. :status 201: The system pool was successfully created. """ owner = None description = None u = identity.current.user if request.json: if 'name' not in request.json: raise BadRequest400('Missing pool name key') new_name = request.json['name'] if 'owner' in request.json: owner = request.json['owner'] if 'description' in request.json: description = request.json['description'] elif request.form: if 'name' not in request.form: raise BadRequest400('Missing pool name parameter') new_name = request.form['name'] if 'owner' in request.form: owner = request.form['owner'] if 'description' in request.form: description = request.form['description'] else: raise UnsupportedMediaType415 with convert_internal_errors(): if SystemPool.query.filter(SystemPool.name == new_name).count() != 0: raise Conflict409('System pool with name %r already exists' % new_name) pool = SystemPool(name=new_name, description=description) session.add(pool) if owner: owner, owner_type = _get_owner(owner) if owner_type == 'user': pool.owning_user = owner else: pool.owning_group = owner else: pool.owning_user = u # new systems pool are visible to everybody by default pool.access_policy = SystemAccessPolicy() pool.access_policy.add_rule(SystemPermission.view, everybody=True) pool.record_activity(user=u, service=u'HTTP', action=u'Created', field=u'Pool', new=unicode(pool)) response = jsonify(pool.__json__()) response.status_code = 201 response.headers.add('Location', absolute_url(pool.href)) return response
def _create_labcontroller_helper(data): with convert_internal_errors(): if LabController.query.filter_by(fqdn=data['fqdn']).count(): raise Conflict409('Lab Controller %s already exists' % data['fqdn']) user = find_user_or_create(data['user_name']) user = update_user( user=user, display_name=data['fqdn'], email_address=data.get('email_address', user.email_address), password=data.get('password', user.password) ) labcontroller = LabController(fqdn=data['fqdn'], disabled=False) labcontroller.record_activity( user=identity.current.user, service=u'HTTP', action=u'Changed', field=u'FQDN', old=u'', new=data['fqdn']) labcontroller.user = user labcontroller.record_activity( user=identity.current.user, service=u'HTTP', action=u'Changed', field=u'User', old=u'', new=user.user_name) # For backwards compatibility labcontroller.record_activity( user=identity.current.user, service=u'HTTP', action=u'Changed', field=u'Disabled', old=u'', new=unicode(labcontroller.disabled)) session.add(labcontroller) # flush it so we return an id, otherwise we'll end up back in here from # the edit form session.flush() response = jsonify(labcontroller.__json__()) response.status_code = 201 return response
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 = User.by_user_name(username) 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) 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 revoke_ownership(group_name): """ Revoke group ownership from an existing group user. :param group_name: Group's name. :queryparam user_name: User's username. """ u = identity.current.user 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 request.args: raise MethodNotAllowed405 user = _get_user_by_username(request.args['user_name']) if user not in group.users: raise BadRequest400('User is not a member of group %s' % group_name) if group.has_owner(user): if len(group.owners()) == 1 and not u.is_admin(): raise Forbidden403('Cannot remove the only owner') group.revoke_ownership(user, agent=identity.current.user) else: raise Conflict409('User %s is not an owner of group %s' % (user.user_name, group_name)) return '', 204
def add_group_membership(group_name): """ Add a user to a group. :param group_name: Group's name. :jsonparam string user_name: User's username. :jsonparam boolean is_owner: If true, the given user will become one of the group owners. """ u = identity.current.user data = read_json_request(request) 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 data: raise BadRequest400('User not specified') user = _get_user_by_username(data['user_name']) if user.removed: raise BadRequest400('Cannot add deleted user %s to group' % user.user_name) is_owner = data.get('is_owner', False) if user not in group.users: group.add_member(user, is_owner=is_owner, agent=identity.current.user) mail.group_membership_notify(user, group, agent=u, action='Added') else: raise Conflict409('User %s is already a member of group %s' % (user.user_name, group_name)) return '', 204
def create_group(): """ Creates a new user group in Beaker. The request must be :mimetype:`application/json`. :jsonparam string group_name: Symbolic name for the group. :jsonparam string display_name: Human-friendly display name for the group. :jsonparam string description: Description of the group. :jsonparam string root_password: Optional root password for group jobs. If this is not set, group jobs will use the root password preferences of the job submitter. :jsonparam string membership_type: Specifies how group membership is populated. Possible values are: * normal: Group is initially empty, members are explicitly added and removed by group owner. * ldap: Membership is populated from the LDAP group with the same group name. * inverted: Group contains all Beaker users *except* users who have been explicitly excluded by the group owner. :status 201: The group was successfully created. """ user = identity.current.user data = read_json_request(request) if 'group_name' not in data: raise BadRequest400('Missing group_name key') if 'display_name' not in data: raise BadRequest400('Missing display_name key') # for backwards compatibility if data.pop('ldap', False): data['membership_type'] = 'ldap' try: Group.by_name(data['group_name']) except NoResultFound: pass else: raise Conflict409("Group '%s' already exists" % data['group_name']) with convert_internal_errors(): group = Group.lazy_create(group_name=data['group_name']) group.display_name = data['display_name'] group.description = data.get('description') group.root_password = data.get('root_password') session.add(group) group.record_activity(user=user, service=u'HTTP', field=u'Group', action=u'Created') if data.get('membership_type'): group.membership_type = GroupMembershipType.from_string( data['membership_type']) if group.membership_type == GroupMembershipType.ldap: group.refresh_ldap_members() else: # LDAP groups don't have any owners group.add_member(user, is_owner=True, agent=identity.current.user) response = jsonify(group.__json__()) response.status_code = 201 response.headers.add('Location', absolute_url(group.href)) return response
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 submit_inventory_job(): """ Submit a inventory job with the most suitable distro selected automatically. Returns a dictionary consisting of the job_id, recipe_id, status (recipe status) and the job XML. If ``dryrun`` is set to ``True`` in the request, the first three are set to ``None``. :jsonparam string fqdn: Fully-qualified domain name for the system. :jsonparam bool dryrun: If True, do not submit the job """ if 'fqdn' not in request.json: raise BadRequest400('Missing the fqdn parameter') fqdn = request.json['fqdn'] if 'dryrun' in request.json: dryrun = request.json['dryrun'] else: dryrun = False try: system = System.by_fqdn(fqdn, identity.current.user) except NoResultFound: raise BadRequest400('System not found: %s' % fqdn) if system.find_current_hardware_scan_recipe(): raise Conflict409('Hardware scanning already in progress') distro = system.distro_tree_for_inventory() if not distro: raise BadRequest400( 'Could not find a compatible distro for hardware scanning available to this system' ) job_details = {} job_details['system'] = system job_details['whiteboard'] = 'Update Inventory for %s' % fqdn with convert_internal_errors(): job_xml = Job.inventory_system_job(distro, dryrun=dryrun, **job_details) r = {} if not dryrun: r = system.find_current_hardware_scan_recipe().__json__() else: r = { 'recipe_id': None, 'status': None, 'job_id': None, } r['job_xml'] = job_xml r = jsonify(r) return r
def create_powertype(): """ Creates a new power type. The request must be :mimetype:`application/json`. :jsonparam string name: Name for the power type. :status 201: The power type was successfully created. """ data = read_json_request(request) with convert_internal_errors(): if PowerType.query.filter_by(**data).count(): raise Conflict409('Power type %s already exists' % data['name']) powertype = PowerType(**data) activity = Activity(identity.current.user, u'HTTP', u'Created', u'PowerType', powertype.name) session.add_all([powertype, activity]) response = jsonify(powertype.__json__()) response.status_code = 201 return response
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_user(): """ Creates a new user account in Beaker. """ data = read_json_request(request) with convert_internal_errors(): new_user_name = data.get('user_name', '').strip() existing_user = User.by_user_name(new_user_name) if existing_user is not None: raise Conflict409('User %s already exists' % new_user_name) new_display_name = data.get('display_name', '').strip() new_email_address = data.get('email_address', '').strip() user = User(user_name=new_user_name, display_name=new_display_name, email_address=new_email_address) session.add(user) session.flush() # to populate id response = jsonify(user_full_json(user)) response.status_code = 201 response.headers.add('Location', absolute_url(user.href)) return response
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 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_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 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_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