Example #1
0
    def post(self, request, collection):
        """Create a new collection

        View to create a new collection and an associated bosskey for that collection
        Args:
            request: DRF Request object
            collection : Collection name
        Returns:
            Collection

        """
        col_data = request.data.copy()
        col_data['name'] = collection

        # Save the object
        serializer = CollectionSerializer(data=col_data)
        if serializer.is_valid():
            serializer.save(creator=self.request.user)
            collection_obj = Collection.objects.get(name=col_data['name'])

            # Assign permissions to the users primary group and admin group
            BossPermissionManager.add_permissions_primary_group(self.request.user, collection_obj)
            BossPermissionManager.add_permissions_admin_group(collection_obj)

            lookup_key = str(collection_obj.pk)
            boss_key = collection_obj.name
            LookUpKey.add_lookup(lookup_key, boss_key, collection_obj.name)

            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
Example #2
0
    def delete(self, request, collection, experiment, channel_layer):
        """
        Delete a Channel  or a Layer
        Args:
            request: DRF Request object
            collection: Collection name
            experiment: Experiment name
            channel_layer: Channel or Layer name

        Returns :
            Http status
        """
        try:
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj)
            channel_layer_obj = ChannelLayer.objects.get(name=channel_layer, experiment=experiment_obj)

            if request.user.has_perm("delete", channel_layer_obj):
                channel_layer_obj.delete()

                # delete the lookup key for this object
                LookUpKey.delete_lookup_key(collection, experiment, channel_layer)
                return HttpResponse(status=204)
            else:
                return BossPermissionError('delete', channel_layer)

        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except ChannelLayer.DoesNotExist:
            return BossResourceNotFoundError(channel_layer)
        except ProtectedError:
            return BossHTTPError("Cannot delete {}. It has layers that reference it.".format(channel_layer),
                                 ErrorCodes.INTEGRITY_ERROR)
Example #3
0
    def delete(self, request, collection, experiment):
        """
        Delete a experiment
        Args:
            request: DRF Request object
            collection:  Name of collection
            experiment: Experiment name to delete
        Returns:
            Http status
        """
        try:
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj)
            if request.user.has_perm("delete", experiment_obj):
                experiment_obj.delete()
                # # get the lookup key and delete all the meta data for this object
                # bosskey = collection + '&' + experiment
                # lkey = LookUpKey.get_lookup_key(bosskey)
                # mdb = MetaDB()
                # mdb.delete_meta_keys(lkey)

                # delete the lookup key for this object
                LookUpKey.delete_lookup_key(collection, experiment, None)
                return HttpResponse(status=204)
            else:
                return BossPermissionError('delete', experiment)
        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except ProtectedError:
            return BossHTTPError("Cannot delete {}. It has channels or layers that reference "
                                      "it.".format(experiment), ErrorCodes.INTEGRITY_ERROR)
Example #4
0
    def test_update_lookup_experiment(self):
        """
        On an experiment rename, make sure only resources that are children of
        the experiment are changed.

        This is a regression test.
        """
        new_exp_name = 'new_exp'
        collection = 'col2'
        orig_exp_name = 'exp1'

        collection_obj = Collection.objects.get(name=collection)
        experiment_obj = Experiment.objects.get(name=orig_exp_name, collection=collection_obj)
        lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk)
        boss_key = collection_obj.name + '&' + new_exp_name

        # Method under test.
        LookUpKey.update_lookup_experiment(
                lookup_key, boss_key, collection_obj.name, new_exp_name)

        # There should be still 5 rows in the lookup table with orig_exp_name
        # as the experiment name under col1.  There should be the experiment
        # and 4 channels.
        all_lookup_objs = BossLookup.objects.filter(experiment_name=orig_exp_name)
        self.assertEqual(5, len(all_lookup_objs))
Example #5
0
    def put(self, request, collection):
        """
        Update a collection using django rest framework
        Args:
            request: DRF Request object
            collection: Collection name
        Returns:
            Collection
        """
        try:
            # Check if the object exists
            collection_obj = Collection.objects.get(name=collection)

            # Check for permissions
            if request.user.has_perm("update", collection_obj):
                serializer = CollectionSerializer(collection_obj, data=request.data, partial=True)
                if serializer.is_valid():
                    serializer.save()

                    # update the lookup key if you update the name
                    if 'name' in request.data and request.data['name'] != collection:
                        lookup_key = str(collection_obj.pk)
                        boss_key = request.data['name']
                        LookUpKey.update_lookup(lookup_key, boss_key, request.data['name'])

                    return Response(serializer.data)
                else:
                    return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('update', collection)
        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
Example #6
0
    def test_update_lookup_experiment(self):
        """
        On an experiment rename, make sure only resources that are children of
        the experiment are changed.

        This is a regression test.
        """
        new_exp_name = 'new_exp'
        collection = 'col2'
        orig_exp_name = 'exp1'

        collection_obj = Collection.objects.get(name=collection)
        experiment_obj = Experiment.objects.get(name=orig_exp_name,
                                                collection=collection_obj)
        lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk)
        boss_key = collection_obj.name + '&' + new_exp_name

        # Method under test.
        LookUpKey.update_lookup_experiment(lookup_key, boss_key,
                                           collection_obj.name, new_exp_name)

        # There should be still 5 rows in the lookup table with orig_exp_name
        # as the experiment name under col1.  There should be the experiment
        # and 4 channels.
        all_lookup_objs = BossLookup.objects.filter(
            experiment_name=orig_exp_name)
        self.assertEqual(5, len(all_lookup_objs))
Example #7
0
    def post(self, request, collection, experiment):
        """Create a new experiment

        View to create a new experiment and an associated bosskey for that experiment
        Args:
            request: DRF Request object
            collection : Collection name
            experiment : Experiment name
        Returns:
            Experiment

        """
        experiment_data = request.data.copy()
        experiment_data['name'] = experiment
        try:
            # Get the collection information
            collection_obj = Collection.objects.get(name=collection)

            if request.user.has_perm("add", collection_obj):
                experiment_data['collection'] = collection_obj.pk

                # Update the coordinate frame
                if 'coord_frame' not in experiment_data:
                    return BossHTTPError("This request requires a valid coordinate frame",
                                         ErrorCodes.INVALID_POST_ARGUMENT)

                coord_frame_obj = CoordinateFrame.objects.get(name=experiment_data['coord_frame'])
                experiment_data['coord_frame'] = coord_frame_obj.pk

                serializer = ExperimentSerializer(data=experiment_data)
                if serializer.is_valid():
                    serializer.save(creator=self.request.user)
                    experiment_obj = Experiment.objects.get(name=experiment_data['name'], collection=collection_obj)

                    # Assign permissions to the users primary group and admin group
                    BossPermissionManager.add_permissions_primary_group(self.request.user, experiment_obj)
                    BossPermissionManager.add_permissions_admin_group(experiment_obj)

                    lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk)
                    boss_key = collection_obj.name + '&' + experiment_obj.name
                    LookUpKey.add_lookup(lookup_key, boss_key, collection_obj.name, experiment_obj.name)

                    serializer = ExperimentReadSerializer(experiment_obj)
                    return Response(serializer.data, status=status.HTTP_201_CREATED)
                else:
                    return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('add', collection)
        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except CoordinateFrame.DoesNotExist:
            return BossResourceNotFoundError(experiment_data['coord_frame'])
        except ValueError:
            return BossHTTPError("Value Error.Collection id {} in post data needs to be an integer"
                                 .format(experiment_data['collection']), ErrorCodes.TYPE_ERROR)
Example #8
0
    def put(self, request, collection, experiment):
        """
        Update a experiment using django rest framework

        Args:
            request: DRF Request object
            collection: Collection name
            experiment : Experiment name for the new experiment

        Returns:
            Experiment
        """
        try:
            # Check if the object exists
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment,
                                                    collection=collection_obj)
            if request.user.has_perm("update", experiment_obj):
                serializer = ExperimentUpdateSerializer(experiment_obj,
                                                        data=request.data,
                                                        partial=True)
                if serializer.is_valid():
                    serializer.save()

                    # update the lookup key if you update the name
                    if 'name' in request.data and request.data[
                            'name'] != experiment:
                        lookup_key = str(collection_obj.pk) + '&' + str(
                            experiment_obj.pk)
                        boss_key = collection_obj.name + '&' + request.data[
                            'name']
                        LookUpKey.update_lookup_experiment(
                            lookup_key, boss_key, collection_obj.name,
                            request.data['name'])

                    # return the object back to the user
                    experiment = serializer.data['name']
                    experiment_obj = Experiment.objects.get(
                        name=experiment, collection=collection_obj)
                    serializer = ExperimentReadSerializer(experiment_obj)
                    return Response(serializer.data)
                else:
                    return BossHTTPError("{}".format(serializer.errors),
                                         ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('update', experiment)

        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except BossError as err:
            return err.to_http()
Example #9
0
    def invoke_ingest_lambda(self, ingest_job, num_invokes=1):
        """Method to trigger extra lambda functions to make sure all the ingest jobs that are actually fully populated
        kick through

        Args:
            ingest_job: Ingest job object
            num_invokes(int): number of invocations to fire

        Returns:

        """
        bosskey = ingest_job.collection + CONNECTER + ingest_job.experiment + CONNECTER + ingest_job.channel
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')
        project_info = [col_id, exp_id, ch_id]
        fake_chunk_key = (BossBackend(self.config)).encode_chunk_key(
            16, project_info, ingest_job.resolution, 0, 0, 0, 0)

        event = {
            "ingest_job": ingest_job.id,
            "chunk_key": fake_chunk_key,
            "lambda-name": "ingest"
        }

        # Invoke Ingest lambda functions
        lambda_client = boto3.client('lambda',
                                     region_name=bossutils.aws.get_region())
        for _ in range(0, num_invokes):
            lambda_client.invoke(FunctionName=INGEST_LAMBDA,
                                 InvocationType='Event',
                                 Payload=json.dumps(event).encode())
Example #10
0
    def invoke_ingest_lambda(self, ingest_job, num_invokes=1):
        """Method to trigger extra lambda functions to make sure all the ingest jobs that are actually fully populated
        kick through

        Args:
            ingest_job: Ingest job object
            num_invokes(int): number of invocations to fire

        Returns:

        """
        bosskey = ingest_job.collection + CONNECTOR + ingest_job.experiment + CONNECTOR + ingest_job.channel
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')
        project_info = [col_id, exp_id, ch_id]
        fake_chunk_key = (BossBackend(self.config)).encode_chunk_key(16, project_info,
                                                                     ingest_job.resolution,
                                                                     0, 0, 0, 0)

        event = {"ingest_job": ingest_job.id,
                 "chunk_key": fake_chunk_key,
                 "function-name": INGEST_LAMBDA,
                 "lambda-name": "ingest"}

        # Invoke Ingest lambda functions
        lambda_client = boto3.client('lambda', region_name=bossutils.aws.get_region())
        for _ in range(0, num_invokes):
            lambda_client.invoke(FunctionName=INGEST_LAMBDA,
                                 InvocationType='Event',
                                 Payload=json.dumps(event).encode())
Example #11
0
    def put(self, request, collection, experiment, channel_layer):
        """
        Update new Channel or Layer
        Args:
            request: DRF Request object
            collection: Collection name
            experiment: Experiment name
            channel_layer: Channel or Layer name

        Returns :
            ChannelLayer
        """
        channel_layer_data = request.data.copy()
        if 'is_channel' in channel_layer_data:
            channel_layer_data['is_channel'] = self.get_bool(channel_layer_data['is_channel'])

        try:
            # Check if the object exists
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj)
            channel_layer_obj = ChannelLayer.objects.get(name=channel_layer, experiment=experiment_obj)
            if request.user.has_perm("update", channel_layer_obj):
                serializer = ChannelLayerSerializer(channel_layer_obj, data=request.data, partial=True)
                if serializer.is_valid():
                    serializer.save()
                    # update the lookup key if you update the name
                    if 'name' in request.data and request.data['name'] != channel_layer:
                        lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk) + '&' \
                                     + str(channel_layer_obj.pk)
                        boss_key = collection_obj.name + '&' + experiment_obj.name + '&' + request.data['name']
                        LookUpKey.update_lookup(lookup_key, boss_key, collection_obj.name,  experiment_obj.name,
                                                request.data['name'])

                    return Response(serializer.data)
                else:
                    return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('update', channel_layer)

        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except ChannelLayer.DoesNotExist:
            return BossResourceNotFoundError(channel_layer)
Example #12
0
    def _generate_upload_queue_args(self, ingest_job):
        """
        Generate dictionary to include in messages placed in the tile upload queue.
        
        Args:
            ingest_job (IngestJob):

        Returns:
            (dict)

        Raises:
            (BossError): If ingest_job.ingest_type invalid.
        """

        bosskey = ingest_job.collection + CONNECTOR + ingest_job.experiment + CONNECTOR + ingest_job.channel
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')

        args = {
            'job_id': ingest_job.id,
            'upload_queue': ingest_job.upload_queue,
            'ingest_queue': ingest_job.ingest_queue,

            'resolution': ingest_job.resolution,
            'project_info': lookup_key.split(CONNECTOR),
            'ingest_type': ingest_job.ingest_type,

            't_start': ingest_job.t_start,
            't_stop': ingest_job.t_stop,
            't_tile_size': 1,

            'x_start': ingest_job.x_start,
            'x_stop': ingest_job.x_stop,
            'x_tile_size': ingest_job.tile_size_x,

            'y_start': ingest_job.y_start,
            'y_stop': ingest_job.y_stop,
            'y_tile_size': ingest_job.tile_size_y,

            'z_start': ingest_job.z_start,
            'z_stop': ingest_job.z_stop,
            'z_tile_size': 1
        }


        if ingest_job.ingest_type == IngestJob.TILE_INGEST:
            # Always the Boss cuboid z size for tile jobs.
            args['z_chunk_size'] = 16
        elif ingest_job.ingest_type == IngestJob.VOLUMETRIC_INGEST:
            # tile_size_* holds the chunk size dimensions for volumetric jobs.
            args['z_chunk_size'] = ingest_job.tile_size_z
        else:
            raise BossError(
                "Ingest job's ingest_type has invalid value: {}".format(
                    self.job.ingest_type), ErrorCodes.UNABLE_TO_VALIDATE)
        return args
Example #13
0
    def _generate_upload_queue_args(self, ingest_job):
        """
        Generate dictionary to include in messages placed in the tile upload queue.
        
        Args:
            ingest_job (IngestJob):

        Returns:
            (dict)

        Raises:
            (BossError): If ingest_job.ingest_type invalid.
        """

        bosskey = ingest_job.collection + CONNECTOR + ingest_job.experiment + CONNECTOR + ingest_job.channel
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')

        args = {
            'job_id': ingest_job.id,
            'upload_queue': ingest_job.upload_queue,
            'ingest_queue': ingest_job.ingest_queue,

            'resolution': ingest_job.resolution,
            'project_info': lookup_key.split(CONNECTOR),
            'ingest_type': ingest_job.ingest_type,

            't_start': ingest_job.t_start,
            't_stop': ingest_job.t_stop,
            't_tile_size': 1,

            'x_start': ingest_job.x_start,
            'x_stop': ingest_job.x_stop,
            'x_tile_size': ingest_job.tile_size_x,

            'y_start': ingest_job.y_start,
            'y_stop': ingest_job.y_stop,
            'y_tile_size': ingest_job.tile_size_y,

            'z_start': ingest_job.z_start,
            'z_stop': ingest_job.z_stop,
            'z_tile_size': 1
        }


        if ingest_job.ingest_type == IngestJob.TILE_INGEST:
            # Always the Boss cuboid z size for tile jobs.
            args['z_chunk_size'] = 16
        elif ingest_job.ingest_type == IngestJob.VOLUMETRIC_INGEST:
            # tile_size_* holds the chunk size dimensions for volumetric jobs.
            args['z_chunk_size'] = ingest_job.tile_size_z
        else:
            raise BossError(
                "Ingest job's ingest_type has invalid value: {}".format(
                    self.job.ingest_type), ErrorCodes.UNABLE_TO_VALIDATE)
        return args
Example #14
0
    def populate_upload_queue(self):
        """Execute the populate_upload_queue Step Function

        Returns:
            string: ARN of the StepFunction Execution started

        Raises:
            BossError : if there is no valid ingest job
        """

        if self.job is None:
            raise BossError(
                "Unable to generate upload tasks for the ingest service. Please specify a ingest job",
                ErrorCodes.UNABLE_TO_VALIDATE)

        ingest_job = self.job

        bosskey = ingest_job.collection + CONNECTER + ingest_job.experiment + CONNECTER + ingest_job.channel
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')
        project_info = [col_id, exp_id, ch_id]

        # TODO DP ???: create IngestJob method that creates the StepFunction arguments?
        args = {
            'upload_sfn': config['sfn']['upload_sfn'],
            'job_id': ingest_job.id,
            'upload_queue': ingest_job.upload_queue,
            'ingest_queue': ingest_job.ingest_queue,
            'resolution': ingest_job.resolution,
            'project_info': lookup_key.split(CONNECTER),
            't_start': ingest_job.t_start,
            't_stop': ingest_job.t_stop,
            't_tile_size': 1,
            'x_start': ingest_job.x_start,
            'x_stop': ingest_job.x_stop,
            'x_tile_size': ingest_job.tile_size_x,
            'y_start': ingest_job.y_start,
            'y_stop': ingest_job.y_stop,
            'y_tile_size': ingest_job.tile_size_y,
            'z_start': ingest_job.z_start,
            'z_stop': ingest_job.z_stop,
            'z_tile_size': 16,
        }

        session = bossutils.aws.get_session()
        populate_sfn = config['sfn']['populate_upload_queue']
        arn = bossutils.aws.sfn_execute(session, populate_sfn, args)

        return arn
Example #15
0
    def generate_upload_tasks(self, job_id=None):
        """
        Generate upload tasks for the ingest job. This creates once task for each tile that has to be uploaded in the
        ingest queue

        Args:
            job_id: Job id of the ingest queue. If not included this takes the current ingest job

        Returns:
            None
        Raises:
            BossError : if there is no valid ingest job

        """

        if job_id is None and self.job is None:
            raise BossError(
                "Unable to generate upload tasks for the ingest service. Please specify a ingest job",
                ErrorCodes.UNABLE_TO_VALIDATE)
        elif job_id:
            # Using the job id to get the job
            try:
                ingest_job = IngestJob.objects.get(id=job_id)
            except IngestJob.DoesNotExist:
                raise BossError(
                    "Ingest job with id {} does not exist".format(job_id),
                    ErrorCodes.RESOURCE_NOT_FOUND)
        else:
            ingest_job = self.job

        # Generate upload tasks for the ingest job
        # Get the project information
        bosskey = ingest_job.collection + CONNECTER + ingest_job.experiment + CONNECTER + ingest_job.channel
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')
        project_info = [col_id, exp_id, ch_id]

        # Batch messages and write to file
        base_file_name = 'tasks_' + lookup_key + '_' + str(ingest_job.id)
        self.file_index = 0

        # open file
        f = io.StringIO()
        header = {
            'job_id': ingest_job.id,
            'upload_queue_url': ingest_job.upload_queue,
            'ingest_queue_url': ingest_job.ingest_queue
        }
        f.write(json.dumps(header))
        f.write('\n')
        num_msg_per_file = 0

        for time_step in range(ingest_job.t_start, ingest_job.t_stop, 1):
            # For each time step, compute the chunks and tile keys

            for z in range(ingest_job.z_start, ingest_job.z_stop, 16):
                for y in range(ingest_job.y_start, ingest_job.y_stop,
                               ingest_job.tile_size_y):
                    for x in range(ingest_job.x_start, ingest_job.x_stop,
                                   ingest_job.tile_size_x):

                        # compute the chunk indices
                        chunk_x = int(x / ingest_job.tile_size_x)
                        chunk_y = int(y / ingest_job.tile_size_y)
                        chunk_z = int(z / 16)

                        # Compute the number of tiles in the chunk
                        if ingest_job.z_stop - z >= 16:
                            num_of_tiles = 16
                        else:
                            num_of_tiles = ingest_job.z_stop - z

                        # Generate the chunk key
                        chunk_key = (BossBackend(
                            self.config)).encode_chunk_key(
                                num_of_tiles, project_info,
                                ingest_job.resolution, chunk_x, chunk_y,
                                chunk_z, time_step)

                        self.num_of_chunks += 1

                        # get the tiles keys for this chunk
                        for tile in range(z, z + num_of_tiles):
                            # get the tile key
                            tile_key = (BossBackend(
                                self.config)).encode_tile_key(
                                    project_info, ingest_job.resolution,
                                    chunk_x, chunk_y, tile, time_step)
                            self.count_of_tiles += 1

                            # Generate the upload task msg
                            msg = chunk_key + ',' + tile_key + '\n'
                            f.write(msg)
                            num_msg_per_file += 1

                            # if there are 10 messages in the batch send it to the upload queue.
                            if num_msg_per_file == MAX_NUM_MSG_PER_FILE:
                                fname = base_file_name + '_' + str(
                                    self.file_index + 1) + '.txt'
                                self.upload_task_file(fname, f.getvalue())
                                self.file_index += 1
                                f.close()
                                # status = self.send_upload_message_batch(batch_msg)

                                fname = base_file_name + '_' + str(
                                    self.file_index + 1) + '.txt'
                                f = io.StringIO()
                                header = {
                                    'job_id': ingest_job.id,
                                    'upload_queue_url':
                                    ingest_job.upload_queue,
                                    'ingest_queue_url': ingest_job.ingest_queue
                                }
                                f.write(json.dumps(header))
                                f.write('\n')
                                num_msg_per_file = 0

        # Edge case: the last batch size maybe smaller than 10
        if num_msg_per_file != 0:
            fname = base_file_name + '_' + str(self.file_index + 1) + '.txt'
            self.upload_task_file(fname, f.getvalue())
            f.close()
            self.file_index += 1
            num_msg_per_file = 0

        # Update status
        self.job.tile_count = self.count_of_tiles
        self.job.save()
Example #16
0
    def generate_upload_tasks(self, job_id=None):
        """

        Args:
            job_id:

        Returns:

        """

        if job_id is None and self.job is None:
            raise BossError(
                "Unable to generate upload tasks for the ingest service. Please specify a ingest job",
                ErrorCodes.UNABLE_TO_VALIDATE)
        elif job_id:
            # Using the job id to get the job
            try:
                ingest_job = IngestJob.objects.get(id=job_id)
            except IngestJob.DoesNotExist:
                raise BossError(
                    "Ingest job with id {} does not exist".format(job_id),
                    ErrorCodes.RESOURCE_NOT_FOUND)
        else:
            ingest_job = self.job

        # Generate upload tasks for the ingest job
        # Get the project information
        bosskey = ingest_job.collection + CONNECTER + ingest_job.experiment + CONNECTER + ingest_job.channel_layer
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')
        project_info = [col_id, exp_id, ch_id]

        for time_step in range(ingest_job.t_start, ingest_job.t_stop, 1):
            # For each time step, compute the chunks and tile keys

            for z in range(ingest_job.z_start, ingest_job.z_stop, 16):
                for y in range(ingest_job.y_start, ingest_job.y_stop,
                               ingest_job.tile_size_y):
                    for x in range(ingest_job.x_start, ingest_job.x_stop,
                                   ingest_job.tile_size_x):

                        # compute the chunk indices
                        chunk_x = int(x / ingest_job.tile_size_x)
                        chunk_y = int(y / ingest_job.tile_size_y)
                        chunk_z = int(z / 16)

                        # Compute the number of tiles in the chunk
                        if ingest_job.z_stop - z >= 16:
                            num_of_tiles = 16
                        else:
                            num_of_tiles = ingest_job.z_stop - z

                        # Generate the chunk key
                        chunk_key = (BossBackend(
                            self.config)).encode_chunk_key(
                                num_of_tiles, project_info,
                                ingest_job.resolution, chunk_x, chunk_y,
                                chunk_z, time_step)
                        # get the tiles keys for this chunk
                        for tile in range(0, num_of_tiles):
                            # get the tile key
                            tile_key = (BossBackend(
                                self.config)).encode_tile_key(
                                    project_info, ingest_job.resolution,
                                    chunk_x, chunk_y, tile, time_step)

                            # Generate the upload task msg
                            msg = self.create_upload_task_message(
                                ingest_job.id, chunk_key, tile_key,
                                ingest_job.upload_queue,
                                ingest_job.ingest_queue)

                            # Upload the message
                            self.send_upload_task_message(msg)
Example #17
0
    def post(self, request, collection, experiment, channel_layer):
        """
        Post a new Channel
        Args:
            request: DRF Request object
            collection: Collection name
            experiment: Experiment name
            channel_layer: Channel or Layer name

        Returns :
            ChannelLayer
        """

        channel_layer_data = request.data.copy()
        channel_layer_data['name'] = channel_layer

        try:
            if 'channels' in channel_layer_data:
                channels = dict(channel_layer_data)['channels']
            else:
                channels = []
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj)
            # Check for add permissions
            if request.user.has_perm("add", experiment_obj):
                channel_layer_data['experiment'] = experiment_obj.pk
                channel_layer_data['is_channel'] = self.get_bool(channel_layer_data['is_channel'])

                # layers require at least 1 channel
                if (channel_layer_data['is_channel'] is False) and (len(channels) == 0):
                    return BossHTTPError("Invalid Request.Please specify a valid channel for the layer",
                                         ErrorCodes.INVALID_POST_ARGUMENT)

                serializer = ChannelLayerSerializer(data=channel_layer_data)
                if serializer.is_valid():
                    serializer.save(creator=self.request.user)
                    channel_layer_obj = ChannelLayer.objects.get(name=channel_layer_data['name'],
                                                                 experiment=experiment_obj)

                    # Layer?
                    if not channel_layer_obj.is_channel:
                        # Layers must map to at least 1 channel
                        for channel_id in channels:
                            # Is this a valid channel?
                            channel_obj = ChannelLayer.objects.get(pk=channel_id)
                            if channel_obj:
                                channel_layer_map = {'channel': channel_id, 'layer': channel_layer_obj.pk}
                                map_serializer = ChannelLayerMapSerializer(data=channel_layer_map)
                                if map_serializer.is_valid():
                                    map_serializer.save()

                    # Assign permissions to the users primary group
                    BossPermissionManager.add_permissions_primary_group(self.request.user, channel_layer_obj)

                    # Add Lookup key
                    lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk) + '&' + str(channel_layer_obj.pk)
                    boss_key = collection_obj.name + '&' + experiment_obj.name + '&' + channel_layer_obj.name
                    LookUpKey.add_lookup(lookup_key, boss_key, collection_obj.name, experiment_obj.name,
                                         channel_layer_obj.name)

                    return Response(serializer.data, status=status.HTTP_201_CREATED)
                else:
                    return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('add', experiment)
        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except ChannelLayer.DoesNotExist:
            return BossResourceNotFoundError(channel_layer)
        except ValueError:
            return BossHTTPError("Value Error in post data", ErrorCodes.TYPE_ERROR)
Example #18
0
    def put(self, request, collection, experiment, channel):
        """
        Update new Channel
        Args:
            request: DRF Request object
            collection: Collection name
            experiment: Experiment name
            channel: Channel name

        Returns :
            Channel
        """
        if 'name' in request.data:
            channel_name = request.data['name']
        else:
            channel_name = channel
        try:
            # Check if the object exists
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj)
            channel_obj = Channel.objects.get(name=channel, experiment=experiment_obj)

            if request.user.has_perm("update", channel_obj):

                # The source and related channels are names and need to be removed from the dict before serialization
                source_channels = request.data.pop('sources', [])
                related_channels = request.data.pop('related', [])

                # Validate the source and related channels if they are incuded
                channels = self.validate_source_related_channels(experiment_obj, source_channels, related_channels)
                source_channels_objs = channels[0]
                related_channels_objs = channels[1]

                serializer = ChannelUpdateSerializer(channel_obj, data=request.data, partial=True)
                if serializer.is_valid():
                    serializer.save()

                    channel_obj = Channel.objects.get(name=channel_name, experiment=experiment_obj)
                    # Save source and related channels if they are valid
                    channel_obj = self.update_source_related_channels(channel_obj, experiment_obj, source_channels_objs,
                                                                      related_channels_objs)

                    # update the lookup key if you update the name
                    if 'name' in request.data and request.data['name'] != channel:
                        lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk) + '&' \
                                     + str(channel_obj.pk)
                        boss_key = collection_obj.name + '&' + experiment_obj.name + '&' + request.data['name']
                        LookUpKey.update_lookup(lookup_key, boss_key, collection_obj.name,  experiment_obj.name,
                                                request.data['name'])

                    # return the object back to the user
                    channel = serializer.data['name']
                    channel_obj = Channel.objects.get(name=channel, experiment=experiment_obj)
                    serializer = ChannelReadSerializer(channel_obj)
                    return Response(serializer.data)
                else:
                    return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('update', channel)

        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except Channel.DoesNotExist:
            return BossResourceNotFoundError(channel)
Example #19
0
    def post(self, request, collection, experiment, channel):
        """
        Post a new Channel
        Args:
            request: DRF Request object
            collection: Collection name
            experiment: Experiment name
            channel: Channel name

        Returns :
            Channel
        """

        channel_data = request.data.copy()
        channel_data['name'] = channel

        try:
            # Get the collection and experiment
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj)

            # Check for add permissions
            if request.user.has_perm("add", experiment_obj):
                channel_data['experiment'] = experiment_obj.pk

                # The source and related channels are names and need to be removed from the dict before serialization
                source_channels = channel_data.pop('sources', [])
                related_channels = channel_data.pop('related', [])

                # Source channels have to be included for new annotation channels
                if 'type' in channel_data and channel_data['type'] == 'annotation' and len(source_channels) == 0:
                    return BossHTTPError("Annotation channels require the source channel to be set. "
                                         "Specify a valid source channel in the post", ErrorCodes.INVALID_POST_ARGUMENT)

                # Validate the source and related channels if they are incuded
                channels = self.validate_source_related_channels(experiment_obj, source_channels, related_channels)
                source_channels_objs = channels[0]
                related_channels_objs = channels[1]

                # Validate and create the channel
                serializer = ChannelSerializer(data=channel_data)
                if serializer.is_valid():
                    serializer.save(creator=self.request.user)
                    channel_obj = Channel.objects.get(name=channel_data['name'], experiment=experiment_obj)

                    # Save source and related channels if they are valid
                    channel_obj = self.add_source_related_channels(channel_obj, experiment_obj, source_channels_objs,
                                                                   related_channels_objs)

                    # Assign permissions to the users primary group and admin group
                    BossPermissionManager.add_permissions_primary_group(self.request.user, channel_obj)
                    BossPermissionManager.add_permissions_admin_group(channel_obj)

                    # Add Lookup key
                    lookup_key = str(collection_obj.pk) + '&' + str(experiment_obj.pk) + '&' + str(channel_obj.pk)
                    boss_key = collection_obj.name + '&' + experiment_obj.name + '&' + channel_obj.name
                    LookUpKey.add_lookup(lookup_key, boss_key, collection_obj.name, experiment_obj.name,
                                         channel_obj.name)

                    serializer = ChannelReadSerializer(channel_obj)
                    return Response(serializer.data, status=status.HTTP_201_CREATED)
                else:
                    return BossHTTPError("{}".format(serializer.errors), ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('add', experiment)
        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except Channel.DoesNotExist:
            return BossResourceNotFoundError(channel)
        except BossError as err:
            return err.to_http()
        except ValueError:
            return BossHTTPError("Value Error in post data", ErrorCodes.TYPE_ERROR)
Example #20
0
    def post(self, request, collection, experiment, channel):
        """
        Post a new Channel
        Args:
            request: DRF Request object
            collection: Collection name
            experiment: Experiment name
            channel: Channel name

        Returns :
            Channel
        """

        channel_data = request.data.copy()
        channel_data['name'] = channel

        try:
            is_admin = BossPermissionManager.is_in_group(
                request.user, ADMIN_GRP)
            if 'bucket' in channel_data and channel_data[
                    'bucket'] and not is_admin:
                return BossHTTPError('Only admins can set bucket name',
                                     ErrorCodes.MISSING_PERMISSION)

            if 'cv_path' in channel_data and channel_data[
                    'cv_path'] and not is_admin:
                return BossHTTPError('Only admins can set cv_path',
                                     ErrorCodes.MISSING_PERMISSION)

            # Get the collection and experiment
            collection_obj = Collection.objects.get(name=collection)
            experiment_obj = Experiment.objects.get(name=experiment,
                                                    collection=collection_obj)

            # Check for add permissions
            if request.user.has_perm("add", experiment_obj):
                channel_data['experiment'] = experiment_obj.pk

                use_cloudvol = channel_data.get(
                    'storage_type', None) == Channel.StorageType.CLOUD_VOLUME
                cv_path = channel_data.get('cv_path', None)
                if use_cloudvol and (cv_path is None or cv_path == ''):
                    channel_data[
                        'cv_path'] = f'/{collection}/{experiment}/{channel}'

                if use_cloudvol:
                    # DX NOTE: For now we assume that cloudvolume channels are downsampled. This means
                    # that the num_hierarchy_levels in the experiment should be limited to the available
                    # mip levels in the cloudvolume layer.
                    channel_data['downsample_status'] = 'DOWNSAMPLED'

                # The source and related channels are names and need to be removed from the dict before serialization
                source_channels = channel_data.pop('sources', [])
                related_channels = channel_data.pop('related', [])

                # TODO: Removed source channel requirement for annotation channels. Future update should allow source channel from
                # different collections.

                # Source channels have to be included for new annotation channels
                # if 'type' in channel_data and channel_data['type'] == 'annotation' and len(source_channels) == 0:
                #     return BossHTTPError("Annotation channels require the source channel to be set. "
                #                          "Specify a valid source channel in the post", ErrorCodes.INVALID_POST_ARGUMENT)

                # Validate the source and related channels if they are incuded
                channels = self.validate_source_related_channels(
                    experiment_obj, source_channels, related_channels)
                source_channels_objs = channels[0]
                related_channels_objs = channels[1]

                # Validate and create the channel
                serializer = ChannelSerializer(data=channel_data)
                if serializer.is_valid():
                    serializer.save(creator=self.request.user)
                    channel_obj = Channel.objects.get(
                        name=channel_data['name'], experiment=experiment_obj)

                    # Save source and related channels if they are valid
                    channel_obj = self.add_source_related_channels(
                        channel_obj, experiment_obj, source_channels_objs,
                        related_channels_objs)

                    # Assign permissions to the users primary group and admin group
                    BossPermissionManager.add_permissions_primary_group(
                        self.request.user, channel_obj)
                    BossPermissionManager.add_permissions_admin_group(
                        channel_obj)

                    # Add Lookup key
                    lookup_key = str(collection_obj.pk) + '&' + str(
                        experiment_obj.pk) + '&' + str(channel_obj.pk)
                    boss_key = collection_obj.name + '&' + experiment_obj.name + '&' + channel_obj.name
                    LookUpKey.add_lookup(lookup_key, boss_key,
                                         collection_obj.name,
                                         experiment_obj.name, channel_obj.name)

                    serializer = ChannelReadSerializer(channel_obj)
                    return Response(serializer.data,
                                    status=status.HTTP_201_CREATED)
                else:
                    return BossHTTPError("{}".format(serializer.errors),
                                         ErrorCodes.INVALID_POST_ARGUMENT)
            else:
                return BossPermissionError('add', experiment)
        except Collection.DoesNotExist:
            return BossResourceNotFoundError(collection)
        except Experiment.DoesNotExist:
            return BossResourceNotFoundError(experiment)
        except Channel.DoesNotExist:
            return BossResourceNotFoundError(channel)
        except BossError as err:
            return err.to_http()
        except ValueError:
            return BossHTTPError("Value Error in post data",
                                 ErrorCodes.TYPE_ERROR)
Example #21
0
    def test_upload_tile_index_table(self):
        """"""
        ingest_mgmr = IngestManager()
        ingest_mgmr.validate_config_file(self.example_config_data)
        ingest_mgmr.validate_properties()
        ingest_mgmr.owner = self.user.pk
        ingest_job = ingest_mgmr.create_ingest_job()
        assert (ingest_job.id is not None)

        # Get the chunks in this job
        # Get the project information
        bosskey = ingest_job.collection + '&' + ingest_job.experiment + '&' + ingest_job.channel_layer
        lookup_key = (LookUpKey.get_lookup_key(bosskey)).lookup_key
        [col_id, exp_id, ch_id] = lookup_key.split('&')
        project_info = [col_id, exp_id, ch_id]
        proj_name = ingest_job.collection + '&' + ingest_job.experiment
        tile_index_db = BossTileIndexDB(proj_name)
        tilebucket = TileBucket(str(col_id) + '&' + str(exp_id))

        for time_step in range(ingest_job.t_start, ingest_job.t_stop, 1):
            # For each time step, compute the chunks and tile keys

            for z in range(ingest_job.z_start, ingest_job.z_stop, 16):
                for y in range(ingest_job.y_start, ingest_job.y_stop,
                               ingest_job.tile_size_y):
                    for x in range(ingest_job.x_start, ingest_job.x_stop,
                                   ingest_job.tile_size_x):

                        # compute the chunk indices
                        chunk_x = int(x / ingest_job.tile_size_x)
                        chunk_y = int(y / ingest_job.tile_size_y)
                        chunk_z = int(z / 16)

                        # Compute the number of tiles in the chunk
                        if ingest_job.z_stop - z >= 16:
                            num_of_tiles = 16
                        else:
                            num_of_tiles = ingest_job.z_stop - z

                        # Generate the chunk key
                        chunk_key = (BossBackend(
                            ingest_mgmr.config)).encode_chunk_key(
                                num_of_tiles, project_info,
                                ingest_job.resolution, chunk_x, chunk_y,
                                chunk_z, time_step)
                        # Upload the chunk to the tile index db
                        tile_index_db.createCuboidEntry(
                            chunk_key, ingest_job.id)
                        key_map = {}
                        for tile in range(0, num_of_tiles):
                            # get the object key and upload it
                            #tile_key = tilebucket.encodeObjectKey(ch_id, ingest_job.resolution,
                            #                              chunk_x, chunk_y, tile, time_step)
                            tile_key = 'fakekey' + str(tile)
                            tile_index_db.markTileAsUploaded(
                                chunk_key, tile_key)

                        # for each chunk key, delete entries from the tile_bucket

        # Check if data has been uploaded
        chunks = list(tile_index_db.getTaskItems(ingest_job.id))
        assert (len(chunks) != 0)

        ingest_mgmr.delete_tiles(ingest_job)
        chunks = list(tile_index_db.getTaskItems(ingest_job.id))
        assert (len(chunks) == 0)