Merge lp:~robru/cupstream2distro/parallelize-migration into lp:cupstream2distro

Proposed by Robert Bruce Park
Status: Merged
Approved by: Robert Bruce Park
Approved revision: 1233
Merged at revision: 1227
Proposed branch: lp:~robru/cupstream2distro/parallelize-migration
Merge into: lp:cupstream2distro
Diff against target: 400 lines (+108/-134)
6 files modified
citrain/jenkins-templates/status.xml.tmpl (+31/-11)
citrain/setup_citrain.py (+6/-2)
citrain/status.py (+16/-30)
files/config.xml (+22/-0)
tests/unit/test_script_setup_citrain.py (+5/-2)
tests/unit/test_script_status.py (+28/-89)
To merge this branch: bzr merge lp:~robru/cupstream2distro/parallelize-migration
Reviewer Review Type Date Requested Status
Robert Bruce Park (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+278121@code.launchpad.net

Commit message

Parallelize migration script.

To post a comment you must log in.
Revision history for this message
Robert Bruce Park (robru) wrote :

https://ci-train.staging.ubuntu.com/view/0.%20Status/

I'm enjoying this in staging, just need to write tests.

1232. By Robert Bruce Park

Run with 10 minute frequency.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1232
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/913/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/913/rebuild

review: Approve (continuous-integration)
1233. By Robert Bruce Park

Keep more logs but fewer artifacts.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1233
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/914/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/914/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robert Bruce Park (robru) wrote :

This loks amazing in staging.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== renamed file 'citrain/jenkins-templates/check-publication-migration.xml.tmpl' => 'citrain/jenkins-templates/status.xml.tmpl'
--- citrain/jenkins-templates/check-publication-migration.xml.tmpl 2015-11-18 05:51:13 +0000
+++ citrain/jenkins-templates/status.xml.tmpl 2015-11-20 11:31:16 +0000
@@ -1,25 +1,25 @@
1<?xml version='1.0' encoding='UTF-8'?>1<?xml version='1.0' encoding='UTF-8'?>
2<project>2<project>
3 <actions/>3 <actions/>
4 <description>Automatically check destination migration for packages.</description>4 <description>Regularly refresh silo status.</description>
5 <logRotator>5 <logRotator>
6 <daysToKeep>1</daysToKeep>6 <daysToKeep>7</daysToKeep>
7 <numToKeep>500</numToKeep>7 <numToKeep>-1</numToKeep>
8 <artifactDaysToKeep>-1</artifactDaysToKeep>8 <artifactDaysToKeep>-1</artifactDaysToKeep>
9 <artifactNumToKeep>-1</artifactNumToKeep>9 <artifactNumToKeep>5</artifactNumToKeep>
10 </logRotator>10 </logRotator>
11 <keepDependencies>false</keepDependencies>11 <keepDependencies>false</keepDependencies>
12 <properties>12 <properties>
13 <hudson.security.AuthorizationMatrixProperty>13 <hudson.security.AuthorizationMatrixProperty>
14 <permission>hudson.model.Item.Cancel:ubuntu-core-dev</permission>14 <permission>hudson.model.Item.Cancel:ubuntu-core-dev</permission>
15 <permission>hudson.model.Item.Cancel:canonical-ci-eng</permission>15 <permission>hudson.model.Item.Cancel:canonical-ci-eng</permission>
16 <permission>hudson.model.Item.Cancel:ci-train-users</permission>16 <permission>hudson.model.Item.Cancel:ci-train-ppa-service</permission>
17 <permission>hudson.model.Item.Build:ubuntu-core-dev</permission>17 <permission>hudson.model.Item.Build:ubuntu-core-dev</permission>
18 <permission>hudson.model.Item.Build:canonical-ci-eng</permission>18 <permission>hudson.model.Item.Build:canonical-ci-eng</permission>
19 <permission>hudson.model.Item.Build:ci-train-users</permission>19 <permission>hudson.model.Item.Build:ci-train-ppa-service</permission>
20 <permission>hudson.model.Item.Read:ubuntu-core-dev</permission>20 <permission>hudson.model.Item.Read:ubuntu-core-dev</permission>
21 <permission>hudson.model.Item.Read:canonical-ci-eng</permission>21 <permission>hudson.model.Item.Read:canonical-ci-eng</permission>
22 <permission>hudson.model.Item.Read:ci-train-users</permission>22 <permission>hudson.model.Item.Read:ci-train-ppa-service</permission>
23 </hudson.security.AuthorizationMatrixProperty>23 </hudson.security.AuthorizationMatrixProperty>
24 <hudson.model.ParametersDefinitionProperty>24 <hudson.model.ParametersDefinitionProperty>
25 <parameterDefinitions>25 <parameterDefinitions>
@@ -38,7 +38,7 @@
38 <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>38 <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
39 <triggers>39 <triggers>
40 <hudson.triggers.TimerTrigger>40 <hudson.triggers.TimerTrigger>
41 <spec>H/30 * * * *</spec>41 <spec>H/10 * * * *</spec>
42 </hudson.triggers.TimerTrigger>42 </hudson.triggers.TimerTrigger>
43 </triggers>43 </triggers>
44 <concurrentBuild>false</concurrentBuild>44 <concurrentBuild>false</concurrentBuild>
@@ -46,15 +46,35 @@
46 <hudson.tasks.Shell>46 <hudson.tasks.Shell>
47 <command>#!/bin/sh47 <command>#!/bin/sh
48export LANG=en_US.UTF-848export LANG=en_US.UTF-8
49export WORKSPACE="$PWD"
50export SILONAME="{SILO_NAME}"
49export PYTHONPATH={LIBDIR}51export PYTHONPATH={LIBDIR}
5052
51cd # go to home directory53[ "$DEBUG" = "true" ] &amp;&amp; set -x
5254
53{BINDIR}/migration.py55[ -e {SILOS_DIR}/{SILO_NAME}/request_id_* ] || exit 0
56
57rm -rf $WORKSPACE/*
58
59cd {SILOS_DIR}/{SILO_NAME}
60
61{BINDIR}/status.py
62RETVAL=$?
63
64cp *.diff "$WORKSPACE" 2&gt;/dev/null || true
65
66exit $RETVAL
54</command>67</command>
55 </hudson.tasks.Shell>68 </hudson.tasks.Shell>
56 </builders>69 </builders>
57 <publishers>70 <publishers>
71 <hudson.tasks.ArtifactArchiver>
72 <artifacts>*.diff</artifacts>
73 <allowEmptyArchive>true</allowEmptyArchive>
74 <onlyIfSuccessful>false</onlyIfSuccessful>
75 <fingerprint>false</fingerprint>
76 <defaultExcludes>true</defaultExcludes>
77 </hudson.tasks.ArtifactArchiver>
58 </publishers>78 </publishers>
59 <buildWrappers/>79 <buildWrappers/>
60</project>80</project>
6181
=== modified file 'citrain/setup_citrain.py'
--- citrain/setup_citrain.py 2015-11-04 16:32:54 +0000
+++ citrain/setup_citrain.py 2015-11-20 11:31:16 +0000
@@ -101,7 +101,12 @@
101 """Setup a silo jenkins job."""101 """Setup a silo jenkins job."""
102 context = dict(SILOS_DIR=SILOS_DIR, SILO_NAME=siloname)102 context = dict(SILOS_DIR=SILOS_DIR, SILO_NAME=siloname)
103 siloname = siloname.replace('/', '-')103 siloname = siloname.replace('/', '-')
104 steps = dict(build='1', publish='2', autopkgtests='2.5', merge_clean='3')104 steps = dict(
105 status='0',
106 build='1',
107 publish='2',
108 autopkgtests='2.5',
109 merge_clean='3')
105 for job, i in sorted(steps.items()):110 for job, i in sorted(steps.items()):
106 job = job.replace('_', '-')111 job = job.replace('_', '-')
107 setup_job('{}-{}-{}'.format(siloname, i, job), template(job), context)112 setup_job('{}-{}-{}'.format(siloname, i, job), template(job), context)
@@ -114,7 +119,6 @@
114 setup_silo(siloname)119 setup_silo(siloname)
115 setup_job('cyphermox-test')120 setup_job('cyphermox-test')
116 setup_job('prepare-silo')121 setup_job('prepare-silo')
117 setup_job('check-publication-migration')
118 setup_job('staleness-report')122 setup_job('staleness-report')
119 setup_job('upgrade-chroot')123 setup_job('upgrade-chroot')
120 setup_job('apt-get-clean')124 setup_job('apt-get-clean')
121125
=== renamed file 'citrain/migration.py' => 'citrain/status.py'
--- citrain/migration.py 2015-11-19 23:08:21 +0000
+++ citrain/status.py 2015-11-20 11:31:16 +0000
@@ -15,9 +15,9 @@
15# this program; if not, write to the Free Software Foundation, Inc.,15# this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1717
18"""CI Train Migration Script18"""CI Train Status Script
1919
20Check if all silo packages have migrated, and if so, trigger merge & clean.20Update silo status, also triggering Merge&Clean if silo is ready.
2121
22Environment variables:22Environment variables:
2323
@@ -30,36 +30,22 @@
3030
31from citrain.recipes.base import BuildBase31from citrain.recipes.base import BuildBase
32from citrain.recipes.manager import Manager32from citrain.recipes.manager import Manager
33from cupstream2distro.utils import env, run_script33from cupstream2distro.utils import run_script
34from cupstream2distro.silomanager import SiloState, stock_main34from cupstream2distro.silomanager import stock_main
35from citrain.merge_clean import merge35from citrain.merge_clean import merge
3636
37SUCCESS = re.compile(r'^((Release|Updates) pocket( \([^()]+\). ?)?)+$')37SUCCESS = re.compile(r'^((Release|Updates) pocket( \([^()]+\). ?)?)+$')
3838
3939
40def main():40def update_status(silo_state):
41 """Execute the migration check, logging & saving any errors.41 """Set the status for this silo."""
4242 silo_state.lock_fd.close()
43 :returns: 0. Always.43 BuildBase.failures.clear()
44 """44 logging.info('Inspecting PPA %s:', silo_state.ppa.web_link)
45 for silo_state in SiloState.iterate():45 status = silo_state.status = Manager(silo_state).get_states()
46 env.SILONAME = silo_state46 if SUCCESS.match(status):
47 BuildBase.failures.clear()47 logging.info('Looks good, proceeding with merge & clean.')
48 logging.info('\n\nInspecting silo %s:', silo_state.ppa.web_link)48 stock_main(merge)
49 try:49
50 silo_state.enforce_lock()50
51 silo_state.lock_fd.close()51run_script(__name__, __doc__, lambda: stock_main(update_status))
52 except BlockingIOError:
53 logging.info('Silo %s is in use, skipping.', env.SILONAME)
54 continue
55
56 status = silo_state.status = Manager(silo_state).get_states()
57 if SUCCESS.match(status):
58 logging.info('Looks good, proceeding with merge & clean.')
59 stock_main(merge)
60 continue
61 silo_state.save_config()
62 return 0
63
64
65run_script(__name__, __doc__, main)
6652
=== modified file 'files/config.xml'
--- files/config.xml 2015-10-28 23:28:38 +0000
+++ files/config.xml 2015-11-20 11:31:16 +0000
@@ -202,6 +202,28 @@
202 </hudson.model.AllView>202 </hudson.model.AllView>
203 <listView>203 <listView>
204 <owner class="hudson" reference="../../.."/>204 <owner class="hudson" reference="../../.."/>
205 <name>0. Status</name>
206 <filterExecutors>false</filterExecutors>
207 <filterQueue>false</filterQueue>
208 <properties class="hudson.model.View$PropertyList"/>
209 <jobNames>
210 <comparator class="hudson.util.CaseInsensitiveComparator"/>
211 </jobNames>
212 <jobFilters/>
213 <columns>
214 <hudson.views.StatusColumn/>
215 <hudson.views.WeatherColumn/>
216 <hudson.views.JobColumn/>
217 <hudson.views.LastSuccessColumn/>
218 <hudson.views.LastFailureColumn/>
219 <hudson.views.LastDurationColumn/>
220 <hudson.views.BuildButtonColumn/>
221 </columns>
222 <includeRegex>.*-0-status</includeRegex>
223 <recurse>false</recurse>
224 </listView>
225 <listView>
226 <owner class="hudson" reference="../../.."/>
205 <name>1. Build</name>227 <name>1. Build</name>
206 <filterExecutors>false</filterExecutors>228 <filterExecutors>false</filterExecutors>
207 <filterQueue>false</filterQueue>229 <filterQueue>false</filterQueue>
208230
=== modified file 'tests/unit/test_script_setup_citrain.py'
--- tests/unit/test_script_setup_citrain.py 2015-11-04 16:32:54 +0000
+++ tests/unit/test_script_setup_citrain.py 2015-11-20 11:31:16 +0000
@@ -123,6 +123,10 @@
123 'publish.xml.tmpl',123 'publish.xml.tmpl',
124 {'SILO_NAME': 'ubuntu/landing-000',124 {'SILO_NAME': 'ubuntu/landing-000',
125 'SILOS_DIR': os.path.expanduser('~/silos')}),125 'SILOS_DIR': os.path.expanduser('~/silos')}),
126 call('ubuntu-landing-000-0-status',
127 'status.xml.tmpl',
128 {'SILO_NAME': 'ubuntu/landing-000',
129 'SILOS_DIR': os.path.expanduser('~/silos')}),
126 ])130 ])
127131
128 def test_main(self):132 def test_main(self):
@@ -137,7 +141,6 @@
137 self.assertEqual(self.script.setup_job.mock_calls, [141 self.assertEqual(self.script.setup_job.mock_calls, [
138 call('cyphermox-test'),142 call('cyphermox-test'),
139 call('prepare-silo'),143 call('prepare-silo'),
140 call('check-publication-migration'),
141 call('staleness-report'),144 call('staleness-report'),
142 call('upgrade-chroot'),145 call('upgrade-chroot'),
143 call('apt-get-clean'),146 call('apt-get-clean'),
@@ -152,11 +155,11 @@
152 'abandon/config.xml',155 'abandon/config.xml',
153 'apt-get-clean/config.xml',156 'apt-get-clean/config.xml',
154 'cyphermox-test/config.xml',157 'cyphermox-test/config.xml',
155 'check-publication-migration/config.xml',
156 'pbuilder-clean/config.xml',158 'pbuilder-clean/config.xml',
157 'prepare-silo/config.xml',159 'prepare-silo/config.xml',
158 'revert/config.xml',160 'revert/config.xml',
159 'staleness-report/config.xml',161 'staleness-report/config.xml',
162 'ubuntu-landing-{:03d}-0-status/config.xml',
160 'ubuntu-landing-{:03d}-1-build/config.xml',163 'ubuntu-landing-{:03d}-1-build/config.xml',
161 'ubuntu-landing-{:03d}-2-publish/config.xml',164 'ubuntu-landing-{:03d}-2-publish/config.xml',
162 'ubuntu-landing-{:03d}-2.5-autopkgtests/config.xml',165 'ubuntu-landing-{:03d}-2.5-autopkgtests/config.xml',
163166
=== renamed file 'tests/unit/test_script_migration.py' => 'tests/unit/test_script_status.py'
--- tests/unit/test_script_migration.py 2015-11-19 23:08:21 +0000
+++ tests/unit/test_script_status.py 2015-11-20 11:31:16 +0000
@@ -14,31 +14,25 @@
14# this program; if not, write to the Free Software Foundation, Inc.,14# this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616
17"""Tests for CI Train Migration script."""17"""Tests for CI Train Status script."""
1818
19from os.path import join
20from mock import Mock, call19from mock import Mock, call
2120
22from tests.unit import CITrainScriptTestCase21from tests.unit import CITrainScriptTestCase
2322
24from cupstream2distro.settings import SILOS_DIR
25
26# Unused import necessary for code coverage reporting23# Unused import necessary for code coverage reporting
27from citrain import migration24from citrain import status
28COVERAGE = [migration]25COVERAGE = [status]
2926
3027
31class MigrationTestCase(CITrainScriptTestCase):28class StatusTestCase(CITrainScriptTestCase):
32 """Tests for CI Train Migration script."""29 """Tests for CI Train Status script."""
33 scriptname = 'migration.py'30 scriptname = 'status.py'
3431
35 def setUp(self):32 def setUp(self):
36 super().setUp()33 super().setUp()
37 self.script.Manager = Mock()34 self.script.Manager = Mock()
38 self.script.stock_main = Mock()35 self.script.stock_main = Mock()
39 self.script.glob.return_value = [
40 join(SILOS_DIR, 'ubuntu', 'landing-00{}'.format(x))
41 for x in range(3)]
4236
43 def test_success_regex(self):37 def test_success_regex(self):
44 """Ensure we can properly recognize silos ready to auto-merge."""38 """Ensure we can properly recognize silos ready to auto-merge."""
@@ -60,89 +54,34 @@
60 results = [bool(self.script.SUCCESS.match(stat)) for stat in bad]54 results = [bool(self.script.SUCCESS.match(stat)) for stat in bad]
61 self.assertEqual(results, [False] * len(bad))55 self.assertEqual(results, [False] * len(bad))
6256
63 def test_main(self):57 def test_status(self):
64 """Trigger merges after successful migrations."""58 """Trigger merges after successful migrations."""
65 tokenize = lambda: Mock(return_value={'hi'})59 tokenize = lambda: Mock(return_value={'hi'})
66 states = self.script.SiloState.iterate.return_value = [Mock(60 silo_state = Mock(tokenize=tokenize())
67 tokenize=tokenize(),
68 is_published=True,
69 is_autopkgtesting=False) for rid in range(3)]
70 mgr = self.script.Manager.return_value61 mgr = self.script.Manager.return_value
71 mgr.get_states.return_value = 'Release pocket (foo).'62 mgr.get_states.return_value = 'Release pocket (foo).'
72 self.assertEqual(self.script.main(), 0)63 self.assertIsNone(self.script.update_status(silo_state))
73 for state in states:64 self.assertEqual(silo_state.mock_calls, [
74 self.assertEqual(state.mock_calls, [65 call.lock_fd.close(),
75 call.enforce_lock(),66 ])
76 call.lock_fd.close(),
77 ])
78 self.assertEqual(self.script.Manager.mock_calls, [67 self.assertEqual(self.script.Manager.mock_calls, [
79 call(states[0]),68 call(silo_state),
80 call().get_states(),
81 call(states[1]),
82 call().get_states(),
83 call(states[2]),
84 call().get_states(),69 call().get_states(),
85 ])70 ])
86 self.assertEqual(self.script.stock_main.mock_calls, [71 self.assertEqual(self.script.stock_main.mock_calls, [
87 call(self.script.merge)72 call(self.script.merge)
88 ] * 3)73 ])
8974
90 def test_main_skip_empty_silos(self):75 def test_status_skip_unpublished_silos(self):
91 """Don't check silos that are empty."""
92 self.script.SiloState.iterate.return_value = []
93 self.assertEqual(self.script.main(), 0)
94 self.assertEqual(self.script.Manager.mock_calls, [])
95 self.assertEqual(self.script.stock_main.mock_calls, [])
96
97 def test_main_skip_locked_silos(self):
98 """Skip over silos that are currently used by other processes."""
99 states = self.script.SiloState.iterate.return_value = [Mock()]
100 states[0].enforce_lock.side_effect = BlockingIOError
101 self.assertEqual(self.script.main(), 0)
102 self.assertEqual(self.script.Manager.mock_calls, [])
103 self.assertEqual(self.script.stock_main.mock_calls, [])
104 self.assertEqual(states[0].mock_calls, [call.enforce_lock()])
105
106 def test_main_skip_unpublished_silos(self):
107 """Don't merge silos that are unpublished."""76 """Don't merge silos that are unpublished."""
108 tokenize = Mock(return_value={'hi'})77 tokenize = Mock(return_value={'hi'})
109 states = self.script.SiloState.iterate.return_value = [78 silo_state = Mock(tokenize=tokenize)
110 Mock(tokenize=tokenize, is_published=False) for rid in range(3)]79 mgr = self.script.Manager.return_value
111 mgr = self.script.Manager.return_value80 mgr.get_states.return_value = 'Proposed pocket (foo).'
112 mgr.get_states.return_value = 'Proposed pocket (foo).'81 self.assertIsNone(self.script.update_status(silo_state))
113 self.assertEqual(self.script.main(), 0)82 self.assertEqual(self.script.Manager.mock_calls, [
114 self.assertEqual(self.script.Manager.mock_calls, [83 call(silo_state),
115 call(states[0]),84 call().get_states(),
116 call().get_states(),85 ])
117 call(states[1]),86 self.assertEqual(self.script.stock_main.mock_calls, [])
118 call().get_states(),87 self.assertEqual(silo_state.save_config.mock_calls, [])
119 call(states[2]),
120 call().get_states(),
121 ])
122 self.assertEqual(self.script.stock_main.mock_calls, [])
123 for state in states:
124 self.assertEqual(state.set_migrating.mock_calls, [])
125 self.assertEqual(state.save_config.mock_calls, [call()])
126
127 def test_main_dont_merge_migrating_silos(self):
128 """Don't merge silos that are still migrating."""
129 tokenize = Mock(return_value={'hi'})
130 states = self.script.SiloState.iterate.return_value = [Mock(
131 tokenize=tokenize,
132 is_published=True,
133 requestid=str(1),
134 )]
135 mgr = self.script.Manager.return_value
136 mgr.get_states.return_value = 'Proposed pocket (foo).'
137 self.assertEqual(self.script.main(), 0)
138 self.assertEqual(states[0].status, mgr.get_states.return_value)
139 self.assertEqual(states[0].mock_calls, [
140 call.enforce_lock(),
141 call.lock_fd.close(),
142 call.save_config(),
143 ])
144 self.assertEqual(self.script.Manager.mock_calls, [
145 call(states[0]),
146 call().get_states(),
147 ])
148 self.assertEqual(self.script.stock_main.mock_calls, [])

Subscribers

People subscribed via source and target branches