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

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: no longer in the source branch.
Merged at revision: 5528
Proposed branch: lp:~ltrager/maas/lp1554636
Merge into: lp:~maas-committers/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) Approve
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.
Revision history for this message
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
Revision history for this message
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.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

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

review: Approve
Revision history for this message
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...

Revision history for this message
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...

Revision history for this message
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...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/provisioningserver/import_images/boot_resources.py'
--- src/provisioningserver/import_images/boot_resources.py 2016-09-30 18:14:53 +0000
+++ src/provisioningserver/import_images/boot_resources.py 2016-11-02 08:28:19 +0000
@@ -233,6 +233,11 @@
233 return BootSources.parse(StringIO(sources_yaml))233 return BootSources.parse(StringIO(sources_yaml))
234234
235235
236def update_iscsi_targets(snapshot_path):
237 maaslog.info("Updating boot image iSCSI targets.")
238 update_targets_conf(snapshot_path)
239
240
236def import_images(sources):241def import_images(sources):
237 """Import images. Callable from the command line.242 """Import images. Callable from the command line.
238243
@@ -244,6 +249,9 @@
244 maaslog.warning("Can't import: region did not provide a source.")249 maaslog.warning("Can't import: region did not provide a source.")
245 return False250 return False
246251
252 with ClusterConfiguration.open() as config:
253 storage = FilePath(config.tftp_root).parent().path
254
247 with tempdir('keyrings') as keyrings_path:255 with tempdir('keyrings') as keyrings_path:
248 # XXX: Band-aid to ensure that the keyring_data is bytes. Future task:256 # XXX: Band-aid to ensure that the keyring_data is bytes. Future task:
249 # try to figure out why this sometimes happens.257 # try to figure out why this sometimes happens.
@@ -259,15 +267,15 @@
259267
260 image_descriptions = download_all_image_descriptions(sources)268 image_descriptions = download_all_image_descriptions(sources)
261 if image_descriptions.is_empty():269 if image_descriptions.is_empty():
270 update_iscsi_targets(os.path.join(storage, 'current'))
262 maaslog.warning(271 maaslog.warning(
263 "Finished importing boot images, the region does not have "272 "Finished importing boot images, the region does not have "
264 "any boot images available.")273 "any boot images available.")
265 return False274 return False
266275
267 with ClusterConfiguration.open() as config:
268 storage = FilePath(config.tftp_root).parent().path
269 meta_file_content = image_descriptions.dump_json()276 meta_file_content = image_descriptions.dump_json()
270 if meta_contains(storage, meta_file_content):277 if meta_contains(storage, meta_file_content):
278 update_iscsi_targets(os.path.join(storage, 'current'))
271 maaslog.info(279 maaslog.info(
272 "Finished importing boot images, the region does not "280 "Finished importing boot images, the region does not "
273 "have any new images.")281 "have any new images.")
@@ -279,6 +287,7 @@
279 snapshot_path = download_all_boot_resources(287 snapshot_path = download_all_boot_resources(
280 sources, storage, product_mapping)288 sources, storage, product_mapping)
281 except:289 except:
290 update_iscsi_targets(os.path.join(storage, 'current'))
282 # Cleanup snapshots and cache since download failed.291 # Cleanup snapshots and cache since download failed.
283 maaslog.warning(292 maaslog.warning(
284 "Unable to import boot images; cleaning up failed snapshot "293 "Unable to import boot images; cleaning up failed snapshot "
@@ -295,8 +304,8 @@
295304
296 # If we got here, all went well. This is now truly the "current" snapshot.305 # If we got here, all went well. This is now truly the "current" snapshot.
297 update_current_symlink(storage, snapshot_path)306 update_current_symlink(storage, snapshot_path)
298 maaslog.info("Updating boot image iSCSI targets.")307
299 update_targets_conf(snapshot_path)308 update_iscsi_targets(snapshot_path)
300309
301 # Now cleanup the old snapshots and cache.310 # Now cleanup the old snapshots and cache.
302 maaslog.info('Cleaning up old snapshots and cache.')311 maaslog.info('Cleaning up old snapshots and cache.')
303312
=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
--- src/provisioningserver/import_images/tests/test_boot_resources.py 2016-10-14 17:37:37 +0000
+++ src/provisioningserver/import_images/tests/test_boot_resources.py 2016-11-02 08:28:19 +0000
@@ -19,11 +19,15 @@
19 Popen,19 Popen,
20)20)
21from unittest import mock21from unittest import mock
22from unittest.mock import call22from unittest.mock import (
23 call,
24 MagicMock,
25)
2326
24from maastesting.factory import factory27from maastesting.factory import factory
25from maastesting.matchers import (28from maastesting.matchers import (
26 MockAnyCall,29 MockAnyCall,
30 MockCalledOnce,
27 MockCalledOnceWith,31 MockCalledOnceWith,
28 MockCalledWith,32 MockCalledWith,
29 MockCallsMatch,33 MockCallsMatch,
@@ -545,6 +549,7 @@
545 BootImageMapping())549 BootImageMapping())
546 self.patch_maaslog()550 self.patch_maaslog()
547 self.patch(boot_resources, 'RepoWriter')551 self.patch(boot_resources, 'RepoWriter')
552 self.patch(boot_resources, 'update_iscsi_targets')
548 args = self.make_args(sources_file=sources_fixture.filename)553 args = self.make_args(sources_file=sources_fixture.filename)
549554
550 boot_resources.main(args)555 boot_resources.main(args)
@@ -691,3 +696,151 @@
691 boot_resources.import_images(sources)696 boot_resources.import_images(sources)
692 self.assertThat(697 self.assertThat(
693 fake_write_all_keyrings, MockCalledWith(mock.ANY, sources))698 fake_write_all_keyrings, MockCalledWith(mock.ANY, sources))
699
700 def test__returns_false_when_no_images(self):
701 # Stop import_images() from actually doing anything.
702 self.patch(boot_resources, 'maaslog')
703 fake_download_all_image_descriptions = self.patch(
704 boot_resources, 'download_all_image_descriptions')
705 fake_download_all_image_descriptions.return_value = MagicMock()
706 fake_update_iscsi_targets = self.patch(
707 boot_resources, 'update_iscsi_targets')
708
709 self.patch(boot_resources, 'write_all_keyrings')
710 sources = [
711 {
712 'keyring_data': self.getUniqueString(),
713 'url': factory.make_name("something"),
714 'selections': [
715 {
716 'os': factory.make_name("os"),
717 'release': factory.make_name("release"),
718 'arches': [factory.make_name("arch")],
719 'subarches': [factory.make_name("subarch")],
720 'labels': [factory.make_name("label")],
721 },
722 ],
723 },
724 ],
725 self.assertFalse(boot_resources.import_images(sources))
726 self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
727
728 def test__returns_false_when_no_new_images(self):
729 # Stop import_images() from actually doing anything.
730 self.patch(boot_resources, 'maaslog')
731 fake_download_all_image_descriptions = self.patch(
732 boot_resources, 'download_all_image_descriptions')
733 fake_image_descriptions = MagicMock()
734 fake_image_descriptions.is_empty.return_value = False
735 fake_download_all_image_descriptions.return_value = (
736 fake_image_descriptions)
737 self.patch(boot_resources, 'meta_contains').return_value = True
738 fake_update_iscsi_targets = self.patch(
739 boot_resources, 'update_iscsi_targets')
740
741 self.patch(boot_resources, 'write_all_keyrings')
742 sources = [
743 {
744 'keyring_data': self.getUniqueString(),
745 'url': factory.make_name("something"),
746 'selections': [
747 {
748 'os': factory.make_name("os"),
749 'release': factory.make_name("release"),
750 'arches': [factory.make_name("arch")],
751 'subarches': [factory.make_name("subarch")],
752 'labels': [factory.make_name("label")],
753 },
754 ],
755 },
756 ],
757 self.assertFalse(boot_resources.import_images(sources))
758 self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
759
760 def test__cleans_up_on_failure(self):
761 # Stop import_images() from actually doing anything.
762 self.patch(boot_resources, 'maaslog')
763 fake_download_all_image_descriptions = self.patch(
764 boot_resources, 'download_all_image_descriptions')
765 fake_image_descriptions = MagicMock()
766 fake_image_descriptions.is_empty.return_value = False
767 fake_download_all_image_descriptions.return_value = (
768 fake_image_descriptions)
769 self.patch(boot_resources, 'meta_contains').return_value = False
770 self.patch(boot_resources, 'map_products')
771 self.patch(
772 boot_resources, 'download_all_boot_resources'
773 ).side_effect = Exception
774 fake_update_iscsi_targets = self.patch(
775 boot_resources, 'update_iscsi_targets')
776 fake_cleanup_snapshots_and_cache = self.patch(
777 boot_resources, 'cleanup_snapshots_and_cache')
778
779 self.patch(boot_resources, 'write_all_keyrings')
780 sources = [
781 {
782 'keyring_data': self.getUniqueString(),
783 'url': factory.make_name("something"),
784 'selections': [
785 {
786 'os': factory.make_name("os"),
787 'release': factory.make_name("release"),
788 'arches': [factory.make_name("arch")],
789 'subarches': [factory.make_name("subarch")],
790 'labels': [factory.make_name("label")],
791 },
792 ],
793 },
794 ],
795 self.assertRaises(
796 Exception, boot_resources.import_images, sources)
797 self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
798 self.assertThat(fake_cleanup_snapshots_and_cache, MockCalledOnce())
799
800 def test__runs_import_and_returns_true(self):
801 # Stop import_images() from actually doing anything.
802 self.patch(boot_resources, 'maaslog')
803 fake_download_all_image_descriptions = self.patch(
804 boot_resources, 'download_all_image_descriptions')
805 fake_image_descriptions = MagicMock()
806 fake_image_descriptions.is_empty.return_value = False
807 fake_download_all_image_descriptions.return_value = (
808 fake_image_descriptions)
809 self.patch(boot_resources, 'meta_contains').return_value = False
810 self.patch(boot_resources, 'map_products')
811 self.patch(boot_resources, 'download_all_boot_resources')
812 fake_write_snapshot_metadata = self.patch(
813 boot_resources, 'write_snapshot_metadata')
814 fake_targets_conf = self.patch(
815 boot_resources, 'write_targets_conf')
816 fake_link_bootloaders = self.patch(boot_resources, 'link_bootloaders')
817 fake_update_current_symlink = self.patch(
818 boot_resources, 'update_current_symlink')
819 fake_update_iscsi_targets = self.patch(
820 boot_resources, 'update_iscsi_targets')
821 fake_cleanup_snapshots_and_cache = self.patch(
822 boot_resources, 'cleanup_snapshots_and_cache')
823
824 self.patch(boot_resources, 'write_all_keyrings')
825 sources = [
826 {
827 'keyring_data': self.getUniqueString(),
828 'url': factory.make_name("something"),
829 'selections': [
830 {
831 'os': factory.make_name("os"),
832 'release': factory.make_name("release"),
833 'arches': [factory.make_name("arch")],
834 'subarches': [factory.make_name("subarch")],
835 'labels': [factory.make_name("label")],
836 },
837 ],
838 },
839 ],
840 self.assertTrue(boot_resources.import_images(sources))
841 self.assertThat(fake_write_snapshot_metadata, MockCalledOnce())
842 self.assertThat(fake_targets_conf, MockCalledOnce())
843 self.assertThat(fake_link_bootloaders, MockCalledOnce())
844 self.assertThat(fake_update_current_symlink, MockCalledOnce())
845 self.assertThat(fake_update_iscsi_targets, MockCalledOnce())
846 self.assertThat(fake_cleanup_snapshots_and_cache, MockCalledOnce())