Пример #1
0
def _delete_bundles():
    """
    Delete the bundles specified.

    Query parameters:
     - `force`: 1 to allow deletion of bundles that have descendants or that
       appear across multiple worksheets, or 0 to throw an error if any of the
       specified bundles have multiple references. Default is 0.
     - `recursive`: 1 to remove all bundles downstream too, or 0 otherwise.
       Default is 0.
     - `data-only`: 1 to only remove contents of the bundle(s) from the bundle
       store and leave the bundle metadata intact, or 0 to remove both the
       bundle contents and the bundle metadata. Default is 0.
     - `dry-run`: 1 to just return list of bundles that would be deleted with
       the given parameters without actually deleting them, or 0 to perform
       the deletion. Default is 0.
    """
    uuids = get_resource_ids(request.json, 'bundles')
    force = query_get_bool('force', default=False)
    recursive = query_get_bool('recursive', default=False)
    data_only = query_get_bool('data-only', default=False)
    dry_run = query_get_bool('dry-run', default=False)
    deleted_uuids = delete_bundles(uuids,
                                   force=force,
                                   recursive=recursive,
                                   data_only=data_only,
                                   dry_run=dry_run)

    # Return list of deleted ids as meta
    return json_api_meta({}, {'ids': deleted_uuids})
Пример #2
0
def create_worksheet_items():
    """
    Bulk add worksheet items.

    |replace| - Replace existing items in host worksheets. Default is False.
    """
    replace = query_get_bool('replace', False)

    new_items = WorksheetItemSchema(strict=True, many=True).load(request.json).data

    worksheet_to_items = {}
    for item in new_items:
        worksheet_to_items.setdefault(item['worksheet_uuid'], []).append(item)

    for worksheet_uuid, items in worksheet_to_items.iteritems():
        worksheet_info = get_worksheet_info(worksheet_uuid, fetch_items=True)
        if replace:
            # Replace items in the worksheet
            update_worksheet_items(
                worksheet_info, [Worksheet.Item.as_tuple(i) for i in items], convert_items=False
            )
        else:
            # Append items to the worksheet
            for item in items:
                add_worksheet_item(worksheet_uuid, Worksheet.Item.as_tuple(item))

    return WorksheetItemSchema(many=True).dump(new_items).data
Пример #3
0
def create_worksheet_items():
    """
    Bulk add worksheet items.

    |replace| - Replace existing items in host worksheets. Default is False.
    """
    replace = query_get_bool('replace', False)
    # Get the uuid of the current worksheet
    worksheet_uuid = query_get_type(str, 'uuid')
    new_items = WorksheetItemSchema(strict=True,
                                    many=True).load(request.json).data

    # Map of worksheet_uuid to list of items that exist in this worksheet
    worksheet_to_items = {}
    worksheet_to_items.setdefault(worksheet_uuid, [])

    for item in new_items:
        worksheet_to_items[worksheet_uuid].append(item)

    for worksheet_uuid, items in worksheet_to_items.items():
        worksheet_info = get_worksheet_info(worksheet_uuid, fetch_items=True)
        if replace:
            # Replace items in the worksheet
            update_worksheet_items(worksheet_info,
                                   [Worksheet.Item.as_tuple(i) for i in items],
                                   convert_items=False)
        else:
            # Append items to the worksheet
            for item in items:
                add_worksheet_item(worksheet_uuid,
                                   Worksheet.Item.as_tuple(item))

    return WorksheetItemSchema(many=True).dump(new_items).data
Пример #4
0
def create_worksheet_items():
    """
    Bulk add worksheet items.

    |replace| - Replace existing items in host worksheets. Default is False.
    """
    replace = query_get_bool('replace', False)

    new_items = WorksheetItemSchema(
        strict=True,
        many=True,
    ).load(request.json).data

    worksheet_to_items = {}
    for item in new_items:
        worksheet_to_items.setdefault(item['worksheet_uuid'], []).append(item)

    for worksheet_uuid, items in worksheet_to_items.iteritems():
        worksheet_info = get_worksheet_info(worksheet_uuid, fetch_items=True)
        if replace:
            # Replace items in the worksheet
            update_worksheet_items(worksheet_info,
                                   [Worksheet.Item.as_tuple(i) for i in items],
                                   convert_items=False)
        else:
            # Append items to the worksheet
            for item in items:
                add_worksheet_item(worksheet_uuid,
                                   Worksheet.Item.as_tuple(item))

    return WorksheetItemSchema(many=True).dump(new_items).data
Пример #5
0
def build_bundles_document(bundle_uuids):
    include_set = query_get_json_api_include_set(supported={
        'owner', 'group_permissions', 'children', 'host_worksheets'
    })

    bundles_dict = get_bundle_infos(
        bundle_uuids,
        get_children='children' in include_set,
        get_permissions='group_permissions' in include_set,
        get_host_worksheets='host_worksheets' in include_set,
        ignore_not_found=False,
    )

    # Create list of bundles in original order
    bundles = [bundles_dict[uuid] for uuid in bundle_uuids]

    # Build response document
    document = BundleSchema(many=True).dump(bundles).data

    # Shim in display metadata used by the front-end application
    if query_get_bool('include_display_metadata', default=False):
        for bundle, data in zip(bundles, document['data']):
            bundle_class = get_bundle_subclass(bundle['bundle_type'])
            json_api_meta(
                data,
                {
                    'editable_metadata_keys':
                    worksheet_util.get_editable_metadata_fields(bundle_class),
                    'metadata_type':
                    worksheet_util.get_metadata_types(bundle_class),
                },
            )

    if 'owner' in include_set:
        owner_ids = set(b['owner_id'] for b in bundles
                        if b['owner_id'] is not None)
        json_api_include(
            document,
            UserSchema(),
            local.model.get_users(user_ids=owner_ids,
                                  limit=len(owner_ids))['results'],
        )

    if 'group_permissions' in include_set:
        for bundle in bundles:
            json_api_include(document, BundlePermissionSchema(),
                             bundle.get('group_permissions', []))

    if 'children' in include_set:
        for bundle in bundles:
            json_api_include(document, BundleSchema(),
                             bundle.get('children', []))

    if 'host_worksheets' in include_set:
        for bundle in bundles:
            json_api_include(document, WorksheetSchema(),
                             bundle.get('host_worksheets', []))

    return document
Пример #6
0
def delete_worksheets():
    """
    Delete the bundles specified.
    If |force|, allow deletion of bundles that have descendants or that appear across multiple worksheets.
    If |recursive|, add all bundles downstream too.
    If |data-only|, only remove from the bundle store, not the bundle metadata.
    If |dry-run|, just return list of bundles that would be deleted, but do not actually delete.
    """
    uuids = get_resource_ids(request.json, 'worksheets')
    force = query_get_bool('force', default=False)
    for uuid in uuids:
        delete_worksheet(uuid, force)
Пример #7
0
def delete_worksheets():
    """
    Delete the bundles specified.
    If |force|, allow deletion of bundles that have descendants or that appear across multiple worksheets.
    If |recursive|, add all bundles downstream too.
    If |data-only|, only remove from the bundle store, not the bundle metadata.
    If |dry-run|, just return list of bundles that would be deleted, but do not actually delete.
    """
    uuids = get_resource_ids(request.json, 'worksheets')
    force = query_get_bool('force', default=False)
    for uuid in uuids:
        delete_worksheet(uuid, force)
Пример #8
0
def _delete_bundles():
    """
    Delete the bundles specified.
    If |force|, allow deletion of bundles that have descendants or that appear across multiple worksheets.
    If |recursive|, add all bundles downstream too.
    If |data-only|, only remove from the bundle store, not the bundle metadata.
    If |dry-run|, just return list of bundles that would be deleted, but do not actually delete.
    """
    uuids = get_resource_ids(request.json, 'bundles')
    force = query_get_bool('force', default=False)
    recursive = query_get_bool('recursive', default=False)
    data_only = query_get_bool('data-only', default=False)
    dry_run = query_get_bool('dry-run', default=False)
    deleted_uuids = delete_bundles(uuids,
                                   force=force,
                                   recursive=recursive,
                                   data_only=data_only,
                                   dry_run=dry_run)

    # Return list of deleted ids as meta
    return json_api_meta({}, {'ids': deleted_uuids})
Пример #9
0
 def wrapper(*args, **kwargs):
     try:
         result = callback(*args, **kwargs)
         # If response is JSON, add server version to meta
         if isinstance(result, dict):
             json_api_meta(result, {'version': CODALAB_VERSION})
         return result
     except ValidationError as err:
         format_errors = query_get_bool('format_errors', default=False)
         if format_errors:
             msg = err.messages
         else:
             msg = '\n'.join([e['detail'] for e in err.messages['errors']])
         abort(httplib.BAD_REQUEST, msg)
Пример #10
0
 def wrapper(*args, **kwargs):
     try:
         result = callback(*args, **kwargs)
         # If response is JSON, add server version to meta
         if isinstance(result, dict):
             json_api_meta(result, {'version': CODALAB_VERSION})
         return result
     except ValidationError as err:
         format_errors = query_get_bool('format_errors', default=False)
         if format_errors:
             msg = err.messages
         else:
             msg = '\n'.join(
                 [e['detail'] for e in err.messages['errors']])
         abort(httplib.BAD_REQUEST, msg)
Пример #11
0
def _update_bundle_contents_blob(uuid):
    """
    Update the contents of the given running or uploading bundle.

    Query parameters:
    - `urls`: (optional) comma-separated list of URLs from which to fetch data
      to fill the bundle, using this option will ignore any uploaded file data
    - `git`: (optional) 1 if URL should be interpreted as git repos to clone
      or 0 otherwise, default is 0.
    - `filename`: (optional) filename of the uploaded file, used to indicate
      whether or not it is an archive, default is 'contents'
    - `unpack`: (optional) 1 if the uploaded file should be unpacked if it is
      an archive, or 0 otherwise, default is 1
    - `simplify`: (optional) 1 if the uploaded file should be 'simplified' if
      it is an archive, or 0 otherwise, default is 1.
    - `finalize_on_failure`: (optional) 1 if bundle state should be set
      to 'failed' in the case of a failure during upload, or 0 if the bundle
      state should not change on failure. Default is 0.
    - `finalize_on_success`: (optional) 1 if bundle state should be set
      to 'state_on_success' when the upload finishes successfully. Default is
      True
    - `state_on_success`: (optional) Update the bundle state to this state if
      the upload completes successfully. Must be either 'ready' or 'failed'.
      Default is 'ready'.
    """
    check_bundles_have_all_permission(local.model, request.user, [uuid])
    bundle = local.model.get_bundle(uuid)
    if bundle.state in State.FINAL_STATES:
        abort(http.client.FORBIDDEN,
              'Contents cannot be modified, bundle already finalized.')

    # Get and validate query parameters
    finalize_on_failure = query_get_bool('finalize_on_failure', default=False)
    finalize_on_success = query_get_bool('finalize_on_success', default=True)
    final_state = request.query.get('state_on_success', default=State.READY)
    if finalize_on_success and final_state not in State.FINAL_STATES:
        abort(
            http.client.BAD_REQUEST,
            'state_on_success must be one of %s' %
            '|'.join(State.FINAL_STATES),
        )

    # If this bundle already has data, remove it.
    if local.upload_manager.has_contents(bundle):
        local.upload_manager.cleanup_existing_contents(bundle)

    # Store the data.
    try:
        sources = None
        if request.query.urls:
            sources = query_get_list('urls')
        # request without "filename" doesn't need to upload to bundle store
        if request.query.filename:
            filename = request.query.get('filename', default='contents')
            sources = [(filename, request['wsgi.input'])]
        if sources:
            local.upload_manager.upload_to_bundle_store(
                bundle,
                sources=sources,
                follow_symlinks=False,
                exclude_patterns=None,
                remove_sources=False,
                git=query_get_bool('git', default=False),
                unpack=query_get_bool('unpack', default=True),
                simplify_archives=query_get_bool('simplify', default=True),
            )  # See UploadManager for full explanation of 'simplify'
            bundle_link_url = getattr(bundle.metadata, "link_url", None)
            bundle_location = bundle_link_url or local.bundle_store.get_bundle_location(
                bundle.uuid)
            local.model.update_disk_metadata(bundle,
                                             bundle_location,
                                             enforce_disk_quota=True)

    except UsageError as err:
        # This is a user error (most likely disk quota overuser) so raise a client HTTP error
        if local.upload_manager.has_contents(bundle):
            local.upload_manager.cleanup_existing_contents(bundle)
        msg = "Upload failed: %s" % err
        local.model.update_bundle(bundle, {
            'state': State.FAILED,
            'metadata': {
                'failure_message': msg
            }
        })
        abort(http.client.BAD_REQUEST, msg)

    except Exception as e:
        # Upload failed: cleanup, update state if desired, and return HTTP error
        if local.upload_manager.has_contents(bundle):
            local.upload_manager.cleanup_existing_contents(bundle)

        msg = "Upload failed: %s" % e

        # The client may not want to finalize the bundle on failure, to keep
        # open the possibility of retrying the upload in the case of transient
        # failure.
        # Workers also use this API endpoint to upload partial contents of
        # running bundles, and they should use finalize_on_failure=0 to avoid
        # letting transient errors during upload fail the bundles prematurely.
        if finalize_on_failure:
            local.model.update_bundle(bundle, {
                'state': State.FAILED,
                'metadata': {
                    'failure_message': msg
                }
            })

        abort(http.client.INTERNAL_SERVER_ERROR, msg)

    else:
        if finalize_on_success:
            # Upload succeeded: update state
            local.model.update_bundle(bundle, {'state': final_state})
Пример #12
0
def _create_bundles():
    """
    Bulk create bundles.

    Query parameters:
    - `worksheet`: UUID of the parent worksheet of the new bundle, add to
      this worksheet if not detached or shadowing another bundle. The new
      bundle also inherits permissions from this worksheet.
    - `shadow`: UUID of the bundle to "shadow" (the new bundle will be added
      as an item immediately after this bundle in its parent worksheet).
    - `detached`: 1 if should not add new bundle to any worksheet,
      or 0 otherwise. Default is 0.
    - `wait_for_upload`: 1 if the bundle state should be initialized to
      "uploading" regardless of the bundle type, or 0 otherwise. Used when
      copying bundles from another CodaLab instance, this prevents these new
      bundles from being executed by the BundleManager. Default is 0.
    """
    worksheet_uuid = request.query.get('worksheet')
    shadow_parent_uuid = request.query.get('shadow')
    after_sort_key = request.query.get('after_sort_key')
    detached = query_get_bool('detached', default=False)
    if worksheet_uuid is None:
        abort(
            http.client.BAD_REQUEST,
            "Parent worksheet id must be specified as"
            "'worksheet' query parameter",
        )

    # Deserialize bundle fields
    bundles = (BundleSchema(strict=True,
                            many=True,
                            dump_only=BUNDLE_CREATE_RESTRICTED_FIELDS).load(
                                request.json).data)

    # Check for all necessary permissions
    worksheet = local.model.get_worksheet(worksheet_uuid, fetch_items=False)
    check_worksheet_has_all_permission(local.model, request.user, worksheet)
    worksheet_util.check_worksheet_not_frozen(worksheet)
    request.user.check_quota(need_time=True, need_disk=True)

    created_uuids = []
    for bundle in bundles:
        # Prep bundle info for saving into database
        # Unfortunately cannot use the `construct` methods because they don't
        # provide a uniform interface for constructing bundles for all types
        # Hopefully this can all be unified after REST migration is complete
        bundle_uuid = bundle.setdefault('uuid', spec_util.generate_uuid())
        created_uuids.append(bundle_uuid)
        bundle_class = get_bundle_subclass(bundle['bundle_type'])
        bundle['owner_id'] = request.user.user_id

        metadata = bundle.get("metadata", {})
        if metadata.get("link_url"):
            bundle['state'] = State.READY
        elif issubclass(bundle_class, UploadedBundle) or query_get_bool(
                'wait_for_upload', False):
            bundle['state'] = State.UPLOADING
        else:
            bundle['state'] = State.CREATED
        bundle[
            'is_anonymous'] = worksheet.is_anonymous  # inherit worksheet anonymity
        bundle.setdefault('metadata', {})['created'] = int(time.time())
        for dep in bundle.setdefault('dependencies', []):
            dep['child_uuid'] = bundle_uuid

        # Create bundle object
        bundle = bundle_class(bundle, strict=False)

        # Save bundle into model
        local.model.save_bundle(bundle)

        # Inherit worksheet permissions
        group_permissions = local.model.get_group_worksheet_permissions(
            request.user.user_id, worksheet_uuid)
        set_bundle_permissions([{
            'object_uuid': bundle_uuid,
            'group_uuid': p['group_uuid'],
            'permission': p['permission'],
        } for p in group_permissions])

        # Add as item to worksheet
        if not detached:
            if shadow_parent_uuid is None:
                local.model.add_worksheet_items(
                    worksheet_uuid, [worksheet_util.bundle_item(bundle_uuid)],
                    after_sort_key)
            else:
                local.model.add_shadow_worksheet_items(shadow_parent_uuid,
                                                       bundle_uuid)

    # Get created bundles
    bundles_dict = get_bundle_infos(created_uuids)

    # Return bundles in original order
    # Need to check if the UUID is in the dict, since there is a chance that a bundle is deleted
    # right after being created.
    bundles = [
        bundles_dict[uuid] for uuid in created_uuids if uuid in bundles_dict
    ]
    return BundleSchema(many=True).dump(bundles).data
Пример #13
0
def _update_bundle_contents_blob(uuid):
    """
    Update the contents of the given running or uploading bundle.

    Query parameters:
    - `urls`: (optional) URL from which to fetch data to fill the bundle;
      using this option will ignore any uploaded file data. Only supports one URL.
    - `git`: (optional) 1 if URL should be interpreted as git repos to clone
      or 0 otherwise, default is 0.
    - `filename`: (optional) filename of the uploaded file, used to indicate
      whether or not it is an archive, default is 'contents'
    - `unpack`: (optional) 1 if the uploaded file should be unpacked if it is
      an archive, or 0 otherwise, default is 1
    - `finalize_on_failure`: (optional) 1 if bundle state should be set
      to 'failed' in the case of a failure during upload, or 0 if the bundle
      state should not change on failure. Default is 0.
    - `finalize_on_success`: (optional) 1 if bundle state should be set
      to 'state_on_success' when the upload finishes successfully. Default is
      True
    - `state_on_success`: (optional) Update the bundle state to this state if
      the upload completes successfully. Must be either 'ready' or 'failed'.
      Default is 'ready'.
    - `use_azure_blob_beta`: (optional) Use Azure Blob Storage to store the bundle.
      Default is False. If CODALAB_ALWAYS_USE_AZURE_BLOB_BETA is set, this parameter
      is disregarded, as Azure Blob Storage will always be used.
    """
    check_bundles_have_all_permission(local.model, request.user, [uuid])
    bundle = local.model.get_bundle(uuid)
    if bundle.state in State.FINAL_STATES:
        abort(http.client.FORBIDDEN, 'Contents cannot be modified, bundle already finalized.')

    # Get and validate query parameters
    finalize_on_failure = query_get_bool('finalize_on_failure', default=False)
    finalize_on_success = query_get_bool('finalize_on_success', default=True)
    use_azure_blob_beta = os.getenv("CODALAB_ALWAYS_USE_AZURE_BLOB_BETA") or query_get_bool(
        'use_azure_blob_beta', default=False
    )
    final_state = request.query.get('state_on_success', default=State.READY)
    if finalize_on_success and final_state not in State.FINAL_STATES:
        abort(
            http.client.BAD_REQUEST,
            'state_on_success must be one of %s' % '|'.join(State.FINAL_STATES),
        )

    # If this bundle already has data, remove it.
    if local.upload_manager.has_contents(bundle):
        local.upload_manager.cleanup_existing_contents(bundle)

    # Store the data.
    try:
        source = None
        if request.query.urls:
            sources = query_get_list('urls')
            if len(sources) != 1:
                abort(http.client.BAD_REQUEST, "Exactly one url must be provided.")
            source = sources[0]
        # request without "filename" doesn't need to upload to bundle store
        if request.query.filename:
            filename = request.query.get('filename', default='contents')
            source = (filename, request['wsgi.input'])
        bundle_link_url = getattr(bundle.metadata, "link_url", None)
        if bundle_link_url:
            # Don't upload to bundle store if using --link, as the path
            # already exists.
            pass
        elif source:
            local.upload_manager.upload_to_bundle_store(
                bundle,
                source=source,
                git=query_get_bool('git', default=False),
                unpack=query_get_bool('unpack', default=True),
                use_azure_blob_beta=use_azure_blob_beta,
            )
            bundle_link_url = getattr(bundle.metadata, "link_url", None)
            bundle_location = bundle_link_url or local.bundle_store.get_bundle_location(bundle.uuid)
            local.model.update_disk_metadata(bundle, bundle_location, enforce_disk_quota=True)

    except UsageError as err:
        # This is a user error (most likely disk quota overuser) so raise a client HTTP error
        if local.upload_manager.has_contents(bundle):
            local.upload_manager.cleanup_existing_contents(bundle)
        msg = "Upload failed: %s" % err
        local.model.update_bundle(
            bundle,
            {
                'state': State.FAILED,
                'metadata': {'failure_message': msg, 'error_traceback': traceback.format_exc()},
            },
        )
        abort(http.client.BAD_REQUEST, msg)

    except Exception as e:
        # Upload failed: cleanup, update state if desired, and return HTTP error
        if local.upload_manager.has_contents(bundle):
            local.upload_manager.cleanup_existing_contents(bundle)

        msg = "Upload failed: %s" % e

        # The client may not want to finalize the bundle on failure, to keep
        # open the possibility of retrying the upload in the case of transient
        # failure.
        # Workers also use this API endpoint to upload partial contents of
        # running bundles, and they should use finalize_on_failure=0 to avoid
        # letting transient errors during upload fail the bundles prematurely.
        if finalize_on_failure:
            local.model.update_bundle(
                bundle,
                {
                    'state': State.FAILED,
                    'metadata': {'failure_message': msg, 'error_traceback': traceback.format_exc()},
                },
            )

        abort(http.client.INTERNAL_SERVER_ERROR, msg)

    else:
        if finalize_on_success:
            # Upload succeeded: update state
            local.model.update_bundle(bundle, {'state': final_state})
Пример #14
0
def _update_bundle_contents_blob(uuid):
    """
    Update the contents of the given running or uploading bundle.

    Query parameters:
        urls - comma-separated list of URLs from which to fetch data to fill the
               bundle, using this option will ignore any uploaded file data
        git - (optional) 1 if URL should be interpreted as git repos to clone
              or 0 otherwise, default is 0
    OR
        filename - (optional) filename of the uploaded file, used to indicate
                   whether or not it is an archive, default is 'contents'

    Query parameters that are always available:
        unpack - (optional) 1 if the uploaded file should be unpacked if it is
                 an archive, or 0 otherwise, default is 1
        simplify - (optional) 1 if the uploaded file should be 'simplified' if
                   it is an archive, or 0 otherwise, default is 1
                   (See UploadManager for full explanation of 'simplification')
        finalize_on_failure - (optional) True ('1') if bundle state should be set
                              to 'failed' in the case of a failure during upload,
                              or False ('0') if the bundle state should not
                              change on failure. Default is False.
        state_on_success - (optional) Update the bundle state to this state if
                           the upload completes successfully. Must be either
                           'ready' or 'failed'. Default is 'ready'.
    """
    check_bundles_have_all_permission(local.model, request.user, [uuid])
    bundle = local.model.get_bundle(uuid)
    if bundle.state in State.FINAL_STATES:
        abort(httplib.FORBIDDEN,
              'Contents cannot be modified, bundle already finalized.')

    # Get and validate query parameters
    finalize_on_failure = query_get_bool('finalize_on_failure', default=False)
    final_state = request.query.get('state_on_success', default=State.READY)
    if final_state not in State.FINAL_STATES:
        abort(
            httplib.BAD_REQUEST, 'state_on_success must be one of %s' %
            '|'.join(State.FINAL_STATES))

    # If this bundle already has data, remove it.
    if local.upload_manager.has_contents(bundle):
        local.upload_manager.cleanup_existing_contents(bundle)

    # Store the data.
    try:
        if request.query.urls:
            sources = query_get_list('urls')
        else:
            filename = request.query.get('filename', default='contents')
            sources = [(filename, request['wsgi.input'])]

        local.upload_manager.upload_to_bundle_store(
            bundle,
            sources=sources,
            follow_symlinks=False,
            exclude_patterns=None,
            remove_sources=False,
            git=query_get_bool('git', default=False),
            unpack=query_get_bool('unpack', default=True),
            simplify_archives=query_get_bool('simplify', default=True))

        local.upload_manager.update_metadata_and_save(bundle, new_bundle=False)

    except Exception as e:
        # Upload failed: cleanup, update state if desired, and return HTTP error
        if local.upload_manager.has_contents(bundle):
            local.upload_manager.cleanup_existing_contents(bundle)

        msg = "Upload failed: %s" % e

        # The client may not want to finalize the bundle on failure, to keep
        # open the possibility of retrying the upload in the case of transient
        # failure.
        # Workers also use this API endpoint to upload partial contents of
        # running bundles, and they should use finalize_on_failure=0 to avoid
        # letting transient errors during upload fail the bundles prematurely.
        if finalize_on_failure:
            local.model.update_bundle(bundle, {
                'state': State.FAILED,
                'metadata': {
                    'failure_message': msg
                },
            })

        abort(httplib.INTERNAL_SERVER_ERROR, msg)

    else:
        # Upload succeeded: update state
        local.model.update_bundle(bundle, {'state': final_state})
Пример #15
0
def _create_bundles():
    """
    Bulk create bundles.

    |worksheet_uuid| - The parent worksheet of the bundle, add to this worksheet
                       if not detached or shadowing another bundle. Also used
                       to inherit permissions.
    |shadow| - the uuid of the bundle to shadow
    |detached| - True ('1') if should not add new bundle to any worksheet,
                 or False ('0') otherwise. Default is False.
    |wait_for_upload| - True ('1') if the bundle state should be initialized to
                        UPLOADING regardless of the bundle type, or False ('0')
                        otherwise. This prevents run bundles that are being
                        copied from another instance from being run by the
                        BundleManager. Default is False.
    """
    worksheet_uuid = request.query.get('worksheet')
    shadow_parent_uuid = request.query.get('shadow')
    detached = query_get_bool('detached', default=False)
    if worksheet_uuid is None:
        abort(
            httplib.BAD_REQUEST, "Parent worksheet id must be specified as"
            "'worksheet' query parameter")

    # Deserialize bundle fields
    bundles = BundleSchema(
        strict=True,
        many=True,
        dump_only=BUNDLE_CREATE_RESTRICTED_FIELDS,
    ).load(request.json).data

    # Check for all necessary permissions
    worksheet = local.model.get_worksheet(worksheet_uuid, fetch_items=False)
    check_worksheet_has_all_permission(local.model, request.user, worksheet)
    worksheet_util.check_worksheet_not_frozen(worksheet)
    request.user.check_quota(need_time=True, need_disk=True)

    created_uuids = []
    for bundle in bundles:
        # Prep bundle info for saving into database
        # Unfortunately cannot use the `construct` methods because they don't
        # provide a uniform interface for constructing bundles for all types
        # Hopefully this can all be unified after REST migration is complete
        bundle_uuid = bundle.setdefault('uuid', spec_util.generate_uuid())
        created_uuids.append(bundle_uuid)
        bundle_class = get_bundle_subclass(bundle['bundle_type'])
        bundle['owner_id'] = request.user.user_id
        bundle['state'] = (
            State.UPLOADING if issubclass(bundle_class, UploadedBundle)
            or query_get_bool('wait_for_upload', False) else State.CREATED)
        bundle.setdefault('metadata', {})['created'] = int(time.time())
        for dep in bundle.setdefault('dependencies', []):
            dep['child_uuid'] = bundle_uuid

        # Create bundle object
        bundle = bundle_class(bundle, strict=False)

        # Save bundle into model
        local.model.save_bundle(bundle)

        # Inherit worksheet permissions
        group_permissions = local.model.get_group_worksheet_permissions(
            request.user.user_id, worksheet_uuid)
        set_bundle_permissions([{
            'object_uuid': bundle_uuid,
            'group_uuid': p['group_uuid'],
            'permission': p['permission'],
        } for p in group_permissions])

        # Add as item to worksheet
        if not detached:
            if shadow_parent_uuid is None:
                local.model.add_worksheet_item(
                    worksheet_uuid, worksheet_util.bundle_item(bundle_uuid))
            else:
                local.model.add_shadow_worksheet_items(shadow_parent_uuid,
                                                       bundle_uuid)

    # Get created bundles
    bundles_dict = get_bundle_infos(created_uuids)

    # Return bundles in original order
    bundles = [bundles_dict[uuid] for uuid in created_uuids]
    return BundleSchema(many=True).dump(bundles).data