mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-07-16 05:36:59 +00:00
sync: Support pluggable remote helpers for smart sync manifest server.
Introduce support for pluggable remote helpers (declared via the optional 'helper' attribute in <manifest-server>) to dynamically resolve proxy addresses. Route the XML-RPC manifest server connection through the resolved proxy. Bug: b/517477903 Change-Id: I3b6b8ea2640bb077521df4b4a9e8a34a8c6ecdad Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/591642 Tested-by: Rahul Yadav <yadavrah@google.com> Commit-Queue: Rahul Yadav <yadavrah@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
This commit is contained in:
committed by
gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
cd307a6089
commit
39c0b60900
@@ -1514,3 +1514,232 @@ class UpdateAllManifestProjectsTests(unittest.TestCase):
|
||||
)
|
||||
mock_sync_to_rev.assert_not_called()
|
||||
mock_update_manifest.assert_called_once()
|
||||
|
||||
|
||||
class TestSmartSyncSetupRemoteHelper(unittest.TestCase):
|
||||
"""Tests for _SmartSyncSetup with remote helpers."""
|
||||
|
||||
def setUp(self):
|
||||
self.cmd = sync.Sync()
|
||||
self.opt = mock.MagicMock()
|
||||
self.opt.quiet = False
|
||||
self.opt.smart_sync = True
|
||||
self.manifest = mock.MagicMock()
|
||||
self.smart_sync_manifest_path = "/fake/path/to/manifest.xml"
|
||||
|
||||
@mock.patch("shutil.which")
|
||||
@mock.patch("subprocess.Popen")
|
||||
@mock.patch("xmlrpc.client.Server")
|
||||
@mock.patch("subcmds.sync.PersistentTransport")
|
||||
def test_smart_sync_setup_with_helper(
|
||||
self, mock_transport_class, mock_server_class, mock_popen, mock_which
|
||||
):
|
||||
"""Test _SmartSyncSetup when a helper is present and succeeds."""
|
||||
import subprocess
|
||||
|
||||
self.manifest.manifest_server = (
|
||||
"persistent-https://android-smartsync.corp.google.com/"
|
||||
"manifestserver"
|
||||
)
|
||||
self.manifest.manifest_server_helper = "repo-remote-sso"
|
||||
mock_which.return_value = "/fake/bin/repo-remote-sso"
|
||||
|
||||
# Mock subprocess to return a JSON with status ok and proxy address
|
||||
mock_process = mock.MagicMock()
|
||||
mock_process.communicate.return_value = (
|
||||
'{"status":"ok","message":"http://127.0.0.1:999"}\n',
|
||||
"",
|
||||
)
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
# Mock XML-RPC server call
|
||||
mock_server = mock.MagicMock()
|
||||
mock_server.GetApprovedManifest.return_value = [
|
||||
True,
|
||||
"<manifest></manifest>",
|
||||
]
|
||||
mock_server_class.return_value = mock_server
|
||||
|
||||
# Mock manifest project branch
|
||||
self.cmd._GetBranch = mock.MagicMock(return_value="main")
|
||||
self.cmd._ReloadManifest = mock.MagicMock()
|
||||
|
||||
# Mock open to avoid writing to disk
|
||||
with mock.patch("builtins.open", mock.mock_open()):
|
||||
manifest_name = self.cmd._SmartSyncSetup(
|
||||
self.opt, self.smart_sync_manifest_path, self.manifest
|
||||
)
|
||||
|
||||
# Assertions
|
||||
mock_which.assert_called_once_with("repo-remote-sso")
|
||||
mock_popen.assert_called_once_with(
|
||||
["repo-remote-sso", self.manifest.manifest_server],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# Verify transport was created with the proxy returned by helper (with
|
||||
# http:// prepended)
|
||||
mock_transport_class.assert_called_once_with(
|
||||
self.manifest.manifest_server, proxy="http://127.0.0.1:999"
|
||||
)
|
||||
|
||||
# Verify Server was created with the same URL, with persistent- stripped
|
||||
mock_server_class.assert_called_once_with(
|
||||
"https://android-smartsync.corp.google.com/manifestserver",
|
||||
transport=mock_transport_class.return_value,
|
||||
)
|
||||
|
||||
self.assertEqual(manifest_name, "manifest.xml")
|
||||
|
||||
@mock.patch("shutil.which")
|
||||
@mock.patch("subprocess.Popen")
|
||||
def test_smart_sync_setup_helper_error(self, mock_popen, mock_which):
|
||||
"""Test _SmartSyncSetup when helper returns an error status."""
|
||||
self.manifest.manifest_server = (
|
||||
"http://android-smartsync.corp.google.com/manifestserver"
|
||||
)
|
||||
self.manifest.manifest_server_helper = "repo-remote-sso"
|
||||
mock_which.return_value = "/fake/bin/repo-remote-sso"
|
||||
|
||||
# Mock subprocess to return a JSON with status error
|
||||
mock_process = mock.MagicMock()
|
||||
mock_process.communicate.return_value = (
|
||||
'{"status":"error","message":"uplink-helper failed"}\n',
|
||||
"",
|
||||
)
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
with self.assertRaises(sync.SmartSyncError) as context:
|
||||
self.cmd._SmartSyncSetup(
|
||||
self.opt, self.smart_sync_manifest_path, self.manifest
|
||||
)
|
||||
|
||||
self.assertIn(
|
||||
"helper repo-remote-sso returned error: uplink-helper failed",
|
||||
str(context.exception),
|
||||
)
|
||||
|
||||
@mock.patch("shutil.which")
|
||||
def test_smart_sync_setup_missing_declared_helper(self, mock_which):
|
||||
"""Test _SmartSyncSetup when helper declared in manifest is missing."""
|
||||
self.manifest.manifest_server = (
|
||||
"http://android-smartsync.corp.google.com/manifestserver"
|
||||
)
|
||||
self.manifest.manifest_server_helper = "repo-remote-sso"
|
||||
mock_which.return_value = None
|
||||
|
||||
with self.assertRaises(sync.SmartSyncError) as context:
|
||||
self.cmd._SmartSyncSetup(
|
||||
self.opt, self.smart_sync_manifest_path, self.manifest
|
||||
)
|
||||
|
||||
self.assertIn(
|
||||
"helper binary 'repo-remote-sso' declared in manifest was not "
|
||||
"found",
|
||||
str(context.exception),
|
||||
)
|
||||
|
||||
@mock.patch("shutil.which")
|
||||
@mock.patch("subprocess.Popen")
|
||||
def test_smart_sync_setup_helper_exit_code_error(
|
||||
self, mock_popen, mock_which
|
||||
):
|
||||
"""Test _SmartSyncSetup when helper exits with non-zero and stderr."""
|
||||
self.manifest.manifest_server = (
|
||||
"http://android-smartsync.corp.google.com/manifestserver"
|
||||
)
|
||||
self.manifest.manifest_server_helper = "repo-remote-sso"
|
||||
mock_which.return_value = "/fake/bin/repo-remote-sso"
|
||||
|
||||
# Mock subprocess: exit code 1, stderr, and no JSON on stdout
|
||||
mock_process = mock.MagicMock()
|
||||
mock_process.communicate.return_value = (
|
||||
"",
|
||||
"internal binary error occurred\n",
|
||||
)
|
||||
mock_process.returncode = 1
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
with self.assertRaises(sync.SmartSyncError) as context:
|
||||
self.cmd._SmartSyncSetup(
|
||||
self.opt, self.smart_sync_manifest_path, self.manifest
|
||||
)
|
||||
|
||||
self.assertIn(
|
||||
"helper repo-remote-sso exited with exit code 1. "
|
||||
"Stderr: internal binary error occurred",
|
||||
str(context.exception),
|
||||
)
|
||||
|
||||
@mock.patch("shutil.which")
|
||||
@mock.patch("subprocess.Popen")
|
||||
def test_smart_sync_setup_helper_json_decode_error_with_stderr(
|
||||
self, mock_popen, mock_which
|
||||
):
|
||||
"""Test _SmartSyncSetup when helper returns invalid JSON and stderr."""
|
||||
self.manifest.manifest_server = (
|
||||
"http://android-smartsync.corp.google.com/manifestserver"
|
||||
)
|
||||
self.manifest.manifest_server_helper = "repo-remote-sso"
|
||||
mock_which.return_value = "/fake/bin/repo-remote-sso"
|
||||
|
||||
mock_process = mock.MagicMock()
|
||||
mock_process.communicate.return_value = (
|
||||
"not a json",
|
||||
"some warning messages\n",
|
||||
)
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
with self.assertRaises(sync.SmartSyncError) as context:
|
||||
self.cmd._SmartSyncSetup(
|
||||
self.opt, self.smart_sync_manifest_path, self.manifest
|
||||
)
|
||||
|
||||
self.assertIn(
|
||||
"failed to parse JSON from helper repo-remote-sso",
|
||||
str(context.exception),
|
||||
)
|
||||
self.assertIn(
|
||||
"Stderr was: some warning messages",
|
||||
str(context.exception),
|
||||
)
|
||||
|
||||
@mock.patch("shutil.which")
|
||||
@mock.patch("subprocess.Popen")
|
||||
def test_smart_sync_setup_helper_error_with_stderr(
|
||||
self, mock_popen, mock_which
|
||||
):
|
||||
"""Test _SmartSyncSetup when helper returns error status and stderr."""
|
||||
self.manifest.manifest_server = (
|
||||
"http://android-smartsync.corp.google.com/manifestserver"
|
||||
)
|
||||
self.manifest.manifest_server_helper = "repo-remote-sso"
|
||||
mock_which.return_value = "/fake/bin/repo-remote-sso"
|
||||
|
||||
mock_process = mock.MagicMock()
|
||||
mock_process.communicate.return_value = (
|
||||
'{"status":"error","message":"uplink-helper failed"}\n',
|
||||
"debugging logs\n",
|
||||
)
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
with self.assertRaises(sync.SmartSyncError) as context:
|
||||
self.cmd._SmartSyncSetup(
|
||||
self.opt, self.smart_sync_manifest_path, self.manifest
|
||||
)
|
||||
|
||||
self.assertIn(
|
||||
"helper repo-remote-sso returned error: uplink-helper failed",
|
||||
str(context.exception),
|
||||
)
|
||||
self.assertIn(
|
||||
"Stderr was: debugging logs",
|
||||
str(context.exception),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user