Merge lp:~ltrager/maas/lp1554636 into lp:maas/trunk

Proposed by Lee Trager on 2016-10-26
Status: Merged
Approved by: Lee Trager on 2016-11-02
Approved revision: 5518
Merged at revision: 5528
Proposed branch: lp:~ltrager/maas/lp1554636
Merge into: lp:maas/trunk
Diff against target: 243 lines (+167/-5)
2 files modified
src/provisioningserver/import_images/boot_resources.py (+13/-4)
src/provisioningserver/import_images/tests/test_boot_resources.py (+154/-1)
To merge this branch: bzr merge lp:~ltrager/maas/lp1554636
Reviewer Review Type Date Requested Status
Mike Pontillo (community) 2016-10-26 Approve on 2016-10-31
Review via email: mp+309318@code.launchpad.net

Commit message

Update iSCSI targets on every import (in case last time failed to update).

Description of the change

In LP:1554636 MAAS was serving old images because when the image was updated the TGT target to be replaced was removed. Since the target was cached the old version kept getting served. This modifies import_images to reload the TGT targets every time its run(every 15 minutes). This is a stop gap until we replace TGT with downloading the image over HTTP.

To post a comment you must log in.
Mike Pontillo (mpontillo) wrote :

A couple questions:

 - When many images are downloaded, is it expensive to update the targets?
 - When tgt is 'reloaded', how invasive is that operation? (Are existing iSCSI connections interrupted, or do they remain open until unmounted -- at which point they become unavailable?)

review: Needs Information
Lee Trager (ltrager) wrote :

MAAS has always tried to update the iSCSI targets whenever images are added, removed, or updated. It does this by using the tgt-admin --update command which compares a configuration file to what tgt is currently serving. It adds new targets, removes targets which are no longer in the config file, and reloads files which have changed. If a target is in use tgt-admin --update will not remove or update that target. When this happens the user is left using an outdated image.

What this change does is run tgt-admin --update every time the an import is run. So any target that wasn't able to be updated will be updated on the next import(happens every 15 minutes). The amount of target loads stays the same, as targets are only changed when new images actually come in. For the most part this just causes the tgt-admin to load the config and see that there is nothing to update.

Mike Pontillo (mpontillo) wrote :

All right. Sounds like this is a good improvement, then. Thanks!

review: Approve
MAAS Lander (maas-lander) wrote :
Download full text (33.1 KiB)

The attempt to merge lp:~ltrager/maas/lp1554636 into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://security.ubuntu.com/ubuntu xenial-security InRelease
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
Get:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease [92.2 kB]
Get:5 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [356 kB]
Fetched 544 kB in 0s (985 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind avahi-utils bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common isc-dhcp-server libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-attr python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
authbind is already the newest version (2.1.1+nmu1).
avahi-utils is already the newest version (0.6.32~rc+dfsg-1ubuntu2).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the newest version (3:6.03+dfsg-11ubuntu1).
python-formencode is already the newest version (1.3.0-0ubuntu5).
python-lxml is already the newest version (3.5.0-1...

MAAS Lander (maas-lander) wrote :
Download full text (439.1 KiB)

The attempt to merge lp:~ltrager/maas/lp1554636 into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://security.ubuntu.com/ubuntu xenial-security InRelease [94.5 kB]
Get:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
Get:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease [92.2 kB]
Get:5 http://security.ubuntu.com/ubuntu xenial-security/main Sources [45.1 kB]
Get:6 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [161 kB]
Get:7 http://security.ubuntu.com/ubuntu xenial-security/main Translation-en [66.6 kB]
Get:8 http://security.ubuntu.com/ubuntu xenial-security/universe amd64 Packages [52.4 kB]
Get:9 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main Sources [203 kB]
Get:10 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [417 kB]
Get:11 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main Translation-en [160 kB]
Get:12 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [357 kB]
Get:13 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/universe Translation-en [129 kB]
Fetched 1,872 kB in 0s (2,770 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind avahi-utils bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common isc-dhcp-server libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-attr python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
authbind is already the newest version (2.1.1+nmu1).
avahi-utils is already the newest version (0.6.32~rc+dfsg-1ubuntu2).
build-essential is already the newest ver...

MAAS Lander (maas-lander) wrote :
Download full text (1.6 MiB)

The attempt to merge lp:~ltrager/maas/lp1554636 into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
Get:3 http://security.ubuntu.com/ubuntu xenial-security InRelease [94.5 kB]
Get:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease [92.2 kB]
Fetched 282 kB in 0s (618 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind avahi-utils bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common isc-dhcp-server libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-attr python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
authbind is already the newest version (2.1.1+nmu1).
avahi-utils is already the newest version (0.6.32~rc+dfsg-1ubuntu2).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the newest version (3:6.03+dfsg-11ubuntu1).
python-formencode is already the newest version (1.3.0-0ubuntu5).
python-lxml is already the newest version (3.5.0-1build1).
python-netaddr is already the newest version (0.7.18-1).
python-netifaces is already the new...

lp:~ltrager/maas/lp1554636 updated on 2016-11-02
5518. By Lee Trager on 2016-11-02

Fix lint

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/import_images/boot_resources.py'
2--- src/provisioningserver/import_images/boot_resources.py 2016-09-30 18:14:53 +0000
3+++ src/provisioningserver/import_images/boot_resources.py 2016-11-02 08:28:19 +0000
4@@ -233,6 +233,11 @@
5 return BootSources.parse(StringIO(sources_yaml))
6
7
8+def update_iscsi_targets(snapshot_path):
9+ maaslog.info("Updating boot image iSCSI targets.")
10+ update_targets_conf(snapshot_path)
11+
12+
13 def import_images(sources):
14 """Import images. Callable from the command line.
15
16@@ -244,6 +249,9 @@
17 maaslog.warning("Can't import: region did not provide a source.")
18 return False
19
20+ with ClusterConfiguration.open() as config:
21+ storage = FilePath(config.tftp_root).parent().path
22+
23 with tempdir('keyrings') as keyrings_path:
24 # XXX: Band-aid to ensure that the keyring_data is bytes. Future task:
25 # try to figure out why this sometimes happens.
26@@ -259,15 +267,15 @@
27
28 image_descriptions = download_all_image_descriptions(sources)
29 if image_descriptions.is_empty():
30+ update_iscsi_targets(os.path.join(storage, 'current'))
31 maaslog.warning(
32 "Finished importing boot images, the region does not have "
33 "any boot images available.")
34 return False
35
36- with ClusterConfiguration.open() as config:
37- storage = FilePath(config.tftp_root).parent().path
38 meta_file_content = image_descriptions.dump_json()
39 if meta_contains(storage, meta_file_content):
40+ update_iscsi_targets(os.path.join(storage, 'current'))
41 maaslog.info(
42 "Finished importing boot images, the region does not "
43 "have any new images.")
44@@ -279,6 +287,7 @@
45 snapshot_path = download_all_boot_resources(
46 sources, storage, product_mapping)
47 except:
48+ update_iscsi_targets(os.path.join(storage, 'current'))
49 # Cleanup snapshots and cache since download failed.
50 maaslog.warning(
51 "Unable to import boot images; cleaning up failed snapshot "
52@@ -295,8 +304,8 @@
53
54 # If we got here, all went well. This is now truly the "current" snapshot.
55 update_current_symlink(storage, snapshot_path)
56- maaslog.info("Updating boot image iSCSI targets.")
57- update_targets_conf(snapshot_path)
58+
59+ update_iscsi_targets(snapshot_path)
60
61 # Now cleanup the old snapshots and cache.
62 maaslog.info('Cleaning up old snapshots and cache.')
63
64=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
65--- src/provisioningserver/import_images/tests/test_boot_resources.py 2016-10-14 17:37:37 +0000
66+++ src/provisioningserver/import_images/tests/test_boot_resources.py 2016-11-02 08:28:19 +0000
67@@ -19,11 +19,15 @@
68 Popen,
69 )
70 from unittest import mock
71-from unittest.mock import call
72+from unittest.mock import (
73+ call,
74+ MagicMock,
75+)
76
77 from maastesting.factory import factory
78 from maastesting.matchers import (
79 MockAnyCall,
80+ MockCalledOnce,
81 MockCalledOnceWith,
82 MockCalledWith,
83 MockCallsMatch,
84@@ -545,6 +549,7 @@
85 BootImageMapping())
86 self.patch_maaslog()
87 self.patch(boot_resources, 'RepoWriter')
88+ self.patch(boot_resources, 'update_iscsi_targets')
89 args = self.make_args(sources_file=sources_fixture.filename)
90
91 boot_resources.main(args)
92@@ -691,3 +696,151 @@
93 boot_resources.import_images(sources)
94 self.assertThat(
95 fake_write_all_keyrings, MockCalledWith(mock.ANY, sources))
96+
97+ def test__returns_false_when_no_images(self):
98+ # Stop import_images() from actually doing anything.
99+ self.patch(boot_resources, 'maaslog')
100+ fake_download_all_image_descriptions = self.patch(
101+ boot_resources, 'download_all_image_descriptions')
102+ fake_download_all_image_descriptions.return_value = MagicMock()
103+ fake_update_iscsi_targets = self.patch(
104+ boot_resources, 'update_iscsi_targets')
105+
106+ self.patch(boot_resources, 'write_all_keyrings')
107+ sources = [
108+ {
109+ 'keyring_data': self.getUniqueString(),
110+ 'url': factory.make_name("something"),
111+ 'selections': [
112+ {
113+ 'os': factory.make_name("os"),
114+ 'release': factory.make_name("release"),
115+ 'arches': [factory.make_name("arch")],
116+ 'subarches': [factory.make_name("subarch")],
117+ 'labels': [factory.make_name("label")],
118+ },
119+ ],
120+ },
121+ ],
122+ self.assertFalse(boot_resources.import_images(sources))
123+ self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
124+
125+ def test__returns_false_when_no_new_images(self):
126+ # Stop import_images() from actually doing anything.
127+ self.patch(boot_resources, 'maaslog')
128+ fake_download_all_image_descriptions = self.patch(
129+ boot_resources, 'download_all_image_descriptions')
130+ fake_image_descriptions = MagicMock()
131+ fake_image_descriptions.is_empty.return_value = False
132+ fake_download_all_image_descriptions.return_value = (
133+ fake_image_descriptions)
134+ self.patch(boot_resources, 'meta_contains').return_value = True
135+ fake_update_iscsi_targets = self.patch(
136+ boot_resources, 'update_iscsi_targets')
137+
138+ self.patch(boot_resources, 'write_all_keyrings')
139+ sources = [
140+ {
141+ 'keyring_data': self.getUniqueString(),
142+ 'url': factory.make_name("something"),
143+ 'selections': [
144+ {
145+ 'os': factory.make_name("os"),
146+ 'release': factory.make_name("release"),
147+ 'arches': [factory.make_name("arch")],
148+ 'subarches': [factory.make_name("subarch")],
149+ 'labels': [factory.make_name("label")],
150+ },
151+ ],
152+ },
153+ ],
154+ self.assertFalse(boot_resources.import_images(sources))
155+ self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
156+
157+ def test__cleans_up_on_failure(self):
158+ # Stop import_images() from actually doing anything.
159+ self.patch(boot_resources, 'maaslog')
160+ fake_download_all_image_descriptions = self.patch(
161+ boot_resources, 'download_all_image_descriptions')
162+ fake_image_descriptions = MagicMock()
163+ fake_image_descriptions.is_empty.return_value = False
164+ fake_download_all_image_descriptions.return_value = (
165+ fake_image_descriptions)
166+ self.patch(boot_resources, 'meta_contains').return_value = False
167+ self.patch(boot_resources, 'map_products')
168+ self.patch(
169+ boot_resources, 'download_all_boot_resources'
170+ ).side_effect = Exception
171+ fake_update_iscsi_targets = self.patch(
172+ boot_resources, 'update_iscsi_targets')
173+ fake_cleanup_snapshots_and_cache = self.patch(
174+ boot_resources, 'cleanup_snapshots_and_cache')
175+
176+ self.patch(boot_resources, 'write_all_keyrings')
177+ sources = [
178+ {
179+ 'keyring_data': self.getUniqueString(),
180+ 'url': factory.make_name("something"),
181+ 'selections': [
182+ {
183+ 'os': factory.make_name("os"),
184+ 'release': factory.make_name("release"),
185+ 'arches': [factory.make_name("arch")],
186+ 'subarches': [factory.make_name("subarch")],
187+ 'labels': [factory.make_name("label")],
188+ },
189+ ],
190+ },
191+ ],
192+ self.assertRaises(
193+ Exception, boot_resources.import_images, sources)
194+ self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
195+ self.assertThat(fake_cleanup_snapshots_and_cache, MockCalledOnce())
196+
197+ def test__runs_import_and_returns_true(self):
198+ # Stop import_images() from actually doing anything.
199+ self.patch(boot_resources, 'maaslog')
200+ fake_download_all_image_descriptions = self.patch(
201+ boot_resources, 'download_all_image_descriptions')
202+ fake_image_descriptions = MagicMock()
203+ fake_image_descriptions.is_empty.return_value = False
204+ fake_download_all_image_descriptions.return_value = (
205+ fake_image_descriptions)
206+ self.patch(boot_resources, 'meta_contains').return_value = False
207+ self.patch(boot_resources, 'map_products')
208+ self.patch(boot_resources, 'download_all_boot_resources')
209+ fake_write_snapshot_metadata = self.patch(
210+ boot_resources, 'write_snapshot_metadata')
211+ fake_targets_conf = self.patch(
212+ boot_resources, 'write_targets_conf')
213+ fake_link_bootloaders = self.patch(boot_resources, 'link_bootloaders')
214+ fake_update_current_symlink = self.patch(
215+ boot_resources, 'update_current_symlink')
216+ fake_update_iscsi_targets = self.patch(
217+ boot_resources, 'update_iscsi_targets')
218+ fake_cleanup_snapshots_and_cache = self.patch(
219+ boot_resources, 'cleanup_snapshots_and_cache')
220+
221+ self.patch(boot_resources, 'write_all_keyrings')
222+ sources = [
223+ {
224+ 'keyring_data': self.getUniqueString(),
225+ 'url': factory.make_name("something"),
226+ 'selections': [
227+ {
228+ 'os': factory.make_name("os"),
229+ 'release': factory.make_name("release"),
230+ 'arches': [factory.make_name("arch")],
231+ 'subarches': [factory.make_name("subarch")],
232+ 'labels': [factory.make_name("label")],
233+ },
234+ ],
235+ },
236+ ],
237+ self.assertTrue(boot_resources.import_images(sources))
238+ self.assertThat(fake_write_snapshot_metadata, MockCalledOnce())
239+ self.assertThat(fake_targets_conf, MockCalledOnce())
240+ self.assertThat(fake_link_bootloaders, MockCalledOnce())
241+ self.assertThat(fake_update_current_symlink, MockCalledOnce())
242+ self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
243+ self.assertThat(fake_cleanup_snapshots_and_cache, MockCalledOnce())