Exemple #1
0
    def patch(self, id):
        """
        Manage Freshmaker event defined by ID. The request must be
        :mimetype:`application/json`.

        Returns the cancelled Freshmaker event as JSON.

        **Sample request**:

        .. sourcecode:: http

            PATCH /api/1/events HTTP/1.1
            Accept: application/json
            Content-Type: application/json

            {
                "action": "cancel"
            }

        :jsonparam string action: Action to do with an Event. Currently only "cancel"
            is supported.
        :statuscode 200: Cancelled event is returned.
        :statuscode 400: Action is missing or is unsupported.
        """
        data = request.get_json(force=True)
        if 'action' not in data:
            return json_error(
                400, "Bad Request", "Missing action in request."
                " Don't know what to do with the event.")

        if data["action"] != "cancel":
            return json_error(400, "Bad Request", "Unsupported action requested.")

        event = models.Event.query.filter_by(id=id).first()
        if not event:
            return json_error(400, "Not Found", "No such event found.")

        if event.requester != g.user.username and not user_has_role("admin"):
            return json_error(
                403, "Forbidden", "You must be an admin to cancel someone else's event.")

        msg = "Event id %s requested for canceling by user %s" % (event.id, g.user.username)
        log.info(msg)

        event.transition(EventState.CANCELED, msg)
        event.builds_transition(
            ArtifactBuildState.CANCELED.value,
            "Build canceled before running on external build system.",
            filters={'state': ArtifactBuildState.PLANNED.value})
        builds_id = event.builds_transition(
            ArtifactBuildState.CANCELED.value, None,
            filters={'state': ArtifactBuildState.BUILD.value})
        db.session.commit()

        data["action"] = self._freshmaker_manage_prefix + data["action"]
        data["event_id"] = event.id
        data["builds_id"] = builds_id
        messaging.publish("manage.eventcancel", data)
        # Return back the JSON representation of Event to client.
        return jsonify(event.json()), 200
    def test_publish(self, Message, AMQProducer):
        fake_msg = {}
        rhmsg_config = {
            'rhmsg': {
                'BROKER_URLS': ['amqps://localhost:5671'],
                'CERT_FILE': '/path/to/cert',
                'KEY_FILE': '/path/to/key',
                'CA_CERT': '/path/to/ca-cert',
                'TOPIC_PREFIX': 'VirtualTopic.eng.freshmaker',
            }
        }
        with patch.object(conf, 'messaging_backends', new=rhmsg_config):
            publish('images.ready', fake_msg)

        AMQProducer.assert_called_with(
            **{
                'urls': ['amqps://localhost:5671'],
                'certificate': '/path/to/cert',
                'private_key': '/path/to/key',
                'trusted_certificates': '/path/to/ca-cert',
            })
        producer = AMQProducer.return_value.__enter__.return_value
        producer.through_topic.assert_called_once_with(
            'VirtualTopic.eng.freshmaker.images.ready')
        producer.send.assert_called_once_with(Message.return_value)
    def test_publish(self, fedmsg_publish):
        fake_msg = {}
        publish('images.ready', fake_msg)

        fedmsg_publish.assert_called_once_with('images.ready',
                                               msg=fake_msg,
                                               modname='freshmaker')
    def test_publish(self, from_fedmsg, work_queue_put):
        fake_msg = {}
        in_memory_config = {'in_memory': {'SERVICE': 'freshmaker'}}

        with patch.object(conf, 'messaging_backends', new=in_memory_config):
            publish('images.ready', fake_msg)

        from_fedmsg.assert_called_once_with('freshmaker.images.ready', {
            'msg_id': '1',
            'msg': fake_msg
        })
        work_queue_put.assert_called_once_with(from_fedmsg.return_value)
Exemple #5
0
    def transition(self, state, state_reason):
        """
        Sets the state and state_reason of this ArtifactBuild.

        :param state: ArtifactBuildState value
        :param state_reason: Reason why this state has been set.
        :return: True/False, whether state was changed
        """
        # Convert state from its possible representation to number.
        state = self.validate_state("state", state)

        # Log the state and state_reason
        if state == ArtifactBuildState.FAILED.value:
            log_fnc = log.error
        else:
            log_fnc = log.info
        log_fnc("Artifact build %r moved to state %s, %r" %
                (self, ArtifactBuildState(state).name, state_reason))

        if self.state == state:
            return False

        self.state = state
        if ArtifactBuildState(state).counter:
            ArtifactBuildState(state).counter.inc()

        self.state_reason = state_reason
        if self.state in [
                ArtifactBuildState.DONE.value, ArtifactBuildState.FAILED.value,
                ArtifactBuildState.CANCELED.value
        ]:
            self.time_completed = datetime.utcnow()

        # For FAILED/CANCELED states, move also all the artifacts depending
        # on this one to FAILED/CANCELED state, because there is no way we
        # can rebuild them.
        if self.state in [
                ArtifactBuildState.FAILED.value,
                ArtifactBuildState.CANCELED.value
        ]:
            for build in self.depending_artifact_builds():
                build.transition(
                    self.state, "Cannot build artifact, because its "
                    "dependency cannot be built.")

        messaging.publish('build.state.changed', self.json())

        return True
Exemple #6
0
    def transition(self, state, state_reason=None):
        """
        Sets the time_done, state, and state_reason of this Event.

        :param state: EventState value
        :param state_reason: Reason why this state has been set.
        :return: True/False, whether state was changed
        """
        # Convert state from its possible representation to number.
        state = self.validate_state("state", state)

        # Update the state reason.
        if state_reason is not None:
            self.state_reason = state_reason

        # Log the state and state_reason
        if state == EventState.FAILED.value:
            log_fnc = log.error
        else:
            log_fnc = log.info
        log_fnc("Event %r moved to state %s, %r" %
                (self, EventState(state).name, state_reason))

        # In case Event is already in the state, return False.
        if self.state == state:
            return False

        self.state = state

        # Log the time done
        if state in [
                EventState.FAILED.value, EventState.COMPLETE.value,
                EventState.SKIPPED.value, EventState.CANCELED.value
        ]:
            self.time_done = datetime.utcnow()

        if EventState(state).counter:
            EventState(state).counter.inc()

        db.session.commit()
        messaging.publish('event.state.changed', self.json())
        messaging.publish('event.state.changed.min', self.json_min())

        return True
    def test_select_backend(self, _in_memory_publish, _rhmsg_publish,
                            _fedmsg_publish):
        fake_msg = {'build': 'n-v-r'}

        mock_messaging_backends = {
            'fedmsg': {
                'publish': _fedmsg_publish
            },
            'rhmsg': {
                'publish': _rhmsg_publish
            },
            'in_memory': {
                'publish': _in_memory_publish
            },
        }
        with patch.dict('freshmaker.messaging._messaging_backends',
                        mock_messaging_backends):
            with patch.object(conf, 'messaging_sender', new='fedmsg'):
                publish('images.ready', fake_msg)
                _fedmsg_publish.assert_called_once_with(
                    'images.ready', fake_msg)

            with patch.object(conf, 'messaging_sender', new='rhmsg'):
                publish('images.ready', fake_msg)
                _rhmsg_publish.assert_called_once_with('images.ready',
                                                       fake_msg)

            with patch.object(conf, 'messaging_sender', new='in_memory'):
                publish('images.ready', fake_msg)
                _in_memory_publish.assert_called_once_with(
                    'images.ready', fake_msg)
Exemple #8
0
    def post(self):
        """
        Trigger Freshmaker async rebuild (a.k.a non-CVE rebuild). The request
        must be :mimetype:`application/json`.

        Returns the newly created Freshmaker event as JSON.

        **Sample request**:

        .. sourcecode:: http

            POST /api/1/async-builds HTTP/1.1
            Accept: application/json
            Content-Type: application/json

            {
                "dist_git_branch": "master",
                "container_images": ["foo-1-1"]
            }

        :jsonparam string dist_git_branch: The name of the branch in dist-git
            to build the container images from. This is a mandatory field.
        :jsonparam list container_images: A list of images to rebuild. They
            might be sharing a parent-child relationship which are then rebuilt
            by Freshmaker in the right order. For example, if images A is parent
            image of B, which is parent image of C, and container_images is
            [A, B, C], Freshmaker will make sure to rebuild all three images,
            in the correct order. It is however possible also to rebuild images
            completely unrelated to each other. This is a mandatory field.
        :jsonparam bool dry_run: When True, the Event will be handled in
            the DRY_RUN mode.
        :jsonparam bool freshmaker_event_id: When set, it defines the event
            which will be used as the dependant event. Successfull builds from
            this event will be reused in the newly created event instead of
            building all the artifacts from scratch. The event should refer
            to an async rebuild event.
        :jsonparam string brew_target: The name of the Brew target. While
            requesting an async rebuild, it should be the same for all the images
            in the list of container_images. This parameter is optional, with
            default value will be pulled from the previous buildContainer task.
        :statuscode 200: A new event was created.
        :statuscode 400: The provided input is invalid.
        """
        error = _validate_rebuild_request(request)
        if error is not None:
            return error

        data = request.get_json(force=True)
        if not all([data.get('dist_git_branch'),
                    data.get('container_images')]):
            return json_error(
                400,
                'Bad Request',
                '"dist_git_branch" and "container_images" are required in the request '
                'for async builds',
            )

        dependent_event = None
        if data.get('freshmaker_event_id'):
            dependent_event = models.Event.get_by_event_id(
                db.session,
                data.get('freshmaker_event_id'),
            )
            async_build_event_type = models.EVENT_TYPES[
                events.FreshmakerAsyncManualBuildEvent]
            if dependent_event.event_type_id != async_build_event_type:
                return json_error(
                    400,
                    'Bad Request',
                    'The event (id={}) is not an async build '
                    'event.'.format(data.get('freshmaker_event_id')),
                )

        # The '-container' string is optional, the user might have omitted it. But we need it to be
        # there for our query. Let's check if it's there, and if it's not, let's add it.
        for i, image in enumerate(data.get('container_images', [])):
            if not image.endswith('-container'):
                data.get('container_images')[i] = f"{image}-container"

        # parse the POST data and generate FreshmakerAsyncManualBuildEvent
        parser = FreshmakerAsyncManualbuildParser()
        db_event = _create_rebuild_event_from_request(db.session, parser,
                                                      request)

        # Forward the POST data (including the msg_id of the database event we
        # added to DB) to backend using UMB messaging. Backend will then
        # re-generate the event and start handling it.
        data["msg_id"] = db_event.message_id

        # add information about requester
        data["requester"] = db_event.requester

        messaging.publish("async.manual.build", data)

        # Return back the JSON representation of Event to client.
        return jsonify(db_event.json()), 200
Exemple #9
0
    def post(self):
        """
        Trigger manual Freshmaker rebuild. The request must be
        :mimetype:`application/json`.

        Returns the newly created Freshmaker event as JSON.

        **Sample request**:

        .. sourcecode:: http

            POST /api/1/builds HTTP/1.1
            Accept: application/json
            Content-Type: application/json

            {
                "errata_id": 12345
            }

        **Sample request**:

        .. sourcecode:: http

            POST /api/1/builds HTTP/1.1
            Accept: application/json
            Content-Type: application/json

            {
                "bundle_images": ["app-bundle-1.0-1.11111"],
            }


        :jsonparam string errata_id: The ID of Errata advisory to rebuild
            artifacts for. If this is not set, freshmaker_event_id must be set.
        :jsonparam list container_images: When set, defines list of NVRs
            of leaf container images which should be rebuild in the
            newly created Event.
        :jsonparam bool dry_run: When True, the Event will be handled in
            the DRY_RUN mode.
        :jsonparam bool freshmaker_event_id: When set, it defines the event
            which will be used as the dependant event. Successfull builds from
            this event will be reused in the newly created event instead of
            building all the artifacts from scratch. If errata_id is not
            provided, it will be inherited from this Freshmaker event.
        :jsonparam list bundle_images: list of images to search Freshmaker
            database for rebuild events with them
        :statuscode 200: A new event was created.
        :statuscode 400: The provided input is invalid.
        """
        error = _validate_rebuild_request(request)
        if error is not None:
            return error

        data = request.get_json(force=True)
        if (not data.get('errata_id') and not data.get('freshmaker_event_id')
                and not data.get('bundle_images')):
            return json_error(
                400,
                'Bad Request',
                'You must at least provide "errata_id" or "freshmaker_event_id"'
                ' or "bundle_images" in the request.',
            )

        dependent_event = None
        if data.get('freshmaker_event_id'):
            dependent_event = models.Event.get_by_event_id(
                db.session,
                data.get('freshmaker_event_id'),
            )
            # requesting a CVE rebuild, the event can not be an async build event which
            # is for non-CVE only
            async_build_event_type = models.EVENT_TYPES[
                events.FreshmakerAsyncManualBuildEvent]
            if dependent_event.event_type_id == async_build_event_type:
                return json_error(
                    400,
                    'Bad Request',
                    'The event (id={}) is an async build event, '
                    'can not be used for this build.'.format(
                        data.get('freshmaker_event_id')),
                )
            if not data.get('errata_id'):
                data['errata_id'] = int(dependent_event.search_key)
            elif int(dependent_event.search_key) != data['errata_id']:
                return json_error(
                    400,
                    'Bad Request',
                    'The provided "errata_id" doesn\'t match the Advisory ID associated with the '
                    'input "freshmaker_event_id".',
                )

        # Use the shared code to parse the POST data and generate right
        # event based on the data. Currently it generates just
        # ManualRebuildWithAdvisoryEvent.
        parser = FreshmakerManualRebuildParser()
        db_event = _create_rebuild_event_from_request(db.session, parser,
                                                      request)

        # Forward the POST data (including the msg_id of the database event we
        # added to DB) to backend using UMB messaging. Backend will then
        # re-generate the event and start handling it.
        data["msg_id"] = db_event.message_id

        # add information about requester
        data["requester"] = db_event.requester

        messaging.publish("manual.rebuild", data)

        # Return back the JSON representation of Event to client.
        return jsonify(db_event.json()), 200