Skip to content

Commit

Permalink
[Feat.] Function invocation methods (#510)
Browse files Browse the repository at this point in the history
[Feat.] Function invocation methods

Part of #438
Implements: https://docs.otc.t-systems.com/function-graph/api-ref/apis/function_invocation/index.html

Reviewed-by: Artem Lifshits
  • Loading branch information
anton-sidelnikov authored Jan 27, 2025
1 parent 5eacede commit 28fcc00
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 0 deletions.
16 changes: 16 additions & 0 deletions doc/source/sdk/guides/function_graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,19 @@ This API is used to update the maximum number of instances of a function.

.. literalinclude:: ../examples/function_graph/update_function_metadata.py
:lines: 16-23

Function Execution Synchronously
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This API is used to execute a function synchronously.

.. literalinclude:: ../examples/function_graph/sync_function_invocation.py
:lines: 16-27

Function Execution Asynchronously
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This API is used to execute a function synchronously.

.. literalinclude:: ../examples/function_graph/async_function_invocation.py
:lines: 16-27
6 changes: 6 additions & 0 deletions doc/source/sdk/proxies/function_graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ Function Operations
create_resource_tags, delete_resource_tags, update_pin_status,
update_function_code, update_function_metadata, update_max_instances

Function Invocation Operations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. autoclass:: otcextensions.sdk.function_graph.v2._proxy.Proxy
:noindex:
:members: executing_function_synchronously, executing_function_asynchronously
1 change: 1 addition & 0 deletions doc/source/sdk/resources/function_graph/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ FunctionGraph Resources
:maxdepth: 1

v2/function
v2/function_invocation
13 changes: 13 additions & 0 deletions doc/source/sdk/resources/function_graph/v2/function_invocation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
otcextensions.sdk.function_graph.v2.function_invocation
=======================================================

.. automodule:: otcextensions.sdk.function_graph.v2.function_invocation

The FunctionInvocation Class
----------------------------

The ``FunctionInvocation`` class inherits from
:class:`~otcextensions.sdk.sdk_resource.Resource`.

.. autoclass:: otcextensions.sdk.function_graph.v2.function_invocation.FunctionInvocation
:members:
27 changes: 27 additions & 0 deletions examples/function_graph/async_function_invocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Function execution asynchronously
"""
import openstack
from otcextensions import sdk

openstack.enable_logging(True)
conn = openstack.connect(cloud='otc')
sdk.register_otc_extensions(conn)

func_urn = ('urn:fss:eu-de:45c274f200d2498683982c8741fb76ac:'
'function:default:access-mysql-js-1213-1737554083545:latest')
inv = conn.functiongraph.executing_function_asynchronously(
func_urn, attrs={'a': 'b'}
)
27 changes: 27 additions & 0 deletions examples/function_graph/sync_function_invocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Function execution synchronously
"""
import openstack
from otcextensions import sdk

openstack.enable_logging(True)
conn = openstack.connect(cloud='otc')
sdk.register_otc_extensions(conn)

func_urn = ('urn:fss:eu-de:45c274f200d2498683982c8741fb76ac:'
'function:default:access-mysql-js-1213-1737554083545:latest')
inv = conn.functiongraph.executing_function_synchronously(
func_urn, attrs={'a': 'b'}
)
31 changes: 31 additions & 0 deletions otcextensions/sdk/function_graph/v2/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from otcextensions.common.utils import extract_url_parts
from otcextensions.sdk.function_graph.v2 import function as _function
from otcextensions.sdk.function_graph.v2 import function_invocation as _fi


class Proxy(proxy.Proxy):
Expand Down Expand Up @@ -158,3 +159,33 @@ def update_max_instances(self, function, instances):
"""
function = self._get_resource(_function.Function, function)
return function._update_max_instances(self, function, instances)

# ======== Function Invocations Methods ========

def executing_function_synchronously(self, func_urn, **attrs):
"""Execute a function synchronously.
:param func_urn: The URN of the Function to run
:param attrs: The request parameter as a key pair ("k":"v")
:rtype: :class:`~otcextensions.sdk.function_graph.v2.
function_invocation.FunctionInvocation`
"""
fi = self._get_resource(
_fi.FunctionInvocation, func_urn,
func_urn=func_urn.rpartition(":")[0]
)
return fi._invocation(self, 'invocations', **attrs)

def executing_function_asynchronously(self, func_urn, **attrs):
"""Execute a function asynchronously.
:param func_urn: The URN of the Function to run
:param attrs: The request parameter as a key pair ("k":"v")
:rtype: :class:`~otcextensions.sdk.function_graph.v2.
function_invocation.FunctionInvocation`
"""
fi = self._get_resource(
_fi.FunctionInvocation, func_urn,
func_urn=func_urn.rpartition(":")[0]
)
return fi._invocation(self, 'invocations-async', **attrs)
43 changes: 43 additions & 0 deletions otcextensions/sdk/function_graph/v2/function_invocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource
from openstack import exceptions


class FunctionInvocation(resource.Resource):
base_path = '/fgs/functions/%(func_urn)s'
# Capabilities
allow_create = True

_query_mapping = resource.QueryParameters('func_urn')

# Properties
func_urn = resource.URI('func_urn', type=str)

# Attributes
invoke_summary = resource.Header('X-Cff-Invoke-Summary', type=str)
cff_request_id = resource.Header('X-Cff-Request-Id', type=str)
request_id = resource.Body('request-id', type=str)
function_log = resource.Header('X-Cff-Function-Log', type=str)
billing_duration = resource.Header('X-CFF-Billing-Duration', type=str)
response_version = resource.Header('X-Cff-Response-Version', type=str)
err_code = resource.Header('X-Func-Err-Code', type=str)
err = resource.Header('X-Is-Func-Err', type=str)

def _invocation(self, session, action, attrs: dict):
"""Function Execution
"""
url = (self.base_path % self._uri.attributes) + f'/{action}'
response = session.post(url, json=str(attrs).replace("'", '"'))
exceptions.raise_from_response(response)
self._translate_response(response)
return self
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from otcextensions.sdk.function_graph.v2 import function
from otcextensions.tests.functional import base

from openstack import _log

_logger = _log.setup_logging('openstack')


class TestFunction(base.BaseFunctionalTest):
ID = None
uuid = uuid.uuid4().hex[:8]

def setUp(self):
super(TestFunction, self).setUp()
self.attrs = {
'func_name': 'test-function-' + self.uuid,
'package': 'default',
'runtime': 'Python3.9',
'handler': 'index.handler',
'timeout': 30,
'memory_size': 128,
'code_type': 'inline',
'func_code': {
'file': 'CmltcG9ydCBqc29uCgpkZWYgaGFuZGxlcihldmVudCwgY29udGV4d'
'Ck6CiAgICB0cnk6CiAgICAgICAgIyBQYXJzZSB0aGUgaW5wdXQgZX'
'ZlbnQgKGRhdGEpCiAgICAgICAgaW5wdXRfZGF0YSA9IGpzb24ubG9'
'hZHMoZXZlbnQpCiAgICAgICAgCiAgICAgICAgIyBQcmludCB0aGUg'
'cHJvdmlkZWQgZGF0YQogICAgICAgIHByaW50KCJSZWNlaXZlZCBkY'
'XRhOiIsIGlucHV0X2RhdGEpCiAgICAgICAgCiAgICAgICAgIyBSZX'
'R1cm4gYSByZXNwb25zZQogICAgICAgIHJlc3BvbnNlID0gewogICA'
'gICAgICAgICAic3RhdHVzQ29kZSI6IDIwMCwKICAgICAgICAgICAg'
'ImJvZHkiOiBmIkRhdGEgcmVjZWl2ZWQ6IHtpbnB1dF9kYXRhfSIKI'
'CAgICAgICB9CiAgICAgICAgcmV0dXJuIGpzb24uZHVtcHMocmVzcG'
'9uc2UpCgogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICA'
'gICMgSGFuZGxlIGVycm9ycwogICAgICAgIGVycm9yX3Jlc3BvbnNl'
'ID0gewogICAgICAgICAgICAic3RhdHVzQ29kZSI6IDUwMCwKICAgI'
'CAgICAgICAgImJvZHkiOiBmIkVycm9yOiB7c3RyKGUpfSIKICAgIC'
'AgICB9CiAgICAgICAgcmV0dXJuIGpzb24uZHVtcHMoZXJyb3JfcmV'
'zcG9uc2UpCg=='
}
}

self.NAME = 'test-function-' + self.uuid
self.UPDATE_NAME = 'test-function-upd-' + self.uuid

self.function = self.conn.functiongraph.create_function(**self.attrs)
assert isinstance(self.function, function.Function)
self.assertEqual(self.NAME, self.function.func_name)
self.ID = self.function.func_id
self.addCleanup(self.conn.functiongraph.delete_function, self.function)

def test_function_invoke_sync(self):
inv = self.conn.functiongraph.executing_function_synchronously(
self.function.func_urn, attrs={'a': 'b'}
)
self.assertEqual(inv.err_code, '0')
self.assertEqual(inv.err, 'false')

def test_function_invoke_async(self):
inv = self.conn.functiongraph.executing_function_asynchronously(
self.function.func_urn, attrs={'a': 'b'}
)
self.assertIsNotNone(inv.request_id)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from openstack.tests.unit import base

from otcextensions.sdk.function_graph.v2 import function_invocation


EXAMPLE = {
'func_urn': 'test-function',
'attrs': {'a': 'b'}
}


class TestFunction(base.TestCase):

def test_basic(self):
sot = function_invocation.FunctionInvocation()
path = '/fgs/functions/%(func_urn)s'
self.assertEqual(path, sot.base_path)
self.assertTrue(sot.allow_create)

def test_make_it(self):
sot = function_invocation.FunctionInvocation(**EXAMPLE)
self.assertEqual(EXAMPLE['func_urn'], sot.func_urn)
37 changes: 37 additions & 0 deletions otcextensions/tests/unit/sdk/function_graph/v2/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from otcextensions.sdk.function_graph.v2 import _proxy
from otcextensions.sdk.function_graph.v2 import function as _function
from otcextensions.sdk.function_graph.v2 import function_invocation as _fi
from openstack.tests.unit import test_proxy_base


Expand Down Expand Up @@ -158,3 +159,39 @@ def test_update_max_instances(self):
method_args=[function, instances],
expected_args=[self.proxy, function, 10],
)

def test_invocation_async(self):
fi = _fi.FunctionInvocation(
func_urn='urn:fss:eu-de:45c274f200d2498683982c8741fb76ac:'
'function:default'
':access-mysql-js-1213-1737554083545:latest',
attrs={'a': 'b'}
)
self.proxy._get_resource = mock.Mock(return_value=fi)
self._verify(
'otcextensions.sdk.function_graph.v2.function_invocation.'
'FunctionInvocation._invocation',
self.proxy.executing_function_asynchronously,
method_args=[fi.func_urn],
method_kwargs={'a': 'b'},
expected_args=[self.proxy, 'invocations-async'],
expected_kwargs={'a': 'b'}
)

def test_invocation_sync(self):
fi = _fi.FunctionInvocation(
func_urn='urn:fss:eu-de:45c274f200d2498683982c8741fb76ac:'
'function:default'
':access-mysql-js-1213-1737554083545:latest',
attrs={'a': 'b'}
)
self.proxy._get_resource = mock.Mock(return_value=fi)
self._verify(
'otcextensions.sdk.function_graph.v2.function_invocation.'
'FunctionInvocation._invocation',
self.proxy.executing_function_synchronously,
method_args=[fi.func_urn],
method_kwargs={'a': 'b'},
expected_args=[self.proxy, 'invocations'],
expected_kwargs={'a': 'b'}
)
4 changes: 4 additions & 0 deletions releasenotes/notes/fg-invocation-f5ae6ea83642438c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
Add FGS functions invocation support.

0 comments on commit 28fcc00

Please sign in to comment.