diff options
| author | Rahul Yadav <yadavrah@google.com> | 2026-10-01 16:34:47 +0000 |
|---|---|---|
| committer | gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-10-14 03:30:36 -0700 |
| commit | 3448df395478c2375d880e33bfc705a9ed36272b (patch) | |
| tree | 4529203b698304d03297b800e28f0bf92436e4c4 /subcmds/sync.py | |
| parent | a110680ff3e0d3e8504edf4457dff759801ad011 (diff) | |
| download | git-repo-3448df395478c2375d880e33bfc705a9ed36272b.tar.gz | |
sync: Add GetApprovedManifestV2 with named request options
Smart sync calls GetApprovedManifest(branch[, target]) over XML-RPC,
which only supports positional arguments. Adding new request options
that way does not scale.
Add a GetApprovedManifestV2 method that takes a single struct of named
options and returns a struct:
request: {branch, target?, best_effort?}
response: {success, manifest_xml?, error_message?}
Servers must ignore unknown options, so new options (e.g. criteria for
selecting a good build) can be added without another method.
The first new option is `best_effort`. repo sets it when smart sync was
enabled implicitly by the manifest's sync-smartsync="true" default
rather than by -s. The server may decline a best_effort request (e.g.
for rollout or cohort gating) by returning success=false. The client
still owns the fallback policy: declined implicit requests fall back to
a regular sync, while explicit -s requests fail as before.
If the server does not implement GetApprovedManifestV2, repo falls back
to the legacy GetApprovedManifest call.
Bug: 545137090
Test: ./run_tests
Change-Id: I9297b728df26516012cb93dffc91d39c1fb0458f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/636623
Reviewed-by: Mike Frysinger <vapier@google.com>
Commit-Queue: Rahul Yadav <yadavrah@google.com>
Tested-by: Rahul Yadav <yadavrah@google.com>
Diffstat (limited to 'subcmds/sync.py')
| -rw-r--r-- | subcmds/sync.py | 78 |
1 files changed, 71 insertions, 7 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py index 8d4896d0f..e83f42345 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -98,6 +98,69 @@ _BLOAT_SIZE_GARBAGE_THRESHOLD_KB = 1 * 1024 * 1024 # 1 GiB in KiB | |||
| 98 | 98 | ||
| 99 | logger = RepoLogger(__file__) | 99 | logger = RepoLogger(__file__) |
| 100 | 100 | ||
| 101 | # Manifest server RPC that takes a single struct of named options. Servers | ||
| 102 | # that do not implement it are called via the positional GetApprovedManifest. | ||
| 103 | _GET_APPROVED_MANIFEST_V2 = "GetApprovedManifestV2" | ||
| 104 | |||
| 105 | |||
| 106 | def _IsMethodNotFoundFault(fault: xmlrpc.client.Fault, method: str) -> bool: | ||
| 107 | """Whether |fault| means the server does not implement |method|.""" | ||
| 108 | if fault.faultCode == xmlrpc.client.METHOD_NOT_FOUND: | ||
| 109 | return True | ||
| 110 | # Python's SimpleXMLRPCDispatcher uses faultCode 1 with this message. | ||
| 111 | return f'method "{method}" is not supported' in str(fault.faultString) | ||
| 112 | |||
| 113 | |||
| 114 | def _GetApprovedManifest( | ||
| 115 | server: xmlrpc.client.ServerProxy, | ||
| 116 | branch: str, | ||
| 117 | target: Optional[str], | ||
| 118 | best_effort: bool, | ||
| 119 | ) -> Tuple[bool, str]: | ||
| 120 | """Requests the approved manifest from the manifest server. | ||
| 121 | |||
| 122 | Args: | ||
| 123 | server: The manifest server proxy. | ||
| 124 | branch: The manifest branch. | ||
| 125 | target: The build target, if any. | ||
| 126 | best_effort: The client can proceed without a manifest (e.g. by | ||
| 127 | falling back to ToT), so the server may decline the request. | ||
| 128 | |||
| 129 | Returns: | ||
| 130 | A (success, manifest XML or error message) tuple. | ||
| 131 | """ | ||
| 132 | request = {"branch": branch} | ||
| 133 | if target: | ||
| 134 | request["target"] = target | ||
| 135 | if best_effort: | ||
| 136 | request["best_effort"] = True | ||
| 137 | |||
| 138 | try: | ||
| 139 | response = server.GetApprovedManifestV2(request) | ||
| 140 | except xmlrpc.client.Fault as e: | ||
| 141 | if not _IsMethodNotFoundFault(e, _GET_APPROVED_MANIFEST_V2): | ||
| 142 | raise | ||
| 143 | # Older server: |best_effort| has no equivalent and is dropped. | ||
| 144 | if target: | ||
| 145 | success, manifest_str = server.GetApprovedManifest(branch, target) | ||
| 146 | else: | ||
| 147 | success, manifest_str = server.GetApprovedManifest(branch) | ||
| 148 | return success, manifest_str | ||
| 149 | |||
| 150 | if not isinstance(response, dict): | ||
| 151 | raise SmartSyncError( | ||
| 152 | f"error: invalid {_GET_APPROVED_MANIFEST_V2} response: {response!r}" | ||
| 153 | ) | ||
| 154 | if response.get("success"): | ||
| 155 | manifest_xml = response.get("manifest_xml") | ||
| 156 | if not isinstance(manifest_xml, str): | ||
| 157 | raise SmartSyncError( | ||
| 158 | f"error: {_GET_APPROVED_MANIFEST_V2} response has invalid " | ||
| 159 | f"manifest_xml: {manifest_xml!r}" | ||
| 160 | ) | ||
| 161 | return True, manifest_xml | ||
| 162 | return False, response.get("error_message", "unknown error") | ||
| 163 | |||
| 101 | 164 | ||
| 102 | def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]: | 165 | def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]: |
| 103 | """Generate a sequence of checkouts that is safe to perform. | 166 | """Generate a sequence of checkouts that is safe to perform. |
| @@ -2094,6 +2157,7 @@ later is required to fix a server side protocol bug. | |||
| 2094 | opt: optparse.Values, | 2157 | opt: optparse.Values, |
| 2095 | smart_sync_manifest_path: str, | 2158 | smart_sync_manifest_path: str, |
| 2096 | manifest: XmlManifest, | 2159 | manifest: XmlManifest, |
| 2160 | best_effort: bool = False, | ||
| 2097 | ) -> str: | 2161 | ) -> str: |
| 2098 | if not manifest.manifest_server: | 2162 | if not manifest.manifest_server: |
| 2099 | raise SmartSyncError( | 2163 | raise SmartSyncError( |
| @@ -2140,12 +2204,9 @@ later is required to fix a server side protocol bug. | |||
| 2140 | os.environ["TARGET_BUILD_VARIANT"], | 2204 | os.environ["TARGET_BUILD_VARIANT"], |
| 2141 | ) | 2205 | ) |
| 2142 | 2206 | ||
| 2143 | if target: | 2207 | success, manifest_str = _GetApprovedManifest( |
| 2144 | [success, manifest_str] = server.GetApprovedManifest( | 2208 | server, branch, target, best_effort |
| 2145 | branch, target | 2209 | ) |
| 2146 | ) | ||
| 2147 | else: | ||
| 2148 | [success, manifest_str] = server.GetApprovedManifest(branch) | ||
| 2149 | else: | 2210 | else: |
| 2150 | assert opt.smart_tag | 2211 | assert opt.smart_tag |
| 2151 | [success, manifest_str] = server.GetManifest(opt.smart_tag) | 2212 | [success, manifest_str] = server.GetManifest(opt.smart_tag) |
| @@ -2519,7 +2580,10 @@ later is required to fix a server side protocol bug. | |||
| 2519 | if opt.smart_sync or opt.smart_tag: | 2580 | if opt.smart_sync or opt.smart_tag: |
| 2520 | try: | 2581 | try: |
| 2521 | manifest_name = self._SmartSyncSetup( | 2582 | manifest_name = self._SmartSyncSetup( |
| 2522 | opt, smart_sync_manifest_path, manifest | 2583 | opt, |
| 2584 | smart_sync_manifest_path, | ||
| 2585 | manifest, | ||
| 2586 | best_effort=implicit_smart_sync, | ||
| 2523 | ) | 2587 | ) |
| 2524 | except (SmartSyncError, ManifestParseError) as e: | 2588 | except (SmartSyncError, ManifestParseError) as e: |
| 2525 | if not implicit_smart_sync: | 2589 | if not implicit_smart_sync: |
