Merge lp:~javier.collado/utah/wrap-latecommand into lp:utah

Proposed by Javier Collado
Status: Merged
Approved by: Javier Collado
Approved revision: 836
Merged at revision: 823
Proposed branch: lp:~javier.collado/utah/wrap-latecommand
Merge into: lp:utah
Diff against target: 385 lines (+144/-90)
15 files modified
conf/utah/default-preseed.cfg (+0/-1)
debian/changelog (+1/-0)
debian/control (+3/-3)
templates/casper-preseed-script.jinja2 (+3/-2)
templates/check-locks-command.jinja2 (+10/-8)
templates/install-commands.jinja2 (+26/-0)
templates/install-deb-command.jinja2 (+2/-1)
templates/latecommand-wrapper.jinja2 (+13/-0)
templates/log-function.jinja2 (+3/-0)
templates/preseed-install-commands.jinja2 (+0/-5)
templates/preseed-postinstall-commands.jinja2 (+0/-4)
templates/preseed-success-command.jinja2 (+0/-8)
templates/utah-latecommand-in-target.jinja2 (+12/-0)
templates/utah-latecommand.jinja2 (+32/-17)
utah/provisioning/provisioning.py (+39/-41)
To merge this branch: bzr merge lp:~javier.collado/utah/wrap-latecommand
Reviewer Review Type Date Requested Status
Javier Collado (community) Approve
Review via email: mp+149943@code.launchpad.net

Description of the change

This branch moves the contens of the late command (and the success command in
desktop images) to a separate script file that uses "set -e" so that the
scripts stop on any failure and its return code can be used to detect that
situation.

The idea about this is to be able to detect when something in the late command
failed to troubleshoot problems faster.

The plan about these changes are as follows:
- do a similar thing for the failure command in desktop installations
- update the log messages to use rsyslog (for now /var/log/utah-install is used)
- refactor the wrapper script to move part of it to utah-latecommand so that the
  wrapper just contains what it was in the original preseed and utah-latecommand
  has what it has been added by utah

I've tested the changes with {desktop,server} {i386,amd64} and worked fine,
that is, the installation was successful and the contents of
/var/log/utah-install was the expected one.

To post a comment you must log in.
Revision history for this message
Andy Doan (doanac) wrote :

+1

Revision history for this message
Javier Collado (javier.collado) wrote :

I've updated the code in this branch to better separate what is added by UTAH
and what was in the original preseed.

After the changes, there are two scripts:
- utah-latecommand: Performs the file copying that is needed to get some files in
the target filesystems: SSH keys, UUID, etc.
- utah-latcommand-in-target: Installs the packages needed to connect through SSH
and updates /etc/rc.local to retry the package installation just in case some
problem happened.

Log messages are written both to /var/log/utah-install and the installer
syslog. Incidentally, I've found that, in desktop installations, messages
appear only in the syslog file available in the utah server side and doesn't
contain the ones requested by the in target script. However, for server
installations all messages are available in the syslog in the utah server side
and those messages are written to the installer syslog in the client side.

One more feature that has been added is the ability to add the
late_command/success_command to the preseed if isn't present. Hence, that line
is no longer needed in the default preseed and has been removed. I believe the
[ "$?" -eq "0" ] command was just a placeholder to ensure that something was
there, but please let me know if that's not the case.

Note that a great deal of effort has been made regarding readability, that is,
templates are written in such a way that the final files (include
/etc/rc.local) contain code easier to read than before. In particular, trailing
backlashes have been removed where there were not needed and indentation has
been preserved to make clear control structures such as while loops.

As usual, I've tested the changes with {desktop,server} {i386,amd64} images and
worked fine. However, I haven't tested different rewriting options which I
think was one of the concerns expressed by Max yesterday. We can discuss today
the use cases we need to cover (I believe nobody is really using it) to make
sure that option works fine.

review: Needs Resubmitting
Revision history for this message
Andy Doan (doanac) wrote :

looking at:

+ template = self.template_env.get_template('check-locks-command.jinja2')
+ check_locks_command = template.render()
+ template = self.template_env.get_template('install-commands.jinja2')
+ install_commands = template.render(
+ packages=config.installpackages,
+ check_locks_command=check_locks_command,
+ log_file='/var/log/utah-install')
+ template = self.template_env.get_template(
+ 'utah-latecommand-in-target.jinja2')
+ latecommand = template.render(install_commands=install_commands)

I get a little confused trying to read through all three templates to understand how the file gets constructed. It seems like these 3 templates could become a single template and it would be easier to understand.

822. By Javier Collado

Added battery_measurements boolean variable to runlist (defaults to False)

Source branch: lp:~javier.collado/utah/battery_measurements_config

Revision history for this message
Javier Collado (javier.collado) wrote :

@Andy

The reason for having three different templates is that the two that are
rendered before the third one are used in multiple places.

In `utah-latecommand-in-target.jinja2`, you'll see that `install_commands` is
used twice (one for installing packages during the installation and the other
one to retry during boot up).

In `install_commands.jinja2` you'll see that `check_locks_command` is used, but
also the same template is used in `install-deb-command.jinja2` by means of an
include statement.

Anyway, I agree on that the code might look overly complicated. The include
statement would be useful to render just one template in the code, but I
haven't been able to get the template contents and then indent it for the sake
of readability. In the jinja documentation I see that maybe this can be done
with macros, so I'm going to give it a try.

Revision history for this message
Javier Collado (javier.collado) wrote :

The code has been updated to use macros in the templates and render just a
single template in the code when needed.

review: Needs Resubmitting
Revision history for this message
Andy Doan (doanac) wrote :

1 merge conflict:

=== modified file 'debian/changelog'
--- debian/changelog 2013-02-27 08:32:22 +0000
+++ debian/changelog 2013-02-27 11:17:22 +0000
@@ -1,9 +1,18 @@
+<<<<<<< TREE
 utah (0.9ubuntu1) UNRELEASED; urgency=low

   * Added battery_measurements boolean variable to runlist (defaults to False)

  -- Javier Collado <email address hidden> Wed, 27 Feb 2013 09:28:58 +0100

+=======
+utah (0.9ubuntu1) UNRELEASED; urgency=low
+
+ * Added success command failure detection (LP: #1126115)
+

Revision history for this message
Andy Doan (doanac) wrote :

I'm basically +1. This check_locks logic seems to be the thing that keeps this a little tricky. I wonder if we shouldn't make that thing its own script and then you wouldn't even need the macro stuff you added. However, then you'd have to copy the script to /target/usr/local/bin/ and other stuff, so my "simplification" might be making this harder. Your choice - I'm fine either way once the merge conflict is fixed.

826. By Javier Collado

Moved utah-latecommand script to the latecommand-wrapper script

827. By Javier Collado

Refactored utah-latecommand to use functions

The late command script is going to become larger, so functions are used to
group code that is related.

828. By Javier Collado

Moved package installation commands to a script that is executed in target

829. By Javier Collado

Moved postinstall commands the the in-target script

With this change, the latecommand wrapper contains a call to the utah
latecommand scripts and the commands in the original preseed in a way that is
easier to read.

830. By Javier Collado

Replaced echo with logger to log messages to syslog during installation

831. By Javier Collado

Refactored installation commands to reuse them properly

In addition to this /etc/rc.local readability has improved.

832. By Javier Collado

Moved log function to its own template

833. By Javier Collado

Added python-jinja2 dependency

834. By Javier Collado

Updated code to create late_command section if not present in the preseed

835. By Javier Collado

Added success command failure detection (LP: #1126115)

836. By Javier Collado

Refactored templates to use macros

Macros let templates grab content from other templates and apply filters (this
isn't provided by the include statement). This makes code simpler since there's
no need to render a template and pass the result as an argument to another
template.

Revision history for this message
Javier Collado (javier.collado) wrote :

I've rebased the changes to fix the merge conflict.

As you said, the script has both advantages and disadvantages, so I prefer to
stick with this solution unless we find some other issue.

I'm merging the changes right away.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'conf/utah/default-preseed.cfg'
2--- conf/utah/default-preseed.cfg 2012-08-13 14:16:46 +0000
3+++ conf/utah/default-preseed.cfg 2013-02-27 16:06:21 +0000
4@@ -37,4 +37,3 @@
5 d-i pkgsel/include string openssh-server python-yaml bzr git gdebi-core
6 #need this for non-desktop
7 d-i finish-install/reboot_in_progress note
8-d-i preseed/late_command string [ "$?" -eq "0" ]
9
10=== modified file 'debian/changelog'
11--- debian/changelog 2013-02-27 08:32:22 +0000
12+++ debian/changelog 2013-02-27 16:06:21 +0000
13@@ -1,6 +1,7 @@
14 utah (0.9ubuntu1) UNRELEASED; urgency=low
15
16 * Added battery_measurements boolean variable to runlist (defaults to False)
17+ * Added success command failure detection (LP: #1126115)
18
19 -- Javier Collado <javier.collado@canonical.com> Wed, 27 Feb 2013 09:28:58 +0100
20
21
22=== modified file 'debian/control'
23--- debian/control 2013-01-23 20:31:02 +0000
24+++ debian/control 2013-02-27 16:06:21 +0000
25@@ -14,7 +14,7 @@
26 Architecture: all
27 Depends: ${misc:Depends}, ${python:Depends},
28 bsdtar, libvirt-bin, lzma,
29- python-apt, python-libvirt,
30+ python-apt, python-jinja2, python-libvirt,
31 python-netifaces, python-paramiko, python-psutil,
32 utah-client (=${binary:Version})
33 Recommends: dl-ubuntu-test-iso, kvm
34@@ -24,7 +24,7 @@
35 Package: utah-all
36 Architecture: all
37 Depends: ${misc:Depends}, ${python:Depends}, utah-bamboofeeder, utah-cobbler,
38- utah-parser
39+ utah-parser
40 Description: Ubuntu Test Automation Harness Complete Package
41 Automation framework for testing in Ubuntu, all sections
42
43@@ -59,7 +59,7 @@
44 Depends: ${misc:Depends}, ${python:Depends}
45 Description: Ubuntu Test Automation Harness Common Files
46 Automation framework for testing in Ubuntu, common files
47-
48+
49 Package: utah-parser
50 Architecture: all
51 Depends: ${misc:Depends}, ${python:Depends},
52
53=== modified file 'templates/casper-preseed-script.jinja2'
54--- templates/casper-preseed-script.jinja2 2013-02-20 16:05:59 +0000
55+++ templates/casper-preseed-script.jinja2 2013-02-27 16:06:21 +0000
56@@ -1,4 +1,5 @@
57 #!/bin/sh
58-
59-cp preseed.cfg utah-ssh-key utah-latecommand /root
60+# Copy preseed and late command scripts
61+cp preseed.cfg utah-latecommand utah-latecommand-in-target latecommand-wrapper /root
62+cp utah-ssh-key /root
63 cp 50-utahdefault.conf /root/etc/rsyslog.d
64
65=== modified file 'templates/check-locks-command.jinja2'
66--- templates/check-locks-command.jinja2 2013-02-20 15:41:34 +0000
67+++ templates/check-locks-command.jinja2 2013-02-27 16:06:21 +0000
68@@ -1,9 +1,11 @@
69+{% macro check_locks_command() -%}
70 {# Check lock files before package installation -#}
71-while (fuser /var/lib/dpkg/lock \
72- /var/cache/apt/archives/lock \
73- /var/lib/apt/lists/lock \
74- >/dev/null 2>&1); \
75-do \
76- echo "Waiting for dpkg lock to become available"; \
77- sleep 10; \
78-done; \
79+while fuser /var/lib/dpkg/lock \
80+ /var/cache/apt/archives/lock \
81+ /var/lib/apt/lists/lock \
82+ >/dev/null 2>&1
83+do
84+ logger -t utah "Waiting for packaging locks to become available..."
85+ sleep 10
86+done
87+{%- endmacro %}
88
89=== added file 'templates/install-commands.jinja2'
90--- templates/install-commands.jinja2 1970-01-01 00:00:00 +0000
91+++ templates/install-commands.jinja2 2013-02-27 16:06:21 +0000
92@@ -0,0 +1,26 @@
93+{% macro install_commands(packages, log_file) -%}
94+{% include "log-function.jinja2" %}
95+
96+{% from "check-locks-command.jinja2" import check_locks_command -%}
97+# Check lock files before package installation
98+check_locks() {
99+ {{ check_locks_command() | indent }}
100+}
101+
102+install_package() {
103+ package=$1
104+ check_locks
105+ log "Installing ${package}..."
106+ apt-get install -y ${package} --force-yes >>{{log_file}} 2>&1
107+}
108+
109+install_packages() {
110+ log "Installing packages..."
111+ for package in $@
112+ do
113+ install_package ${package}
114+ done
115+}
116+
117+install_packages {{packages | join(' ')}}
118+{%- endmacro %}
119
120=== modified file 'templates/install-deb-command.jinja2'
121--- templates/install-deb-command.jinja2 2013-02-20 18:22:37 +0000
122+++ templates/install-deb-command.jinja2 2013-02-27 16:06:21 +0000
123@@ -1,2 +1,3 @@
124-{% include "check-locks-command.jinja2" %}
125+{% from "check-locks-command.jinja2" import check_locks_command -%}
126+{{ check_locks_command() }}
127 DEBIAN_FRONTEND=noninteractive gdebi -n -q {{deb}}
128
129=== added file 'templates/latecommand-wrapper.jinja2'
130--- templates/latecommand-wrapper.jinja2 1970-01-01 00:00:00 +0000
131+++ templates/latecommand-wrapper.jinja2 2013-02-27 16:06:21 +0000
132@@ -0,0 +1,13 @@
133+#!/bin/sh
134+set -e
135+{% include "log-function.jinja2" %}
136+
137+log "Running UTAH late command..."
138+sh utah-latecommand
139+log "Running UTAH late command in-target..."
140+in-target sh /tmp/utah-latecommand-in-target
141+{% if latecommand -%}
142+log "Running original late command..."
143+{{latecommand}}
144+{% endif -%}
145+log "Late command success"
146
147=== added file 'templates/log-function.jinja2'
148--- templates/log-function.jinja2 1970-01-01 00:00:00 +0000
149+++ templates/log-function.jinja2 2013-02-27 16:06:21 +0000
150@@ -0,0 +1,3 @@
151+log() {
152+ logger -s -t utah "$1" 2>>{{log_file}}
153+}
154
155=== removed file 'templates/preseed-install-commands.jinja2'
156--- templates/preseed-install-commands.jinja2 2013-02-20 15:41:34 +0000
157+++ templates/preseed-install-commands.jinja2 1970-01-01 00:00:00 +0000
158@@ -1,5 +0,0 @@
159-{% for package_name in package_names -%}
160-{% include "check-locks-command.jinja2" %}
161-{# Install package -#}
162-apt-get install -y --force-yes {{package_name}} >>{{log_file}} 2>&1; \
163-{% endfor %}
164
165=== removed file 'templates/preseed-postinstall-commands.jinja2'
166--- templates/preseed-postinstall-commands.jinja2 2013-02-20 16:02:11 +0000
167+++ templates/preseed-postinstall-commands.jinja2 1970-01-01 00:00:00 +0000
168@@ -1,4 +0,0 @@
169-sed -i -e '/^exit 0$/i\
170- {{install_commands | indent}}
171-'\
172-/etc/rc.local;
173
174=== removed file 'templates/preseed-success-command.jinja2'
175--- templates/preseed-success-command.jinja2 2013-02-20 15:41:34 +0000
176+++ templates/preseed-success-command.jinja2 1970-01-01 00:00:00 +0000
177@@ -1,8 +0,0 @@
178-{# Note that since the chroot command is passed to sh within single quotes, the
179-single quotes inside the chroot command have to be escaped using '"'"'.
180-For more information, please refer to:
181-http://stackoverflow.com/q/1250079/183066 -#}
182-\
183- in-target sh -c '\
184- {{commands | replace('\'', '\'"\'"\'') | indent(8)}}\
185- ';
186
187=== added file 'templates/utah-latecommand-in-target.jinja2'
188--- templates/utah-latecommand-in-target.jinja2 1970-01-01 00:00:00 +0000
189+++ templates/utah-latecommand-in-target.jinja2 2013-02-27 16:06:21 +0000
190@@ -0,0 +1,12 @@
191+#!/bin/sh
192+set -e
193+{% from "install-commands.jinja2" import install_commands -%}
194+{{ install_commands(packages, log_file) }}
195+
196+log "Adding installation commands to rc.local..."
197+sed -i -e '/^exit 0$/i\
198+{# Escape backslashes and add new ones for each line to make sed command work -#}
199+{{ install_commands(packages, log_file) |
200+ replace('\\', '\\\\') |
201+ add_backslash }}
202+' /etc/rc.local
203
204=== modified file 'templates/utah-latecommand.jinja2'
205--- templates/utah-latecommand.jinja2 2013-02-20 16:01:13 +0000
206+++ templates/utah-latecommand.jinja2 2013-02-27 16:06:21 +0000
207@@ -1,18 +1,33 @@
208 #!/bin/sh
209-
210-ROOTDIR=/target/root/.ssh
211-USERDIR=/target/home/{{user}}/.ssh
212-
213-for DIR in $ROOTDIR $USERDIR
214-do
215- mkdir -p $DIR
216- cp utah-ssh-key $DIR/authorized_keys
217- chmod 700 $DIR
218-done
219-
220-chown -R root:root $ROOTDIR
221-chown -R 1000:1000 $USERDIR
222-
223-mkdir -p /target/etc/utah
224-echo "{{uuid}}" > /target/etc/utah/uuid
225-echo "{{user}}" >> /target/etc/utah/users
226+set -e
227+{% include "log-function.jinja2" %}
228+
229+# Copy SSH keys to access to later to provisioned machine
230+copy_ssh_keys() {
231+ log "Copying UTAH SSH keys to system under test.."
232+
233+ ROOTDIR=/target/root/.ssh
234+ USERDIR=/target/home/{{user}}/.ssh
235+
236+ for DIR in $ROOTDIR $USERDIR
237+ do
238+ mkdir -p $DIR
239+ cp utah-ssh-key $DIR/authorized_keys
240+ chmod 700 $DIR
241+ done
242+ chown -R root:root $ROOTDIR
243+ chown -R 1000:1000 $USERDIR
244+}
245+
246+write_utah_data() {
247+ log "Writing UTAH installation data..."
248+ mkdir -p /target/etc/utah
249+ echo "{{uuid}}" > /target/etc/utah/uuid
250+ echo "{{user}}" >> /target/etc/utah/users
251+}
252+
253+copy_ssh_keys
254+write_utah_data
255+
256+log "Copying UTAH latecommmand in-target script..."
257+cp utah-latecommand-in-target /target/tmp
258
259=== modified file 'utah/provisioning/provisioning.py'
260--- utah/provisioning/provisioning.py 2013-02-20 18:22:37 +0000
261+++ utah/provisioning/provisioning.py 2013-02-27 16:06:21 +0000
262@@ -209,6 +209,12 @@
263 self.template_env = \
264 Environment(loader=FileSystemLoader(config.template_dir))
265
266+ def add_backslash_filter(value):
267+ """Append backslash character to each line"""
268+ return '\n'.join(['{}\\'.format(line)
269+ for line in value.splitlines()])
270+ self.template_env.filters['add_backslash'] = add_backslash_filter
271+
272 self.logger.debug('Machine init finished')
273
274 def _namesetup(self, name=None):
275@@ -779,14 +785,25 @@
276 shutil.copyfile(config.sshpublickey,
277 os.path.join(tmpdir, 'initrd.d', 'utah-ssh-key'))
278
279- self.logger.info('Creating latecommand script')
280+ self.logger.info('Creating latecommand scripts')
281 template = self.template_env.get_template('utah-latecommand.jinja2')
282 latecommand = template.render(user=config.user,
283- uuid=self.uuid)
284+ uuid=self.uuid,
285+ log_file='/target/var/log/utah-install')
286 filename = os.path.join(tmpdir, 'initrd.d', 'utah-latecommand')
287 with open(filename, 'w') as f:
288 f.write(latecommand)
289
290+ template = self.template_env.get_template(
291+ 'utah-latecommand-in-target.jinja2')
292+ latecommand = template.render(
293+ packages=config.installpackages,
294+ log_file='/var/log/utah-install')
295+ filename = os.path.join(tmpdir, 'initrd.d',
296+ 'utah-latecommand-in-target')
297+ with open(filename, 'w') as f:
298+ f.write(latecommand)
299+
300 def _setuppreseed(self, tmpdir=None):
301 """
302 Rewrite the preseed to include what we need for an automated install
303@@ -800,13 +817,7 @@
304 if self.rewrite in ['all', 'minimal']:
305 with open(self.preseed) as f:
306 preseed = Preseed(f)
307- if 'preseed/late_command' in preseed:
308- # Note that this might change question name
309- # from preseed/latecommand to ubiquity/sucess_command,
310- # so don't move below the call to self._rewrite_success_command
311- self._rewrite_latecommand(preseed)
312- if 'ubiquity/success_command' in preseed:
313- self._rewrite_success_command(preseed)
314+ self._rewrite_latecommand(preseed, tmpdir)
315 if self.rewrite == 'all':
316 if 'pkgsel/include' in preseed:
317 self._rewrite_pkgsel_include(preseed)
318@@ -828,48 +839,35 @@
319 self.rewrite in ['all', 'minimal', 'casperonly']):
320 self._preseedcasper(tmpdir=tmpdir)
321
322- def _rewrite_latecommand(self, preseed):
323+ def _rewrite_latecommand(self, preseed, tmpdir):
324 """
325 Rewrite latecommand in preseed
326 """
327+ # Create a late command question if not present already
328+ if not 'preseed/late_command' in preseed:
329+ preseed.append('d-i preseed/late_command string')
330 question = preseed['preseed/late_command']
331+
332+ log_file = '/var/log/utah-install'
333 if self.installtype == 'desktop':
334 self.logger.info('Changing d-i latecommand '
335 'to ubiquity success_command '
336 'and prepending ubiquity lines')
337 question.owner = 'ubiquity'
338 question.qname = 'ubiquity/success_command'
339- log_file = '/var/log/utah-install'
340-
341- template = self.template_env.get_template(
342- 'preseed-install-commands.jinja2')
343- install_commands = template.render(
344- package_names=config.installpackages,
345- log_file=log_file)
346- template = self.template_env.get_template(
347- 'preseed-postinstall-commands.jinja2')
348- postinstall_commands = template.render(
349- install_commands=install_commands)
350- template = self.template_env.get_template(
351- 'preseed-success-command.jinja2')
352- chroot_command = template.render(
353- commands=''.join([install_commands,
354- postinstall_commands]))
355- question.value.prepend(chroot_command)
356- question.prepend("ubiquity ubiquity/summary note")
357- question.prepend("ubiquity ubiquity/reboot boolean true")
358- else:
359- self.logger.info('Prepending latecommand')
360- question.value.prepend('\\\n sh utah-latecommand; ')
361-
362- def _rewrite_success_command(self, preseed):
363- """
364- Add utah-latecommand script to ubiquity/success_command
365- in the preseed
366- """
367- self.logger.info('Prepending success_command')
368- question = preseed['ubiquity/success_command']
369- question.value.prepend('\\\n sh utah-latecommand; ')
370+ question.prepend('ubiquity ubiquity/summary note')
371+ question.prepend('ubiquity ubiquity/reboot boolean true')
372+
373+ template = self.template_env.get_template('latecommand-wrapper.jinja2')
374+ filename = os.path.join(tmpdir, 'initrd.d', 'latecommand-wrapper')
375+ target_log_file = '/target{}'.format(log_file)
376+ with open(filename, 'w') as f:
377+ f.write(template.render(latecommand=question.value.text,
378+ log_file=target_log_file))
379+ question.value = (
380+ 'sh latecommand-wrapper || '
381+ 'logger -s -t utah "Late command failure detected" '
382+ '2>>{}'.format(target_log_file))
383
384 def _rewrite_pkgsel_include(self, preseed):
385 """

Subscribers

People subscribed via source and target branches