Ejemplo n.º 1
0
def handle_function_service_expiration(ctx, engine):
    """Clean up resources related to expired functions.

    If it's image function, we will rely on the orchestrator itself to do the
    image clean up, e.g. image collection feature in kubernetes.
    """
    context.set_ctx(ctx)
    delta = timedelta(seconds=CONF.engine.function_service_expiration)
    expiry_time = datetime.utcnow() - delta

    results = db_api.get_functions(sort_keys=['updated_at'],
                                   insecure=True,
                                   updated_at={'lte': expiry_time})
    if len(results) == 0:
        return

    for func_db in results:
        if not etcd_util.get_service_url(func_db.id):
            continue

        LOG.info('Deleting service mapping and workers for function %s',
                 func_db.id)

        # Delete resources related to the function
        engine.delete_function(ctx, func_db.id)
        # Delete etcd keys
        etcd_util.delete_function(func_db.id)
Ejemplo n.º 2
0
    def test_update_recurring_job_failed(self):
        job_id = self.create_job(
            self.function_id,
            first_execution_time=datetime.utcnow() + timedelta(hours=1),
            next_execution_time=datetime.utcnow() + timedelta(hours=1),
            pattern='0 */1 * * *',
            status=status.RUNNING,
            count=10).id
        url = '/v1/jobs/%s' % job_id

        # Try to change job type
        resp = self.app.put_json(url, {'pattern': ''}, expect_errors=True)

        self.assertEqual(400, resp.status_int)
        self.assertIn('Can not change job type.', resp.json['faultstring'])

        # Pause the job and try to resume with an invalid next_execution_time
        auth_context.set_ctx(self.ctx)
        self.addCleanup(auth_context.set_ctx, None)
        db_api.update_job(job_id, {'status': status.PAUSED})
        resp = self.app.put_json(
            url, {
                'status': status.RUNNING,
                'next_execution_time':
                str(datetime.utcnow() - timedelta(hours=1))
            },
            expect_errors=True)

        self.assertEqual(400, resp.status_int)
        self.assertIn('Execution time must be at least 1 minute in the future',
                      resp.json['faultstring'])
Ejemplo n.º 3
0
def handle_function_service_expiration(ctx, engine_client, orchestrator):
    context.set_ctx(ctx)

    delta = timedelta(seconds=CONF.engine.function_service_expiration)
    expiry_time = datetime.utcnow() - delta

    results = db_api.get_functions(sort_keys=['updated_at'],
                                   insecure=True,
                                   updated_at={'lte': expiry_time})
    if len(results) == 0:
        return

    for func_db in results:
        if not func_db.service:
            continue

        with db_api.transaction():
            LOG.info('Deleting service mapping and workers for function %s',
                     func_db.id)

            # Delete resources related to the function
            engine_client.delete_function(func_db.id)

            # Delete service mapping and worker records
            db_api.delete_function_service_mapping(func_db.id)
            db_api.delete_function_workers(func_db.id)
Ejemplo n.º 4
0
    def test_invoke_with_function_alias(self, mock_create_execution,
                                        mock_create_context):
        exec_mock = mock_create_execution.return_value
        exec_mock.id = "fake_id"

        db_api.increase_function_version(self.func_id, 0)
        alias_name = self.rand_name(name="alias", prefix=self.prefix)
        body = {
            'function_id': self.func_id,
            'function_version': 1,
            'name': alias_name
        }
        db_api.create_function_alias(**body)
        webhook = self.create_webhook(function_alias=alias_name)

        resp = self.app.post_json('/v1/webhooks/%s/invoke' % webhook.id, {})
        context.set_ctx(self.ctx)

        self.assertEqual(202, resp.status_int)

        params = {
            'function_id': self.func_id,
            'function_version': 1,
            'sync': False,
            'input': json.dumps({}),
            'description': constants.EXECUTION_BY_WEBHOOK % webhook.id
        }
        mock_create_execution.assert_called_once_with(mock.ANY, params)
Ejemplo n.º 5
0
    def test_update_one_shot_job_failed(self):
        job_id = self.create_job(self.function_id,
                                 prefix='TestJobController',
                                 first_execution_time=datetime.utcnow(),
                                 next_execution_time=datetime.utcnow() +
                                 timedelta(hours=1),
                                 status=status.RUNNING,
                                 count=1).id
        url = '/v1/jobs/%s' % job_id

        # Try to change job type
        resp = self.app.put_json(url, {'pattern': '*/1 * * * *'},
                                 expect_errors=True)

        self.assertEqual(400, resp.status_int)
        self.assertIn('Can not change job type.', resp.json['faultstring'])

        # Try to resume job but the execution time is invalid
        auth_context.set_ctx(self.ctx)
        self.addCleanup(auth_context.set_ctx, None)
        db_api.update_job(
            job_id, {
                'next_execution_time': datetime.utcnow() - timedelta(hours=1),
                'status': status.PAUSED
            })
        resp = self.app.put_json(url, {'status': status.RUNNING},
                                 expect_errors=True)

        self.assertEqual(400, resp.status_int)
        self.assertIn('Execution time must be at least 1 minute in the future',
                      resp.json['faultstring'])
Ejemplo n.º 6
0
    def invoke(self, id, **kwargs):
        with db_api.transaction():
            # The webhook url can be accessed without authentication, so
            # insecure is used here
            webhook_db = db_api.get_webhook(id, insecure=True)
            function_db = webhook_db.function
            trust_id = function_db.trust_id
            project_id = function_db.project_id
            version = webhook_db.function_version

        LOG.info(
            'Invoking function %s(version %s) by webhook %s',
            webhook_db.function_id, version, id
        )

        # Setup user context
        ctx = keystone_utils.create_trust_context(trust_id, project_id)
        context.set_ctx(ctx)

        params = {
            'function_id': webhook_db.function_id,
            'function_version': version,
            'sync': False,
            'input': json.dumps(kwargs),
            'description': constants.EXECUTION_BY_WEBHOOK % id
        }
        execution = executions.create_execution(self.engine_client, params)
        pecan.response.status = 202

        return {'execution_id': execution.id}
Ejemplo n.º 7
0
    def setUp(self):
        super(DbTestCase, self).setUp()

        self._heavy_init()

        self.ctx = get_context()
        auth_context.set_ctx(self.ctx)

        self.addCleanup(auth_context.set_ctx, None)
        self.addCleanup(self._clean_db)
Ejemplo n.º 8
0
    def test_update_recurring_job(self):
        job_id = self.create_job(
            self.function_id,
            first_execution_time=datetime.utcnow() + timedelta(hours=1),
            next_execution_time=datetime.utcnow() + timedelta(hours=1),
            pattern='0 */1 * * *',
            status=status.RUNNING,
            count=10).id

        next_hour_and_half = datetime.utcnow() + timedelta(hours=1.5)
        next_two_hours = datetime.utcnow() + timedelta(hours=2)

        req_body = {
            'next_execution_time':
            str(next_hour_and_half.strftime('%Y-%m-%dT%H:%M:%SZ')),
            'pattern':
            '1 */1 * * *'
        }
        resp = self.app.put_json('/v1/jobs/%s' % job_id, req_body)

        self.assertEqual(200, resp.status_int)
        self._assertDictContainsSubset(resp.json, req_body)

        # Pause the job and resume with a valid next_execution_time
        req_body = {'status': status.PAUSED}
        resp = self.app.put_json('/v1/jobs/%s' % job_id, req_body)

        self.assertEqual(200, resp.status_int)
        self._assertDictContainsSubset(resp.json, req_body)

        req_body = {
            'status':
            status.RUNNING,
            'next_execution_time':
            str(next_two_hours.strftime('%Y-%m-%dT%H:%M:%SZ')),
        }
        resp = self.app.put_json('/v1/jobs/%s' % job_id, req_body)

        self.assertEqual(200, resp.status_int)
        self._assertDictContainsSubset(resp.json, req_body)

        # Pause the job and resume without specifying next_execution_time
        auth_context.set_ctx(self.ctx)
        self.addCleanup(auth_context.set_ctx, None)
        db_api.update_job(
            job_id, {
                'next_execution_time': datetime.utcnow() - timedelta(hours=1),
                'status': status.PAUSED
            })

        req_body = {'status': status.RUNNING}
        resp = self.app.put_json('/v1/jobs/%s' % job_id, req_body)

        self.assertEqual(200, resp.status_int)
        self._assertDictContainsSubset(resp.json, req_body)
Ejemplo n.º 9
0
    def test_job_handler_with_version(self, mock_next_time):
        db_func = self.create_function()
        function_id = db_func.id
        new_version = db_api.increase_function_version(function_id, 0)

        self.assertEqual(0, new_version.count)

        now = datetime.utcnow()
        db_job = self.create_job(
            function_id,
            function_version=1,
            status=status.RUNNING,
            next_execution_time=now,
            count=2
        )
        job_id = db_job.id

        e_client = mock.Mock()
        # It doesn't matter what's the returned value, but need to be in
        # datetime type.
        mock_next_time.return_value = now + timedelta(seconds=1)

        periodics.handle_job(e_client)
        context.set_ctx(self.ctx)

        db_job = db_api.get_job(job_id)
        self.assertEqual(1, db_job.count)
        db_func = db_api.get_function(function_id)
        self.assertEqual(0, db_func.count)
        db_version = db_api.get_function_version(function_id, 1)
        self.assertEqual(1, db_version.count)
        db_execs = db_api.get_executions(function_id=function_id,
                                         function_version=1)
        self.assertEqual(1, len(db_execs))

        periodics.handle_job(e_client)
        context.set_ctx(self.ctx)

        db_job = db_api.get_job(job_id)
        self.assertEqual(0, db_job.count)
        self.assertEqual(status.DONE, db_job.status)
        db_func = db_api.get_function(function_id)
        self.assertEqual(0, db_func.count)
        db_version = db_api.get_function_version(function_id, 1)
        self.assertEqual(2, db_version.count)
        db_execs = db_api.get_executions(function_id=function_id,
                                         function_version=1)
        self.assertEqual(2, len(db_execs))
Ejemplo n.º 10
0
    def test_get(self):
        name = 'TestAlias'
        function_version = 0
        body = {
            'function_id': self.func_id,
            'function_version': function_version,
            'name': name,
            'description': 'new alias'
        }
        db_api.create_function_alias(**body)

        resp = self.app.get('/v1/aliases/%s' % name)

        context.set_ctx(self.ctx)

        self.assertEqual(200, resp.status_int)
        self.assertEqual("new alias", resp.json.get('description'))
Ejemplo n.º 11
0
    def test_post(self):
        name = 'TestAlias'
        body = {
            'function_id': self.func_id,
            'name': name,
            'description': 'new alias'
        }

        resp = self.app.post_json('/v1/aliases', body)

        self.assertEqual(201, resp.status_int)
        self._assertDictContainsSubset(resp.json, body)

        context.set_ctx(self.ctx)

        func_alias_db = db_api.get_function_alias(name)
        self.assertEqual(name, func_alias_db.name)
        self.assertEqual(0, func_alias_db.function_version)
Ejemplo n.º 12
0
    def test_invoke_with_function_id(self, mock_create_execution,
                                     mock_create_context):
        exec_mock = mock_create_execution.return_value
        exec_mock.id = "fake_id"
        webhook = self.create_webhook(function_id=self.func_id)

        resp = self.app.post_json('/v1/webhooks/%s/invoke' % webhook.id, {})
        context.set_ctx(self.ctx)

        self.assertEqual(202, resp.status_int)

        params = {
            'function_id': self.func_id,
            'function_version': None,
            'sync': False,
            'input': json.dumps({}),
            'description': constants.EXECUTION_BY_WEBHOOK % webhook.id
        }
        mock_create_execution.assert_called_once_with(mock.ANY, params)
Ejemplo n.º 13
0
    def test_delete(self):
        name = self.rand_name(name="alias", prefix=self.prefix)
        function_version = 0
        body = {
            'function_id': self.func_id,
            'function_version': function_version,
            'name': name,
            'description': 'new alias'
        }

        db_api.create_function_alias(**body)

        resp = self.app.delete('/v1/aliases/%s' % name)

        self.assertEqual(204, resp.status_int)

        context.set_ctx(self.ctx)

        self.assertRaises(exc.DBEntityNotFoundError, db_api.get_function_alias,
                          name)
Ejemplo n.º 14
0
    def test_delete(self, mock_package_delete, mock_engine_delete,
                    mock_etcd_delete):
        db_api.increase_function_version(self.func_id,
                                         0,
                                         description="version 1")

        resp = self.app.delete('/v1/functions/%s/versions/1' % self.func_id)

        self.assertEqual(204, resp.status_int)
        mock_engine_delete.assert_called_once_with(self.func_id, version=1)
        mock_etcd_delete.assert_called_once_with(self.func_id, version=1)
        mock_package_delete.assert_called_once_with(
            unit_base.DEFAULT_PROJECT_ID, self.func_id, None, version=1)

        # We need to set context as it was removed after the API call
        context.set_ctx(self.ctx)

        with db_api.transaction():
            func_db = db_api.get_function(self.func_id)
            self.assertEqual(0, len(func_db.versions))
            self.assertEqual(0, func_db.latest_version)
Ejemplo n.º 15
0
    def test_job_handler(self, mock_get_next):
        db_func = self.create_function()
        function_id = db_func.id

        self.assertEqual(0, db_func.count)

        now = datetime.utcnow()
        db_job = self.create_job(
            function_id,
            status=status.RUNNING,
            next_execution_time=now,
            count=2
        )
        job_id = db_job.id

        e_client = mock.Mock()
        mock_get_next.return_value = now + timedelta(seconds=1)

        periodics.handle_job(e_client)
        context.set_ctx(self.ctx)

        db_job = db_api.get_job(job_id)
        self.assertEqual(1, db_job.count)
        db_func = db_api.get_function(function_id)
        self.assertEqual(1, db_func.count)
        db_execs = db_api.get_executions(function_id=function_id)
        self.assertEqual(1, len(db_execs))

        periodics.handle_job(e_client)
        context.set_ctx(self.ctx)

        db_job = db_api.get_job(job_id)
        self.assertEqual(0, db_job.count)
        self.assertEqual(status.DONE, db_job.status)
        db_func = db_api.get_function(function_id)
        self.assertEqual(2, db_func.count)
        db_execs = db_api.get_executions(function_id=function_id)
        self.assertEqual(2, len(db_execs))
Ejemplo n.º 16
0
def handle_job(engine_client):
    """Execute job task with no db transactions."""
    for job in db_api.get_next_jobs(timeutils.utcnow() + timedelta(seconds=3)):
        job_id = job.id
        func_id = job.function_id
        LOG.debug("Processing job: %s, function: %s", job_id, func_id)

        func_db = db_api.get_function(func_id, insecure=True)
        trust_id = func_db.trust_id

        try:
            # Setup context before schedule job.
            ctx = keystone_utils.create_trust_context(trust_id, job.project_id)
            context.set_ctx(ctx)

            if (job.count is not None and job.count > 0):
                job.count -= 1

            # Job delete/update is done using UPDATE ... FROM ... WHERE
            # non-locking clause.
            if job.count == 0:
                modified = db_api.conditional_update(
                    models.Job,
                    {
                        'status': status.DONE,
                        'count': 0
                    },
                    {
                        'id': job_id,
                        'status': status.RUNNING
                    },
                    insecure=True,
                )
            else:
                next_time = jobs.get_next_execution_time(
                    job.pattern, job.next_execution_time)

                modified = db_api.conditional_update(
                    models.Job,
                    {
                        'next_execution_time': next_time,
                        'count': job.count
                    },
                    {
                        'id': job_id,
                        'next_execution_time': job.next_execution_time
                    },
                    insecure=True,
                )

            if not modified:
                LOG.warning(
                    'Job %s has been already handled by another periodic '
                    'task.', job_id)
                continue

            LOG.debug("Starting to execute function %s by job %s", func_id,
                      job_id)

            params = {
                'function_id': func_id,
                'input': job.function_input,
                'sync': False,
                'description': constants.EXECUTION_BY_JOB % job_id
            }
            executions.create_execution(engine_client, params)
        except Exception:
            LOG.exception("Failed to process job %s", job_id)
        finally:
            context.set_ctx(None)
Ejemplo n.º 17
0
    def deserialize_context(self, context):
        qinling_ctx = ctx.Context.from_dict(context)
        ctx.set_ctx(qinling_ctx)

        return qinling_ctx