Skip to content

Commit 8e9919b

Browse files
feat: support ingress settings in remote_function (#1011)
* feat: support ingress settings in `remote_function` * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * propagate new param to bigframes.pandas module * fix mypy error --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 35b458e commit 8e9919b

File tree

5 files changed

+122
-1
lines changed

5 files changed

+122
-1
lines changed

bigframes/functions/_remote_function_client.py

+23
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import string
2424
import sys
2525
import tempfile
26+
import types
2627
from typing import cast, Tuple, TYPE_CHECKING
2728

2829
from bigframes_vendored import constants
@@ -43,6 +44,15 @@
4344

4445
logger = logging.getLogger(__name__)
4546

47+
# https://cloud.google.com/sdk/gcloud/reference/functions/deploy#--ingress-settings
48+
_INGRESS_SETTINGS_MAP = types.MappingProxyType(
49+
{
50+
"all": functions_v2.ServiceConfig.IngressSettings.ALLOW_ALL,
51+
"internal-only": functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_ONLY,
52+
"internal-and-gclb": functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_AND_GCLB,
53+
}
54+
)
55+
4656

4757
class RemoteFunctionClient:
4858
# Wait time (in seconds) for an IAM binding to take effect after creation
@@ -228,6 +238,7 @@ def create_cloud_function(
228238
is_row_processor=False,
229239
vpc_connector=None,
230240
memory_mib=1024,
241+
ingress_settings="all",
231242
):
232243
"""Create a cloud function from the given user defined function.
233244
@@ -324,6 +335,16 @@ def create_cloud_function(
324335
function.service_config.service_account_email = (
325336
self._cloud_function_service_account
326337
)
338+
if ingress_settings not in _INGRESS_SETTINGS_MAP:
339+
raise ValueError(
340+
"'{}' not one of the supported ingress settings values: {}".format(
341+
ingress_settings, list(_INGRESS_SETTINGS_MAP)
342+
)
343+
)
344+
function.service_config.ingress_settings = cast(
345+
functions_v2.ServiceConfig.IngressSettings,
346+
_INGRESS_SETTINGS_MAP[ingress_settings],
347+
)
327348
function.kms_key_name = self._cloud_function_kms_key_name
328349
create_function_request.function = function
329350

@@ -372,6 +393,7 @@ def provision_bq_remote_function(
372393
is_row_processor,
373394
cloud_function_vpc_connector,
374395
cloud_function_memory_mib,
396+
cloud_function_ingress_settings,
375397
):
376398
"""Provision a BigQuery remote function."""
377399
# Augment user package requirements with any internal package
@@ -418,6 +440,7 @@ def provision_bq_remote_function(
418440
is_row_processor=is_row_processor,
419441
vpc_connector=cloud_function_vpc_connector,
420442
memory_mib=cloud_function_memory_mib,
443+
ingress_settings=cloud_function_ingress_settings,
421444
)
422445
else:
423446
logger.info(f"Cloud function {cloud_function_name} already exists.")

bigframes/functions/_remote_function_session.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,17 @@
1919
import inspect
2020
import sys
2121
import threading
22-
from typing import Any, cast, Dict, Mapping, Optional, Sequence, TYPE_CHECKING, Union
22+
from typing import (
23+
Any,
24+
cast,
25+
Dict,
26+
Literal,
27+
Mapping,
28+
Optional,
29+
Sequence,
30+
TYPE_CHECKING,
31+
Union,
32+
)
2333
import warnings
2434

2535
import bigframes_vendored.constants as constants
@@ -110,6 +120,9 @@ def remote_function(
110120
cloud_function_max_instances: Optional[int] = None,
111121
cloud_function_vpc_connector: Optional[str] = None,
112122
cloud_function_memory_mib: Optional[int] = 1024,
123+
cloud_function_ingress_settings: Literal[
124+
"all", "internal-only", "internal-and-gclb"
125+
] = "all",
113126
):
114127
"""Decorator to turn a user defined function into a BigQuery remote function.
115128
@@ -280,6 +293,11 @@ def remote_function(
280293
default memory of cloud functions be allocated, pass `None`. See
281294
for more details
282295
https://cloud.google.com/functions/docs/configuring/memory.
296+
cloud_function_ingress_settings (str, Optional):
297+
Ingress settings controls dictating what traffic can reach the
298+
function. By default `all` will be used. It must be one of:
299+
`all`, `internal-only`, `internal-and-gclb`. See for more details
300+
https://cloud.google.com/functions/docs/networking/network-settings#ingress_settings.
283301
"""
284302
# Some defaults may be used from the session if not provided otherwise
285303
import bigframes.exceptions as bf_exceptions
@@ -504,6 +522,7 @@ def try_delattr(attr):
504522
is_row_processor=is_row_processor,
505523
cloud_function_vpc_connector=cloud_function_vpc_connector,
506524
cloud_function_memory_mib=cloud_function_memory_mib,
525+
cloud_function_ingress_settings=cloud_function_ingress_settings,
507526
)
508527

509528
# TODO(shobs): Find a better way to support udfs with param named "name".

bigframes/pandas/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,9 @@ def remote_function(
669669
cloud_function_max_instances: Optional[int] = None,
670670
cloud_function_vpc_connector: Optional[str] = None,
671671
cloud_function_memory_mib: Optional[int] = 1024,
672+
cloud_function_ingress_settings: Literal[
673+
"all", "internal-only", "internal-and-gclb"
674+
] = "all",
672675
):
673676
return global_session.with_default_session(
674677
bigframes.session.Session.remote_function,
@@ -687,6 +690,7 @@ def remote_function(
687690
cloud_function_max_instances=cloud_function_max_instances,
688691
cloud_function_vpc_connector=cloud_function_vpc_connector,
689692
cloud_function_memory_mib=cloud_function_memory_mib,
693+
cloud_function_ingress_settings=cloud_function_ingress_settings,
690694
)
691695

692696

bigframes/session/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,9 @@ def remote_function(
10401040
cloud_function_max_instances: Optional[int] = None,
10411041
cloud_function_vpc_connector: Optional[str] = None,
10421042
cloud_function_memory_mib: Optional[int] = 1024,
1043+
cloud_function_ingress_settings: Literal[
1044+
"all", "internal-only", "internal-and-gclb"
1045+
] = "all",
10431046
):
10441047
"""Decorator to turn a user defined function into a BigQuery remote function. Check out
10451048
the code samples at: https://cloud.google.com/bigquery/docs/remote-functions#bigquery-dataframes.
@@ -1194,6 +1197,11 @@ def remote_function(
11941197
default memory of cloud functions be allocated, pass `None`. See
11951198
for more details
11961199
https://cloud.google.com/functions/docs/configuring/memory.
1200+
cloud_function_ingress_settings (str, Optional):
1201+
Ingress settings controls dictating what traffic can reach the
1202+
function. By default `all` will be used. It must be one of:
1203+
`all`, `internal-only`, `internal-and-gclb`. See for more details
1204+
https://cloud.google.com/functions/docs/networking/network-settings#ingress_settings.
11971205
Returns:
11981206
callable: A remote function object pointing to the cloud assets created
11991207
in the background to support the remote execution. The cloud assets can be
@@ -1220,6 +1228,7 @@ def remote_function(
12201228
cloud_function_max_instances=cloud_function_max_instances,
12211229
cloud_function_vpc_connector=cloud_function_vpc_connector,
12221230
cloud_function_memory_mib=cloud_function_memory_mib,
1231+
cloud_function_ingress_settings=cloud_function_ingress_settings,
12231232
)
12241233

12251234
def read_gbq_function(

tests/system/large/test_remote_function.py

+66
Original file line numberDiff line numberDiff line change
@@ -2173,3 +2173,69 @@ def foo(x):
21732173
cleanup_remote_function_assets(
21742174
session.bqclient, session.cloudfunctionsclient, foo
21752175
)
2176+
2177+
2178+
@pytest.mark.parametrize(
2179+
("ingress_settings_args", "effective_ingress_settings"),
2180+
[
2181+
pytest.param(
2182+
{}, functions_v2.ServiceConfig.IngressSettings.ALLOW_ALL, id="no-set"
2183+
),
2184+
pytest.param(
2185+
{"cloud_function_ingress_settings": "all"},
2186+
functions_v2.ServiceConfig.IngressSettings.ALLOW_ALL,
2187+
id="set-all",
2188+
),
2189+
pytest.param(
2190+
{"cloud_function_ingress_settings": "internal-only"},
2191+
functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_ONLY,
2192+
id="set-internal-only",
2193+
),
2194+
pytest.param(
2195+
{"cloud_function_ingress_settings": "internal-and-gclb"},
2196+
functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_AND_GCLB,
2197+
id="set-internal-and-gclb",
2198+
),
2199+
],
2200+
)
2201+
@pytest.mark.flaky(retries=2, delay=120)
2202+
def test_remote_function_ingress_settings(
2203+
session, scalars_dfs, ingress_settings_args, effective_ingress_settings
2204+
):
2205+
try:
2206+
2207+
def square(x: int) -> int:
2208+
return x * x
2209+
2210+
square_remote = session.remote_function(reuse=False, **ingress_settings_args)(
2211+
square
2212+
)
2213+
2214+
# Assert that the GCF is created with the intended maximum timeout
2215+
gcf = session.cloudfunctionsclient.get_function(
2216+
name=square_remote.bigframes_cloud_function
2217+
)
2218+
assert gcf.service_config.ingress_settings == effective_ingress_settings
2219+
2220+
scalars_df, scalars_pandas_df = scalars_dfs
2221+
2222+
bf_result = scalars_df["int64_too"].apply(square_remote).to_pandas()
2223+
pd_result = scalars_pandas_df["int64_too"].apply(square)
2224+
2225+
pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False)
2226+
finally:
2227+
# clean up the gcp assets created for the remote function
2228+
cleanup_remote_function_assets(
2229+
session.bqclient, session.cloudfunctionsclient, square_remote
2230+
)
2231+
2232+
2233+
@pytest.mark.flaky(retries=2, delay=120)
2234+
def test_remote_function_ingress_settings_unsupported(session):
2235+
with pytest.raises(
2236+
ValueError, match="'unknown' not one of the supported ingress settings values"
2237+
):
2238+
2239+
@session.remote_function(reuse=False, cloud_function_ingress_settings="unknown")
2240+
def square(x: int) -> int:
2241+
return x * x

0 commit comments

Comments
 (0)