Don't store pipeline references on builds

On full and tenant reconfiguration, we construct new pipeline objects
and re-enqueue all of the items into them.  Because of this, we need
to be careful with references to pipelines.  Generally we use the
forward references from the Layout, but we also regularly make use
of backward references from QueueItems.  When we re-enqueue items
we take care to update these backward references (or clear them
if we are unable to re-enqueue them).

Unfortunately, we missed another backward reference, those from
Build objects.  This cas cause old pipelines (and, because they
have backward references to their layouts, entire old layouts) to
persist as long as there is a build in the system referencing them.
And because we keep all of the builds for an item in the previous
buildset records, that can be as long as an item is in the system.

Which can be a very long time on bad days.

To correct, remove the pipeline backref from Build and replace it
with a property method which finds the pipeline by way of the
build's buildset and then its item, which should be safely updated
on re-enqueing, as described above.

Add a test which verifies no extra pipeline objects persist.

Change-Id: I837c0eb8f49ea238a1d5ca2435acb8b245f4a871
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 1c633ba..78524f2 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -17,6 +17,8 @@
 import json
 import os
 import textwrap
+import gc
+from unittest import skip
 
 import testtools
 
@@ -170,6 +172,39 @@
         self.assertIn('tenant-one-gate', A.messages[1],
                       "A should transit tenant-one gate")
 
+    @skip("This test is useful, but not reliable")
+    def test_full_and_dynamic_reconfig(self):
+        self.executor_server.hold_jobs_in_build = True
+        in_repo_conf = textwrap.dedent(
+            """
+            - job:
+                name: project-test1
+
+            - project:
+                name: org/project
+                tenant-one-gate:
+                  jobs:
+                    - project-test1
+            """)
+
+        file_dict = {'.zuul.yaml': in_repo_conf}
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+                                           files=file_dict)
+        A.addApproval('Code-Review', 2)
+        self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
+        self.waitUntilSettled()
+        self.sched.reconfigure(self.config)
+        self.waitUntilSettled()
+
+        gc.collect()
+        pipelines = [obj for obj in gc.get_objects()
+                     if isinstance(obj, zuul.model.Pipeline)]
+        self.assertEqual(len(pipelines), 4)
+
+        self.executor_server.hold_jobs_in_build = False
+        self.executor_server.release()
+        self.waitUntilSettled()
+
     def test_dynamic_config(self):
         in_repo_conf = textwrap.dedent(
             """