Merge lp:~joetalbott/core-image-builder/add_build_logic into lp:core-image-builder

Proposed by Joe Talbott
Status: Merged
Approved by: Joe Talbott
Approved revision: 5
Merged at revision: 3
Proposed branch: lp:~joetalbott/core-image-builder/add_build_logic
Merge into: lp:core-image-builder
Diff against target: 304 lines (+169/-37)
5 files modified
core_image_builder/__init__.py (+3/-3)
core_image_builder/queue.py (+2/-2)
core_image_builder/worker.py (+151/-32)
requirements.txt (+11/-0)
test_requirements.txt (+2/-0)
To merge this branch: bzr merge lp:~joetalbott/core-image-builder/add_build_logic
Reviewer Review Type Date Requested Status
Joe Talbott (community) Approve
Francis Ginther Approve
Review via email: mp+259834@code.launchpad.net

Commit message

Add first pass at image building logic.

Description of the change

Add first pass at image building logic.

To post a comment you must log in.
Revision history for this message
Para Siva (psivaa) wrote :

Could not find any issues except a question inline.

Revision history for this message
Francis Ginther (fginther) wrote :

I've logged several comments, but nothing that should block this current MP. In fact, I would feel better landing this and then iterating on the comments in other MPs.

Also, it is my opinion that the channel and release input needs to come from the core-tester-agent. This is to solve the problem of needing to test both vivid and wily packages that land into two different core channels. Perhaps there is another solution that I've overlooked.

review: Approve
Revision history for this message
Ubuntu CI Bot (uci-bot) wrote :
Download full text (24.0 KiB)

The attempt to merge lp:~joetalbott/core-image-builder/add_build_logic into lp:core-image-builder failed. Below is the output from the failed tests.

Using base prefix '/usr'
New python executable in /tmp/venv-core-image-builder7hpvuyab/bin/python3
Also creating executable in /tmp/venv-core-image-builder7hpvuyab/bin/python
Installing setuptools, pip...done.
Running virtualenv with interpreter /usr/bin/python3
Ignoring indexes: https://pypi.python.org/simple/
Downloading/unpacking cffi (from -r requirements.txt (line 5))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/cffi/setup.py) egg_info for package cffi

Downloading/unpacking pyasn1 (from -r requirements.txt (line 6))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/pyasn1/setup.py) egg_info for package pyasn1

Downloading/unpacking kombu==3.0.24 (from -r requirements.txt (line 7))
Downloading/unpacking python-logstash==0.4.2 (from -r requirements.txt (line 8))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/python-logstash/setup.py) egg_info for package python-logstash

Downloading/unpacking python-glanceclient==0.17.0 (from -r requirements.txt (line 9))
Downloading/unpacking uservice-utils==1.0.3 (from -r requirements.txt (line 10))
Downloading/unpacking traceback2==1.4.0 (from -r requirements.txt (line 11))
Downloading/unpacking pycparser (from cffi->-r requirements.txt (line 5))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/pycparser/setup.py) egg_info for package pycparser

    warning: no previously-included files matching 'yacctab.*' found under directory 'tests'
    warning: no previously-included files matching 'lextab.*' found under directory 'tests'
    warning: no previously-included files matching 'yacctab.*' found under directory 'examples'
    warning: no previously-included files matching 'lextab.*' found under directory 'examples'
Downloading/unpacking anyjson>=0.3.3 (from kombu==3.0.24->-r requirements.txt (line 7))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/anyjson/setup.py) egg_info for package anyjson

Downloading/unpacking amqp>=1.4.5,<2.0 (from kombu==3.0.24->-r requirements.txt (line 7))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/amqp/setup.py) egg_info for package amqp

Downloading/unpacking argparse (from python-glanceclient==0.17.0->-r requirements.txt (line 9))
Downloading/unpacking python-keystoneclient>=1.0.0 (from python-glanceclient==0.17.0->-r requirements.txt (line 9))
Downloading/unpacking warlock>=1.0.1,<2 (from python-glanceclient==0.17.0->-r requirements.txt (line 9))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/warlock/setup.py) egg_info for package warlock

Downloading/unpacking Babel>=1.3 (from python-glanceclient==0.17.0->-r requirements.txt (line 9))
  Running setup.py (path:/tmp/venv-core-image-builder7hpvuyab/build/Babel/setup.py) egg_info for package Babel

    warning: no previously-included files matching '*' found under directory 'docs/_build'
    warning: no previously-included files matching '*.pyc' found under directory 'tests'
    warning: no previously-...

5. By Joe Talbott

Fix flake8 warning

Revision history for this message
Joe Talbott (joetalbott) wrote :

One more commit to fix a silly flake8 warning. I'm self-approving since it's so minor.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'core_image_builder/__init__.py'
2--- core_image_builder/__init__.py 2015-05-19 21:37:40 +0000
3+++ core_image_builder/__init__.py 2015-05-26 18:29:22 +0000
4@@ -28,7 +28,7 @@
5
6 from core_image_builder.queue import (
7 create_connection_from_config,
8- CoreImageResultBuilder,
9+ CoreImageBuilder,
10 )
11 from core_image_builder import (
12 constants,
13@@ -67,14 +67,14 @@
14 connection = create_connection_from_config(config)
15 try:
16 with connection:
17- builder = CoreImageResultBuilder(connection)
18+ builder = CoreImageBuilder(connection)
19 retry_policy = DefaultRetryPolicy(
20 max_retries=3,
21 dead_queue='core.deadletters.{}'.format(constants.API_VERSION)
22 )
23 monitor = SimpleRabbitQueueWorker(
24 connection,
25- 'core.image.{}'.format(constants.API_VERSION),
26+ 'core.package.{}'.format(constants.API_VERSION),
27 worker.ImageBuilderWorker(config, builder),
28 retry_policy,
29 )
30
31=== modified file 'core_image_builder/queue.py'
32--- core_image_builder/queue.py 2015-05-19 21:37:40 +0000
33+++ core_image_builder/queue.py 2015-05-26 18:29:22 +0000
34@@ -24,7 +24,7 @@
35 logger = logging.getLogger(__name__)
36
37 __all__ = [
38- 'CoreImageResultBuilder',
39+ 'CoreImageBuilder',
40 'create_connection_from_config',
41 ]
42
43@@ -39,7 +39,7 @@
44 return kombu.Connection(amqp_uris)
45
46
47-class CoreImageResultBuilder(object):
48+class CoreImageBuilder(object):
49
50 """A callable that knows how to publish results from this service.
51
52
53=== modified file 'core_image_builder/worker.py'
54--- core_image_builder/worker.py 2015-05-19 21:37:40 +0000
55+++ core_image_builder/worker.py 2015-05-26 18:29:22 +0000
56@@ -17,6 +17,7 @@
57
58 """Business logic or the service lives here."""
59
60+import glob
61 import logging
62 import os
63 import subprocess
64@@ -26,7 +27,10 @@
65
66 from core_image_builder.constants import LOGGING_EXTRA
67 from core_image_builder.cloud import get_glance_client
68-from core_image_builder.utils import check_call
69+from core_image_builder.utils import (
70+ check_call,
71+ BetterCalledProcessError,
72+)
73
74
75 logger = logging.getLogger(__name__)
76@@ -47,6 +51,7 @@
77 image_name = payload['image_name']
78 channel = payload['channel']
79 device = payload['device']
80+ package_name = payload['package_name']
81 except KeyError as e:
82 logger.error(
83 "Unable to deserialize message payload - "
84@@ -57,9 +62,9 @@
85 return MessageActions.Retry
86
87 with tempfile.TemporaryDirectory() as tmpdir:
88- logger.info("Beginning image download.", extra=extra)
89+ logger.info("Beginning rootfs download.", extra=extra)
90 try:
91- image_path = download_image(
92+ rootfs_path = download_rootfs(
93 image_name,
94 channel,
95 device,
96@@ -72,7 +77,61 @@
97 extra=extra
98 )
99 return MessageActions.Retry
100- logger.info("Ubuntu Core image downloaded OK.", extra=extra)
101+
102+ logger.info("Adding packages from proposed")
103+ MAX_RETRIES = 3
104+ count = MAX_RETRIES
105+ while count > 0:
106+ new_rootfs_path = add_proposed_packages(rootfs_path,
107+ package_name)
108+ if new_rootfs_path is not None:
109+ break
110+
111+ # try removing the rootfs as it sometimes is corrupted.
112+ # XXX: find out why it's getting corrupted.
113+ logger.info("Retrying rootfs download.", extra=extra)
114+ os.remove(rootfs_path)
115+ try:
116+ rootfs_path = download_rootfs(
117+ image_name,
118+ channel,
119+ device,
120+ tmpdir
121+ )
122+ except subprocess.CalledProcessError as e:
123+ logger.error(
124+ "Unable to download core image: %s",
125+ e,
126+ extra=extra
127+ )
128+ return MessageActions.Retry
129+
130+ count -= 1
131+ if count == 0:
132+ logger.error(
133+ "Unable to add packages after %d tries: %s",
134+ MAX_RETRIES,
135+ package_name,
136+ )
137+ return MessageActions.Retry
138+
139+ try:
140+ logger.info("Building the image")
141+ image_path = build_image(
142+ image_name,
143+ new_rootfs_path,
144+ channel,
145+ device,
146+ tmpdir
147+ )
148+ except subprocess.CalledProcessError as e:
149+ logger.error(
150+ "Unable to build the image: %s",
151+ e,
152+ extra=extra
153+ )
154+ return MessageActions.Retry
155+ logger.info("Ubuntu Core image build OK.", extra=extra)
156
157 try:
158 nova_image_path = convert_nova_image(image_path)
159@@ -106,34 +165,94 @@
160 return MessageActions.Acknowledge
161
162
163-def download_image(name, channel, device, tmpdir):
164- """Download the ubuntu code image, return a path to it on disk."""
165- image_path = os.path.join(tmpdir, 'core-{}.img'.format(name))
166- udf_env = {
167- # u-d-f fills '~/.cache/ubuntuimages/' on downloads,
168- # not particulary useful to us, unless we only have one
169- # worker and perform testing retries.
170- 'HOME': tmpdir,
171- }
172- # The folling uses hardcoded parameters for '--channel' and 'release'.
173- # It is ignoring the channel value passed in.
174- cmd = ['sudo',
175- 'ubuntu-device-flash',
176- '--revision', name,
177- 'core',
178- '--device', device,
179- '--channel', 'edge',
180- '--size', '3',
181- '-o', image_path,
182- '--developer-mode',
183- '--cloud',
184- 'rolling']
185- check_call(cmd, env=udf_env)
186- cmd = ['sudo',
187- 'rm',
188- '-rf',
189- os.path.join(tmpdir, '.gnupg')]
190- check_call(cmd, env=udf_env)
191+def download_rootfs(name, channel, device, tmpdir):
192+ """Download the ubuntu code image, return a path to it on disk."""
193+ image_path = os.path.join(tmpdir, 'core-{}.img'.format(name))
194+
195+ # The following uses hardcoded parameters for '--channel' and 'release'.
196+ # It is ignoring the channel value passed in.
197+ cmd = ['sudo',
198+ './ubuntu-device-flash',
199+ '--revision', name,
200+ '--download-only',
201+ 'core',
202+ '--device', device,
203+ '--channel', 'edge',
204+ '--size', '3',
205+ '-o', image_path,
206+ '--developer-mode',
207+ '--cloud',
208+ 'rolling']
209+ check_call(cmd)
210+
211+ cmd = ['sudo',
212+ 'rm',
213+ '-rf',
214+ os.path.join(tmpdir, '.gnupg')]
215+ check_call(cmd)
216+ output = glob.glob('ubuntu-*.tar.xz')
217+ return output[0]
218+
219+
220+def add_proposed_packages(rootfs_path, package_name):
221+
222+ new_rootfs_path = rootfs_path.replace('.tar.xz', '-modified.tar.xz')
223+
224+ ROOTFS_BASE_DIR = 'system'
225+
226+ # untar the rootfs
227+ cmd = ['sudo', 'tar', 'xJf', rootfs_path]
228+ try:
229+ check_call(cmd)
230+ except BetterCalledProcessError as e:
231+ logger.error(e)
232+ return None
233+
234+ # sanity check that the base directory exists
235+ if not os.path.exists('system'):
236+ logger.error("Failed to untar the rootfs: %s", rootfs_path)
237+ return None
238+
239+ # chroot into the rootfs and install packages
240+ cmd = ['sudo', 'chroot', os.path.join(os.getcwd(), ROOTFS_BASE_DIR),
241+ 'apt-get', ' update']
242+ check_call(cmd)
243+ cmd = ['sudo', 'chroot', os.path.join(os.getcwd(), ROOTFS_BASE_DIR),
244+ 'apt-get', ' install ', package_name]
245+ check_call(cmd)
246+
247+ # tar the rootfs back up.
248+ cmd = ['sudo', 'tar', 'cJf', new_rootfs_path, ROOTFS_BASE_DIR]
249+ check_call(cmd)
250+
251+ return new_rootfs_path
252+
253+
254+def build_image(name, rootfs_path, channel, device, tmpdir):
255+ """Download the ubuntu code image, return a path to it on disk."""
256+ image_path = os.path.join(tmpdir, 'core-{}.img'.format(name))
257+ # The following uses hardcoded parameters for '--channel' and 'release'.
258+ # It is ignoring the channel value passed in.
259+ cmd = ['sudo',
260+ './ubuntu-device-flash',
261+ '--revision', name,
262+ 'core',
263+ '--device', device,
264+ '--channel', 'edge',
265+ '--size', '3',
266+ '--image-part', rootfs_path,
267+ '-o', image_path,
268+ '--developer-mode',
269+ '--cloud',
270+ 'rolling']
271+ check_call(cmd)
272+
273+ cmd = ['sudo',
274+ 'rm',
275+ '-rf',
276+ os.path.join(tmpdir, '.gnupg')]
277+ check_call(cmd)
278+
279 return image_path
280
281
282
283=== added file 'requirements.txt'
284--- requirements.txt 1970-01-01 00:00:00 +0000
285+++ requirements.txt 2015-05-26 18:29:22 +0000
286@@ -0,0 +1,11 @@
287+# XXX cprov 20150330: cffi and pyasn1 are cryptography (for glanceclient)
288+# dependencies and if not available it uses easy_install for installing
289+# and it doesn't respect local pip-cache setup, it ends up hitting pypi
290+# on the internet.
291+cffi
292+pyasn1
293+kombu==3.0.24
294+python-logstash==0.4.2
295+python-glanceclient==0.17.0
296+uservice-utils==1.0.3
297+traceback2==1.4.0
298
299=== added file 'test_requirements.txt'
300--- test_requirements.txt 1970-01-01 00:00:00 +0000
301+++ test_requirements.txt 2015-05-26 18:29:22 +0000
302@@ -0,0 +1,2 @@
303+testtools==1.7.1
304+flake8==2.4.0

Subscribers

People subscribed via source and target branches