James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 1 | :title: Project Gating |
| 2 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 3 | .. _project_gating: |
| 4 | |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 5 | Project Gating |
| 6 | ============== |
| 7 | |
| 8 | Traditionally, many software development projects merge changes from |
| 9 | developers into the repository, and then identify regressions |
| 10 | resulting from those changes (perhaps by running a test suite with a |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 11 | continuous integration system), followed by more patches to fix those |
| 12 | bugs. When the mainline of development is broken, it can be very |
| 13 | frustrating for developers and can cause lost productivity, |
| 14 | particularly so when the number of contributors or contributions is |
| 15 | large. |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 16 | |
| 17 | The process of gating attempts to prevent changes that introduce |
| 18 | regressions from being merged. This keeps the mainline of development |
| 19 | open and working for all developers, and only when a change is |
| 20 | confirmed to work without disruption is it merged. |
| 21 | |
| 22 | Many projects practice an informal method of gating where developers |
| 23 | with mainline commit access ensure that a test suite runs before |
| 24 | merging a change. With more developers, more changes, and more |
| 25 | comprehensive test suites, that process does not scale very well, and |
| 26 | is not the best use of a developer's time. Zuul can help automate |
| 27 | this process, with a particular emphasis on ensuring large numbers of |
| 28 | changes are tested correctly. |
| 29 | |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 30 | Testing in parallel |
| 31 | ------------------- |
| 32 | |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 33 | A particular focus of Zuul is ensuring correctly ordered testing of |
| 34 | changes in parallel. A gating system should always test each change |
| 35 | applied to the tip of the branch exactly as it is going to be merged. |
| 36 | A simple way to do that would be to test one change at a time, and |
| 37 | merge it only if it passes tests. That works very well, but if |
| 38 | changes take a long time to test, developers may have to wait a long |
| 39 | time for their changes to make it into the repository. With some |
| 40 | projects, it may take hours to test changes, and it is easy for |
| 41 | developers to create changes at a rate faster than they can be tested |
| 42 | and merged. |
| 43 | |
James E. Blair | 91fe483 | 2017-07-28 17:28:26 -0700 | [diff] [blame] | 44 | Zuul's :value:`dependent pipeline manager<pipeline.manager.dependent>` |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 45 | allows for parallel execution of test jobs for gating while ensuring |
| 46 | changes are tested correctly, exactly as if they had been tested one |
| 47 | at a time. It does this by performing speculative execution of test |
| 48 | jobs; it assumes that all jobs will succeed and tests them in parallel |
| 49 | accordingly. If they do succeed, they can all be merged. However, if |
| 50 | one fails, then changes that were expecting it to succeed are |
| 51 | re-tested without the failed change. In the best case, as many |
| 52 | changes as execution contexts are available may be tested in parallel |
| 53 | and merged at once. In the worst case, changes are tested one at a |
| 54 | time (as each subsequent change fails, changes behind it start again). |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 55 | |
| 56 | For example, if a core developer approves five changes in rapid |
| 57 | succession:: |
| 58 | |
| 59 | A, B, C, D, E |
| 60 | |
| 61 | Zuul queues those changes in the order they were approved, and notes |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 62 | that each subsequent change depends on the one ahead of it merging: |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 63 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 64 | .. blockdiag:: |
| 65 | |
| 66 | blockdiag foo { |
| 67 | node_width = 40; |
| 68 | span_width = 40; |
| 69 | A <- B <- C <- D <- E; |
| 70 | } |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 71 | |
| 72 | Zuul then starts immediately testing all of the changes in parallel. |
| 73 | But in the case of changes that depend on others, it instructs the |
| 74 | test system to include the changes ahead of it, with the assumption |
| 75 | they pass. That means jobs testing change *B* include change *A* as |
| 76 | well:: |
| 77 | |
| 78 | Jobs for A: merge change A, then test |
| 79 | Jobs for B: merge changes A and B, then test |
| 80 | Jobs for C: merge changes A, B and C, then test |
| 81 | Jobs for D: merge changes A, B, C and D, then test |
| 82 | Jobs for E: merge changes A, B, C, D and E, then test |
| 83 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 84 | Hence jobs triggered to tests A will only test A and ignore B, C, D: |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 85 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 86 | .. blockdiag:: |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 87 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 88 | blockdiag foo { |
| 89 | node_width = 40; |
| 90 | span_width = 40; |
| 91 | master -> A -> B -> C -> D -> E; |
| 92 | group jobs_for_A { |
| 93 | label = "Merged changes for A"; |
| 94 | master -> A; |
| 95 | } |
| 96 | group ignored_to_test_A { |
| 97 | label = "Ignored changes"; |
| 98 | color = "lightgray"; |
| 99 | B -> C -> D -> E; |
| 100 | } |
| 101 | } |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 102 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 103 | The jobs for E would include the whole dependency chain: A, B, C, D, and E. |
| 104 | E will be tested assuming A, B, C, and D passed: |
| 105 | |
| 106 | .. blockdiag:: |
| 107 | |
| 108 | blockdiag foo { |
| 109 | node_width = 40; |
| 110 | span_width = 40; |
| 111 | group jobs_for_E { |
| 112 | label = "Merged changes for E"; |
| 113 | master -> A -> B -> C -> D -> E; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | If changes *A* and *B* pass tests (green), and *C*, *D*, and *E* fail (red): |
| 118 | |
| 119 | .. blockdiag:: |
| 120 | |
| 121 | blockdiag foo { |
| 122 | node_width = 40; |
| 123 | span_width = 40; |
| 124 | |
| 125 | A [color = lightgreen]; |
| 126 | B [color = lightgreen]; |
| 127 | C [color = pink]; |
| 128 | D [color = pink]; |
| 129 | E [color = pink]; |
| 130 | |
| 131 | master <- A <- B <- C <- D <- E; |
| 132 | } |
| 133 | |
| 134 | Zuul will merge change *A* followed by change *B*, leaving this queue: |
| 135 | |
| 136 | .. blockdiag:: |
| 137 | |
| 138 | blockdiag foo { |
| 139 | node_width = 40; |
| 140 | span_width = 40; |
| 141 | |
| 142 | C [color = pink]; |
| 143 | D [color = pink]; |
| 144 | E [color = pink]; |
| 145 | |
| 146 | C <- D <- E; |
| 147 | } |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 148 | |
| 149 | Since *D* was dependent on *C*, it is not clear whether *D*'s failure is the |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 150 | result of a defect in *D* or *C*: |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 151 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 152 | .. blockdiag:: |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 153 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 154 | blockdiag foo { |
| 155 | node_width = 40; |
| 156 | span_width = 40; |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 157 | |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 158 | C [color = pink]; |
| 159 | D [label = "D\n?"]; |
| 160 | E [label = "E\n?"]; |
| 161 | |
| 162 | C <- D <- E; |
| 163 | } |
| 164 | |
| 165 | Since *C* failed, Zuul will report its failure and drop *C* from the queue, |
| 166 | keeping D and E: |
| 167 | |
| 168 | .. blockdiag:: |
| 169 | |
| 170 | blockdiag foo { |
| 171 | node_width = 40; |
| 172 | span_width = 40; |
| 173 | |
| 174 | D [label = "D\n?"]; |
| 175 | E [label = "E\n?"]; |
| 176 | |
| 177 | D <- E; |
| 178 | } |
James E. Blair | cdd0007 | 2012-06-08 19:17:28 -0700 | [diff] [blame] | 179 | |
| 180 | This queue is the same as if two new changes had just arrived, so Zuul |
| 181 | starts the process again testing *D* against the tip of the branch, and |
Antoine Musso | 3a43e14 | 2013-10-30 23:51:58 +0100 | [diff] [blame] | 182 | *E* against *D*: |
| 183 | |
| 184 | .. blockdiag:: |
| 185 | |
| 186 | blockdiag foo { |
| 187 | node_width = 40; |
| 188 | span_width = 40; |
| 189 | master -> D -> E; |
| 190 | group jobs_for_D { |
| 191 | label = "Merged changes for D"; |
| 192 | master -> D; |
| 193 | } |
| 194 | group ignored_to_test_D { |
| 195 | label = "Skip"; |
| 196 | color = "lightgray"; |
| 197 | E; |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | .. blockdiag:: |
| 202 | |
| 203 | blockdiag foo { |
| 204 | node_width = 40; |
| 205 | span_width = 40; |
| 206 | group jobs_for_E { |
| 207 | label = "Merged changes for E"; |
| 208 | master -> D -> E; |
| 209 | } |
| 210 | } |
| 211 | |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 212 | |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 213 | Cross Project Testing |
| 214 | --------------------- |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 215 | |
| 216 | When your projects are closely coupled together, you want to make sure |
| 217 | changes entering the gate are going to be tested with the version of |
| 218 | other projects currently enqueued in the gate (since they will |
| 219 | eventually be merged and might introduce breaking features). |
| 220 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 221 | Such relationships can be defined in Zuul configuration by placing |
| 222 | projects in a shared queue within a dependent pipeline. Whenever |
| 223 | changes for any project enter a pipeline with such a shared queue, |
| 224 | they are tested together, such that the commits for the changes ahead |
| 225 | in the queue are automatically present in the jobs for the changes |
| 226 | behind them. See :ref:`project` for more details. |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 227 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 228 | A given dependent pipeline may have as many shared change queues as |
| 229 | necessary, so groups of related projects may share a change queue |
David Shrewsbury | b040b0a | 2017-08-03 15:53:59 -0400 | [diff] [blame] | 230 | without interfering with unrelated projects. |
| 231 | :value:`Independent pipelines <pipeline.manager.independent>` do |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 232 | not use shared change queues, however, they may still be used to test |
| 233 | changes across projects using cross-project dependencies. |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 234 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 235 | .. _dependencies: |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 236 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 237 | Cross-Project Dependencies |
| 238 | -------------------------- |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 239 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 240 | Zuul permits users to specify dependencies across projects. Using a |
| 241 | special footer in Git commit messages, users may specify that a change |
| 242 | depends on another change in any repository known to Zuul. |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 243 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 244 | Zuul's cross-project dependencies behave like a directed acyclic graph |
| 245 | (DAG), like git itself, to indicate a one-way dependency relationship |
| 246 | between changes in different git repositories. Change A may depend on |
| 247 | B, but B may not depend on A. |
Antoine Musso | 5586753 | 2014-01-10 18:24:35 +0100 | [diff] [blame] | 248 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 249 | To use them, include ``Depends-On: <change-url>`` in the footer of a |
| 250 | commit message. For example, a change which depends on a GitHub pull |
| 251 | request (PR #4) might have the following footer:: |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 252 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 253 | Depends-On: https://github.com/example/test/pull/4 |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 254 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 255 | And a change which depends on a Gerrit change (change number 3):: |
| 256 | |
| 257 | Depends-On: https://review.example.com/3 |
| 258 | |
| 259 | Changes may depend on changes in any other project, even projects not |
| 260 | on the same system (i.e., a Gerrit change may depend on a GitHub pull |
| 261 | request). |
| 262 | |
| 263 | .. note:: |
| 264 | |
| 265 | An older syntax of specifying dependencies using Gerrit change-ids |
| 266 | is still supported, however it is deprecated and will be removed in |
| 267 | a future version. |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 268 | |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 269 | Dependent Pipeline |
| 270 | ~~~~~~~~~~~~~~~~~~ |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 271 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 272 | When Zuul sees changes with cross-project dependencies, it serializes |
| 273 | them in the usual manner when enqueuing them into a pipeline. This |
| 274 | means that if change A depends on B, then when they are added to a |
| 275 | dependent pipeline, B will appear first and A will follow: |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 276 | |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 277 | .. blockdiag:: |
| 278 | :align: center |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 279 | |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 280 | blockdiag crd { |
| 281 | orientation = portrait |
| 282 | span_width = 30 |
| 283 | class greendot [ |
| 284 | label = "", |
| 285 | shape = circle, |
| 286 | color = green, |
| 287 | width = 20, height = 20 |
| 288 | ] |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 289 | |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 290 | A_status [ class = greendot ] |
| 291 | B_status [ class = greendot ] |
| 292 | B_status -- A_status |
| 293 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 294 | 'Change B\nURL: .../4' <- 'Change A\nDepends-On: .../4' |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 295 | } |
| 296 | |
| 297 | If tests for B fail, both B and A will be removed from the pipeline, and |
| 298 | it will not be possible for A to merge until B does. |
| 299 | |
| 300 | |
| 301 | .. note:: |
| 302 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 303 | If changes with cross-project dependencies do not share a change |
| 304 | queue then Zuul is unable to enqueue them together, and the first |
| 305 | will be required to merge before the second is enqueued. |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 306 | |
| 307 | Independent Pipeline |
| 308 | ~~~~~~~~~~~~~~~~~~~~ |
| 309 | |
| 310 | When changes are enqueued into an independent pipeline, all of the |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 311 | related dependencies (both normal git-dependencies that come from |
| 312 | parent commits as well as cross-project dependencies) appear in a |
| 313 | dependency graph, as in a dependent pipeline. This means that even in |
| 314 | an independent pipeline, your change will be tested with its |
| 315 | dependencies. Changes that were previously unable to be fully tested |
| 316 | until a related change landed in a different repository may now be |
| 317 | tested together from the start. |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 318 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 319 | All of the changes are still independent (you will note that the whole |
| 320 | pipeline does not share a graph as in a dependent pipeline), but for |
| 321 | each change tested, all of its dependencies are visually connected to |
| 322 | it, and they are used to construct the git repositories that Zuul uses |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 323 | when testing. |
| 324 | |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 325 | When looking at this graph on the status page, you will note that the |
| 326 | dependencies show up as grey dots, while the actual change tested shows |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 327 | up as red or green (depending on the jobs results): |
| 328 | |
| 329 | .. blockdiag:: |
| 330 | :align: center |
| 331 | |
| 332 | blockdiag crdgrey { |
| 333 | orientation = portrait |
| 334 | span_width = 30 |
| 335 | class dot [ |
| 336 | label = "", |
| 337 | shape = circle, |
| 338 | width = 20, height = 20 |
| 339 | ] |
| 340 | |
| 341 | A_status [class = "dot", color = green] |
| 342 | B_status [class = "dot", color = grey] |
| 343 | B_status -- A_status |
| 344 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 345 | "Change B\nURL: .../4" <- "Change A\nDepends-On: .../4" |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 346 | } |
| 347 | |
| 348 | This is to indicate that the grey changes are only there to establish |
| 349 | dependencies. Even if one of the dependencies is also being tested, it |
| 350 | will show up as a grey dot when used as a dependency, but separately and |
| 351 | additionally will appear as its own red or green dot for its test. |
| 352 | |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 353 | |
| 354 | Multiple Changes |
| 355 | ~~~~~~~~~~~~~~~~ |
| 356 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 357 | A change may list more than one dependency by simply adding more |
| 358 | ``Depends-On:`` lines to the commit message footer. It is possible |
| 359 | for a change in project A to depend on a change in project B and a |
| 360 | change in project C. |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 361 | |
| 362 | .. blockdiag:: |
| 363 | :align: center |
| 364 | |
| 365 | blockdiag crdmultichanges { |
| 366 | orientation = portrait |
| 367 | span_width = 30 |
| 368 | class greendot [ |
| 369 | label = "", |
| 370 | shape = circle, |
| 371 | color = green, |
| 372 | width = 20, height = 20 |
| 373 | ] |
| 374 | |
| 375 | C_status [ class = "greendot" ] |
| 376 | B_status [ class = "greendot" ] |
| 377 | A_status [ class = "greendot" ] |
| 378 | C_status -- B_status -- A_status |
| 379 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 380 | A [ label = "Repo A\nDepends-On: .../3\nDepends-On: .../4" ] |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 381 | group { |
| 382 | orientation = portrait |
| 383 | label = "Dependencies" |
| 384 | color = "lightgray" |
| 385 | |
James E. Blair | c84736e | 2018-01-16 09:34:09 -0800 | [diff] [blame] | 386 | B [ label = "Repo B\nURL: .../3" ] |
| 387 | C [ label = "Repo C\nURL: .../4" ] |
Antoine Musso | 281a89e | 2015-09-30 15:05:49 +0200 | [diff] [blame] | 388 | } |
| 389 | B, C <- A |
| 390 | } |
James E. Blair | 096a80a | 2015-09-28 14:19:31 -0700 | [diff] [blame] | 391 | |
| 392 | Cycles |
| 393 | ~~~~~~ |
| 394 | |
James E. Blair | eff5a9d | 2017-06-20 00:00:37 -0700 | [diff] [blame] | 395 | If a cycle is created by use of cross-project dependencies, Zuul will |
| 396 | abort its work very early. There will be no message in Gerrit and no |
| 397 | changes that are part of the cycle will be enqueued into any pipeline. |
| 398 | This is to protect Zuul from infinite loops. |