From 6246c14e76ae9fdea7f9eb74e6771e52963bf6a1 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Tue, 22 Sep 2026 16:44:10 +0000 Subject: [PATCH 1/5] [Better Tests] Support GCE_METADATA_HOST with port in compute_metadata --- .../google_cloud_utils/compute_metadata.py | 17 ++++- .../compute_metadata_test.py | 63 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py diff --git a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py index 362e25a6d8..f4caf99c5c 100644 --- a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py +++ b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py @@ -13,6 +13,7 @@ # limitations under the License. """GCE metadata.""" +import os import socket import requests @@ -20,7 +21,10 @@ from clusterfuzz._internal.base import retry from clusterfuzz._internal.system import environment -_METADATA_SERVER = 'metadata.google.internal' +# GCE_METADATA_HOST points this module at a metadata emulator instead of the +# real server. It is the same variable google-auth honours, and it may carry a +# port ("127.0.0.1:41234"). Unset in production, where the default applies. +_METADATA_SERVER = os.getenv('GCE_METADATA_HOST', 'metadata.google.internal') _METADATA_URL = 'http://{}/computeMetadata/v1/'.format(_METADATA_SERVER) _RETRIES = 3 @@ -48,10 +52,19 @@ def get(path): return _get_raw(path) +def _metadata_host_port(): + """Splits _METADATA_SERVER into (host, port), defaulting to port 80.""" + if ':' in _METADATA_SERVER: + host, _, port = _METADATA_SERVER.partition(':') + return host, int(port) + + return _METADATA_SERVER, 80 + + def is_gce(): """Return whether or not we're on GCE.""" try: - sock = socket.create_connection((_METADATA_SERVER, 80)) + sock = socket.create_connection(_metadata_host_port()) sock.close() except Exception: return False diff --git a/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py b/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py new file mode 100644 index 0000000000..fd1c8414c6 --- /dev/null +++ b/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py @@ -0,0 +1,63 @@ +# Copyright 2026 Google LLC +# +# 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. +"""Unit tests for compute_metadata.""" + +import importlib +import os +import socket +import unittest +from unittest import mock + +from clusterfuzz._internal.google_cloud_utils import compute_metadata + + +class ComputeMetadataTest(unittest.TestCase): + """Tests for compute_metadata host and port resolution.""" + + def test_host_port_split(self): + """Verifies that _metadata_host_port() defaults to port 80 for bare hostnames and parses explicit host:port values.""" + with mock.patch.object(compute_metadata, '_METADATA_SERVER', + 'metadata.google.internal'): + self.assertEqual(('metadata.google.internal', 80), + compute_metadata._metadata_host_port()) # pylint: disable=protected-access + + with mock.patch.object(compute_metadata, '_METADATA_SERVER', + '127.0.0.1:41234'): + self.assertEqual(('127.0.0.1', 41234), + compute_metadata._metadata_host_port()) # pylint: disable=protected-access + + def test_is_gce_on_non_default_port(self): + """Verifies that compute_metadata.is_gce() connects to the port in _METADATA_SERVER rather than hardcoding port 80.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener: + listener.bind(('127.0.0.1', 0)) + listener.listen(1) + port = listener.getsockname()[1] + + with mock.patch.object(compute_metadata, '_METADATA_SERVER', + f'127.0.0.1:{port}'): + self.assertTrue(compute_metadata.is_gce()) + + with mock.patch.object(compute_metadata, '_METADATA_SERVER', '127.0.0.1:1'): + self.assertFalse(compute_metadata.is_gce()) + + def test_gce_metadata_host_env_override(self): + """Verifies that GCE_METADATA_HOST overrides the default metadata server and URL.""" + with mock.patch.dict( + os.environ, {'GCE_METADATA_HOST': '127.0.0.1:45678'}, clear=False): + importlib.reload(compute_metadata) + self.assertEqual('127.0.0.1:45678', compute_metadata._METADATA_SERVER) # pylint: disable=protected-access + self.assertEqual('http://127.0.0.1:45678/computeMetadata/v1/', + compute_metadata._METADATA_URL) # pylint: disable=protected-access + + importlib.reload(compute_metadata) From a4dadd23955dc5c178226cdaeed4870dba40ec2b Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Fri, 25 Sep 2026 01:26:42 +0000 Subject: [PATCH 2/5] [Better Tests] Refine comment --- .../_internal/google_cloud_utils/compute_metadata.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py index f4caf99c5c..703def2fc6 100644 --- a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py +++ b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py @@ -21,9 +21,8 @@ from clusterfuzz._internal.base import retry from clusterfuzz._internal.system import environment -# GCE_METADATA_HOST points this module at a metadata emulator instead of the -# real server. It is the same variable google-auth honours, and it may carry a -# port ("127.0.0.1:41234"). Unset in production, where the default applies. +# GCE_METADATA_HOST may point to a metadata emulator instead of the +# real server. It is the same variable google-auth honours. _METADATA_SERVER = os.getenv('GCE_METADATA_HOST', 'metadata.google.internal') _METADATA_URL = 'http://{}/computeMetadata/v1/'.format(_METADATA_SERVER) From e2b677d03789ddcb3f36c9a89f90a125ce4455f2 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Fri, 25 Sep 2026 01:43:58 +0000 Subject: [PATCH 3/5] [Better tests] Better parsing in compute_metadata --- .../_internal/google_cloud_utils/compute_metadata.py | 12 ++++++------ .../core/google_cloud_utils/compute_metadata_test.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py index 703def2fc6..f9f5077403 100644 --- a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py +++ b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py @@ -15,10 +15,12 @@ import os import socket +from urllib.parse import urlsplit import requests from clusterfuzz._internal.base import retry +from clusterfuzz._internal.metrics import logs from clusterfuzz._internal.system import environment # GCE_METADATA_HOST may point to a metadata emulator instead of the @@ -53,11 +55,8 @@ def get(path): def _metadata_host_port(): """Splits _METADATA_SERVER into (host, port), defaulting to port 80.""" - if ':' in _METADATA_SERVER: - host, _, port = _METADATA_SERVER.partition(':') - return host, int(port) - - return _METADATA_SERVER, 80 + parsed = urlsplit('//' + _METADATA_SERVER) + return parsed.hostname, parsed.port or 80 def is_gce(): @@ -65,7 +64,8 @@ def is_gce(): try: sock = socket.create_connection(_metadata_host_port()) sock.close() - except Exception: + except Exception as e: + logs.info(f'Bot not marked as GCE: {e}') return False return True diff --git a/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py b/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py index fd1c8414c6..a56185562d 100644 --- a/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py +++ b/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py @@ -37,6 +37,16 @@ def test_host_port_split(self): self.assertEqual(('127.0.0.1', 41234), compute_metadata._metadata_host_port()) # pylint: disable=protected-access + def test_host_port_split_ipv6(self): + """Verifies that _metadata_host_port() strips brackets from IPv6 hosts and parses an optional port.""" + with mock.patch.object(compute_metadata, '_METADATA_SERVER', + '[fd20:ce::254]'): + self.assertEqual(('fd20:ce::254', 80), + compute_metadata._metadata_host_port()) # pylint: disable=protected-access + + with mock.patch.object(compute_metadata, '_METADATA_SERVER', '[::1]:41234'): + self.assertEqual(('::1', 41234), compute_metadata._metadata_host_port()) # pylint: disable=protected-access + def test_is_gce_on_non_default_port(self): """Verifies that compute_metadata.is_gce() connects to the port in _METADATA_SERVER rather than hardcoding port 80.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener: From b3a108b3466d2b20da7d3149eed5496d60b6177c Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Tue, 29 Sep 2026 16:37:36 +0000 Subject: [PATCH 4/5] [Better tests] Adds logs for monitoring metadata server --- .../_internal/google_cloud_utils/compute_metadata.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py index f9f5077403..f357ece9f5 100644 --- a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py +++ b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py @@ -62,10 +62,12 @@ def _metadata_host_port(): def is_gce(): """Return whether or not we're on GCE.""" try: - sock = socket.create_connection(_metadata_host_port()) + host, port = _metadata_host_port() + logs.info(f'Connecting to metadata server at {host}:{port}') + sock = socket.create_connection((host, port)) sock.close() except Exception as e: - logs.info(f'Bot not marked as GCE: {e}') + logs.warning(f'Bot not marked as GCE: {e}') return False return True From 278548e617aa22a2e204a15f9f94af9d87e2ecce Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Tue, 29 Sep 2026 22:39:46 +0000 Subject: [PATCH 5/5] [Better tests] Make assertions cleaner by asserting per host & port" --- .../google_cloud_utils/compute_metadata.py | 2 +- .../compute_metadata_test.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py index f357ece9f5..1b9a72df65 100644 --- a/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py +++ b/src/clusterfuzz/_internal/google_cloud_utils/compute_metadata.py @@ -67,7 +67,7 @@ def is_gce(): sock = socket.create_connection((host, port)) sock.close() except Exception as e: - logs.warning(f'Bot not marked as GCE: {e}') + logs.info(f'Bot not marked as GCE: {e}') return False return True diff --git a/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py b/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py index a56185562d..c91c3cd2a7 100644 --- a/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py +++ b/src/clusterfuzz/_internal/tests/core/google_cloud_utils/compute_metadata_test.py @@ -29,23 +29,28 @@ def test_host_port_split(self): """Verifies that _metadata_host_port() defaults to port 80 for bare hostnames and parses explicit host:port values.""" with mock.patch.object(compute_metadata, '_METADATA_SERVER', 'metadata.google.internal'): - self.assertEqual(('metadata.google.internal', 80), - compute_metadata._metadata_host_port()) # pylint: disable=protected-access + host, port = compute_metadata._metadata_host_port() # pylint: disable=protected-access + self.assertEqual('metadata.google.internal', host) + self.assertEqual(80, port) with mock.patch.object(compute_metadata, '_METADATA_SERVER', '127.0.0.1:41234'): - self.assertEqual(('127.0.0.1', 41234), - compute_metadata._metadata_host_port()) # pylint: disable=protected-access + host, port = compute_metadata._metadata_host_port() # pylint: disable=protected-access + self.assertEqual('127.0.0.1', host) + self.assertEqual(41234, port) def test_host_port_split_ipv6(self): """Verifies that _metadata_host_port() strips brackets from IPv6 hosts and parses an optional port.""" with mock.patch.object(compute_metadata, '_METADATA_SERVER', '[fd20:ce::254]'): - self.assertEqual(('fd20:ce::254', 80), - compute_metadata._metadata_host_port()) # pylint: disable=protected-access + host, port = compute_metadata._metadata_host_port() # pylint: disable=protected-access + self.assertEqual('fd20:ce::254', host) + self.assertEqual(80, port) with mock.patch.object(compute_metadata, '_METADATA_SERVER', '[::1]:41234'): - self.assertEqual(('::1', 41234), compute_metadata._metadata_host_port()) # pylint: disable=protected-access + host, port = compute_metadata._metadata_host_port() # pylint: disable=protected-access + self.assertEqual('::1', host) + self.assertEqual(41234, port) def test_is_gce_on_non_default_port(self): """Verifies that compute_metadata.is_gce() connects to the port in _METADATA_SERVER rather than hardcoding port 80."""