Merge lp:~ltrager/maas/lp1554636 into lp:~maas-committers/maas/trunk
- lp1554636
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
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!
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~ltrager/maas/lp1554636 into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Hit:2 http://
Get:3 http://
Get:4 http://
Get:5 http://
Fetched 544 kB in 0s (985 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
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~
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubun
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+
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 : | # |
The attempt to merge lp:~ltrager/maas/lp1554636 into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Get:2 http://
Get:3 http://
Get:4 http://
Get:5 http://
Get:6 http://
Get:7 http://
Get:8 http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Get:13 http://
Fetched 1,872 kB in 0s (2,770 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
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~
build-essential is already the newest ver...
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~ltrager/maas/lp1554636 into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Get:2 http://
Get:3 http://
Get:4 http://
Fetched 282 kB in 0s (618 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
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~
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubun
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+
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
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 | 233 | return BootSources.parse(StringIO(sources_yaml)) | 233 | return BootSources.parse(StringIO(sources_yaml)) |
6 | 234 | 234 | ||
7 | 235 | 235 | ||
8 | 236 | def update_iscsi_targets(snapshot_path): | ||
9 | 237 | maaslog.info("Updating boot image iSCSI targets.") | ||
10 | 238 | update_targets_conf(snapshot_path) | ||
11 | 239 | |||
12 | 240 | |||
13 | 236 | def import_images(sources): | 241 | def import_images(sources): |
14 | 237 | """Import images. Callable from the command line. | 242 | """Import images. Callable from the command line. |
15 | 238 | 243 | ||
16 | @@ -244,6 +249,9 @@ | |||
17 | 244 | maaslog.warning("Can't import: region did not provide a source.") | 249 | maaslog.warning("Can't import: region did not provide a source.") |
18 | 245 | return False | 250 | return False |
19 | 246 | 251 | ||
20 | 252 | with ClusterConfiguration.open() as config: | ||
21 | 253 | storage = FilePath(config.tftp_root).parent().path | ||
22 | 254 | |||
23 | 247 | with tempdir('keyrings') as keyrings_path: | 255 | with tempdir('keyrings') as keyrings_path: |
24 | 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: |
25 | 249 | # try to figure out why this sometimes happens. | 257 | # try to figure out why this sometimes happens. |
26 | @@ -259,15 +267,15 @@ | |||
27 | 259 | 267 | ||
28 | 260 | image_descriptions = download_all_image_descriptions(sources) | 268 | image_descriptions = download_all_image_descriptions(sources) |
29 | 261 | if image_descriptions.is_empty(): | 269 | if image_descriptions.is_empty(): |
30 | 270 | update_iscsi_targets(os.path.join(storage, 'current')) | ||
31 | 262 | maaslog.warning( | 271 | maaslog.warning( |
32 | 263 | "Finished importing boot images, the region does not have " | 272 | "Finished importing boot images, the region does not have " |
33 | 264 | "any boot images available.") | 273 | "any boot images available.") |
34 | 265 | return False | 274 | return False |
35 | 266 | 275 | ||
36 | 267 | with ClusterConfiguration.open() as config: | ||
37 | 268 | storage = FilePath(config.tftp_root).parent().path | ||
38 | 269 | meta_file_content = image_descriptions.dump_json() | 276 | meta_file_content = image_descriptions.dump_json() |
39 | 270 | if meta_contains(storage, meta_file_content): | 277 | if meta_contains(storage, meta_file_content): |
40 | 278 | update_iscsi_targets(os.path.join(storage, 'current')) | ||
41 | 271 | maaslog.info( | 279 | maaslog.info( |
42 | 272 | "Finished importing boot images, the region does not " | 280 | "Finished importing boot images, the region does not " |
43 | 273 | "have any new images.") | 281 | "have any new images.") |
44 | @@ -279,6 +287,7 @@ | |||
45 | 279 | snapshot_path = download_all_boot_resources( | 287 | snapshot_path = download_all_boot_resources( |
46 | 280 | sources, storage, product_mapping) | 288 | sources, storage, product_mapping) |
47 | 281 | except: | 289 | except: |
48 | 290 | update_iscsi_targets(os.path.join(storage, 'current')) | ||
49 | 282 | # Cleanup snapshots and cache since download failed. | 291 | # Cleanup snapshots and cache since download failed. |
50 | 283 | maaslog.warning( | 292 | maaslog.warning( |
51 | 284 | "Unable to import boot images; cleaning up failed snapshot " | 293 | "Unable to import boot images; cleaning up failed snapshot " |
52 | @@ -295,8 +304,8 @@ | |||
53 | 295 | 304 | ||
54 | 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. |
55 | 297 | update_current_symlink(storage, snapshot_path) | 306 | update_current_symlink(storage, snapshot_path) |
58 | 298 | maaslog.info("Updating boot image iSCSI targets.") | 307 | |
59 | 299 | update_targets_conf(snapshot_path) | 308 | update_iscsi_targets(snapshot_path) |
60 | 300 | 309 | ||
61 | 301 | # Now cleanup the old snapshots and cache. | 310 | # Now cleanup the old snapshots and cache. |
62 | 302 | maaslog.info('Cleaning up old snapshots and cache.') | 311 | maaslog.info('Cleaning up old snapshots and cache.') |
63 | 303 | 312 | ||
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 | 19 | Popen, | 19 | Popen, |
69 | 20 | ) | 20 | ) |
70 | 21 | from unittest import mock | 21 | from unittest import mock |
72 | 22 | from unittest.mock import call | 22 | from unittest.mock import ( |
73 | 23 | call, | ||
74 | 24 | MagicMock, | ||
75 | 25 | ) | ||
76 | 23 | 26 | ||
77 | 24 | from maastesting.factory import factory | 27 | from maastesting.factory import factory |
78 | 25 | from maastesting.matchers import ( | 28 | from maastesting.matchers import ( |
79 | 26 | MockAnyCall, | 29 | MockAnyCall, |
80 | 30 | MockCalledOnce, | ||
81 | 27 | MockCalledOnceWith, | 31 | MockCalledOnceWith, |
82 | 28 | MockCalledWith, | 32 | MockCalledWith, |
83 | 29 | MockCallsMatch, | 33 | MockCallsMatch, |
84 | @@ -545,6 +549,7 @@ | |||
85 | 545 | BootImageMapping()) | 549 | BootImageMapping()) |
86 | 546 | self.patch_maaslog() | 550 | self.patch_maaslog() |
87 | 547 | self.patch(boot_resources, 'RepoWriter') | 551 | self.patch(boot_resources, 'RepoWriter') |
88 | 552 | self.patch(boot_resources, 'update_iscsi_targets') | ||
89 | 548 | args = self.make_args(sources_file=sources_fixture.filename) | 553 | args = self.make_args(sources_file=sources_fixture.filename) |
90 | 549 | 554 | ||
91 | 550 | boot_resources.main(args) | 555 | boot_resources.main(args) |
92 | @@ -691,3 +696,151 @@ | |||
93 | 691 | boot_resources.import_images(sources) | 696 | boot_resources.import_images(sources) |
94 | 692 | self.assertThat( | 697 | self.assertThat( |
95 | 693 | fake_write_all_keyrings, MockCalledWith(mock.ANY, sources)) | 698 | fake_write_all_keyrings, MockCalledWith(mock.ANY, sources)) |
96 | 699 | |||
97 | 700 | def test__returns_false_when_no_images(self): | ||
98 | 701 | # Stop import_images() from actually doing anything. | ||
99 | 702 | self.patch(boot_resources, 'maaslog') | ||
100 | 703 | fake_download_all_image_descriptions = self.patch( | ||
101 | 704 | boot_resources, 'download_all_image_descriptions') | ||
102 | 705 | fake_download_all_image_descriptions.return_value = MagicMock() | ||
103 | 706 | fake_update_iscsi_targets = self.patch( | ||
104 | 707 | boot_resources, 'update_iscsi_targets') | ||
105 | 708 | |||
106 | 709 | self.patch(boot_resources, 'write_all_keyrings') | ||
107 | 710 | sources = [ | ||
108 | 711 | { | ||
109 | 712 | 'keyring_data': self.getUniqueString(), | ||
110 | 713 | 'url': factory.make_name("something"), | ||
111 | 714 | 'selections': [ | ||
112 | 715 | { | ||
113 | 716 | 'os': factory.make_name("os"), | ||
114 | 717 | 'release': factory.make_name("release"), | ||
115 | 718 | 'arches': [factory.make_name("arch")], | ||
116 | 719 | 'subarches': [factory.make_name("subarch")], | ||
117 | 720 | 'labels': [factory.make_name("label")], | ||
118 | 721 | }, | ||
119 | 722 | ], | ||
120 | 723 | }, | ||
121 | 724 | ], | ||
122 | 725 | self.assertFalse(boot_resources.import_images(sources)) | ||
123 | 726 | self.assertThat(fake_update_iscsi_targets, MockCalledOnce()) | ||
124 | 727 | |||
125 | 728 | def test__returns_false_when_no_new_images(self): | ||
126 | 729 | # Stop import_images() from actually doing anything. | ||
127 | 730 | self.patch(boot_resources, 'maaslog') | ||
128 | 731 | fake_download_all_image_descriptions = self.patch( | ||
129 | 732 | boot_resources, 'download_all_image_descriptions') | ||
130 | 733 | fake_image_descriptions = MagicMock() | ||
131 | 734 | fake_image_descriptions.is_empty.return_value = False | ||
132 | 735 | fake_download_all_image_descriptions.return_value = ( | ||
133 | 736 | fake_image_descriptions) | ||
134 | 737 | self.patch(boot_resources, 'meta_contains').return_value = True | ||
135 | 738 | fake_update_iscsi_targets = self.patch( | ||
136 | 739 | boot_resources, 'update_iscsi_targets') | ||
137 | 740 | |||
138 | 741 | self.patch(boot_resources, 'write_all_keyrings') | ||
139 | 742 | sources = [ | ||
140 | 743 | { | ||
141 | 744 | 'keyring_data': self.getUniqueString(), | ||
142 | 745 | 'url': factory.make_name("something"), | ||
143 | 746 | 'selections': [ | ||
144 | 747 | { | ||
145 | 748 | 'os': factory.make_name("os"), | ||
146 | 749 | 'release': factory.make_name("release"), | ||
147 | 750 | 'arches': [factory.make_name("arch")], | ||
148 | 751 | 'subarches': [factory.make_name("subarch")], | ||
149 | 752 | 'labels': [factory.make_name("label")], | ||
150 | 753 | }, | ||
151 | 754 | ], | ||
152 | 755 | }, | ||
153 | 756 | ], | ||
154 | 757 | self.assertFalse(boot_resources.import_images(sources)) | ||
155 | 758 | self.assertThat(fake_update_iscsi_targets, MockCalledOnce()) | ||
156 | 759 | |||
157 | 760 | def test__cleans_up_on_failure(self): | ||
158 | 761 | # Stop import_images() from actually doing anything. | ||
159 | 762 | self.patch(boot_resources, 'maaslog') | ||
160 | 763 | fake_download_all_image_descriptions = self.patch( | ||
161 | 764 | boot_resources, 'download_all_image_descriptions') | ||
162 | 765 | fake_image_descriptions = MagicMock() | ||
163 | 766 | fake_image_descriptions.is_empty.return_value = False | ||
164 | 767 | fake_download_all_image_descriptions.return_value = ( | ||
165 | 768 | fake_image_descriptions) | ||
166 | 769 | self.patch(boot_resources, 'meta_contains').return_value = False | ||
167 | 770 | self.patch(boot_resources, 'map_products') | ||
168 | 771 | self.patch( | ||
169 | 772 | boot_resources, 'download_all_boot_resources' | ||
170 | 773 | ).side_effect = Exception | ||
171 | 774 | fake_update_iscsi_targets = self.patch( | ||
172 | 775 | boot_resources, 'update_iscsi_targets') | ||
173 | 776 | fake_cleanup_snapshots_and_cache = self.patch( | ||
174 | 777 | boot_resources, 'cleanup_snapshots_and_cache') | ||
175 | 778 | |||
176 | 779 | self.patch(boot_resources, 'write_all_keyrings') | ||
177 | 780 | sources = [ | ||
178 | 781 | { | ||
179 | 782 | 'keyring_data': self.getUniqueString(), | ||
180 | 783 | 'url': factory.make_name("something"), | ||
181 | 784 | 'selections': [ | ||
182 | 785 | { | ||
183 | 786 | 'os': factory.make_name("os"), | ||
184 | 787 | 'release': factory.make_name("release"), | ||
185 | 788 | 'arches': [factory.make_name("arch")], | ||
186 | 789 | 'subarches': [factory.make_name("subarch")], | ||
187 | 790 | 'labels': [factory.make_name("label")], | ||
188 | 791 | }, | ||
189 | 792 | ], | ||
190 | 793 | }, | ||
191 | 794 | ], | ||
192 | 795 | self.assertRaises( | ||
193 | 796 | Exception, boot_resources.import_images, sources) | ||
194 | 797 | self.assertThat(fake_update_iscsi_targets, MockCalledOnce()) | ||
195 | 798 | self.assertThat(fake_cleanup_snapshots_and_cache, MockCalledOnce()) | ||
196 | 799 | |||
197 | 800 | def test__runs_import_and_returns_true(self): | ||
198 | 801 | # Stop import_images() from actually doing anything. | ||
199 | 802 | self.patch(boot_resources, 'maaslog') | ||
200 | 803 | fake_download_all_image_descriptions = self.patch( | ||
201 | 804 | boot_resources, 'download_all_image_descriptions') | ||
202 | 805 | fake_image_descriptions = MagicMock() | ||
203 | 806 | fake_image_descriptions.is_empty.return_value = False | ||
204 | 807 | fake_download_all_image_descriptions.return_value = ( | ||
205 | 808 | fake_image_descriptions) | ||
206 | 809 | self.patch(boot_resources, 'meta_contains').return_value = False | ||
207 | 810 | self.patch(boot_resources, 'map_products') | ||
208 | 811 | self.patch(boot_resources, 'download_all_boot_resources') | ||
209 | 812 | fake_write_snapshot_metadata = self.patch( | ||
210 | 813 | boot_resources, 'write_snapshot_metadata') | ||
211 | 814 | fake_targets_conf = self.patch( | ||
212 | 815 | boot_resources, 'write_targets_conf') | ||
213 | 816 | fake_link_bootloaders = self.patch(boot_resources, 'link_bootloaders') | ||
214 | 817 | fake_update_current_symlink = self.patch( | ||
215 | 818 | boot_resources, 'update_current_symlink') | ||
216 | 819 | fake_update_iscsi_targets = self.patch( | ||
217 | 820 | boot_resources, 'update_iscsi_targets') | ||
218 | 821 | fake_cleanup_snapshots_and_cache = self.patch( | ||
219 | 822 | boot_resources, 'cleanup_snapshots_and_cache') | ||
220 | 823 | |||
221 | 824 | self.patch(boot_resources, 'write_all_keyrings') | ||
222 | 825 | sources = [ | ||
223 | 826 | { | ||
224 | 827 | 'keyring_data': self.getUniqueString(), | ||
225 | 828 | 'url': factory.make_name("something"), | ||
226 | 829 | 'selections': [ | ||
227 | 830 | { | ||
228 | 831 | 'os': factory.make_name("os"), | ||
229 | 832 | 'release': factory.make_name("release"), | ||
230 | 833 | 'arches': [factory.make_name("arch")], | ||
231 | 834 | 'subarches': [factory.make_name("subarch")], | ||
232 | 835 | 'labels': [factory.make_name("label")], | ||
233 | 836 | }, | ||
234 | 837 | ], | ||
235 | 838 | }, | ||
236 | 839 | ], | ||
237 | 840 | self.assertTrue(boot_resources.import_images(sources)) | ||
238 | 841 | self.assertThat(fake_write_snapshot_metadata, MockCalledOnce()) | ||
239 | 842 | self.assertThat(fake_targets_conf, MockCalledOnce()) | ||
240 | 843 | self.assertThat(fake_link_bootloaders, MockCalledOnce()) | ||
241 | 844 | self.assertThat(fake_update_current_symlink, MockCalledOnce()) | ||
242 | 845 | self.assertThat(fake_update_iscsi_targets, MockCalledOnce()) | ||
243 | 846 | self.assertThat(fake_cleanup_snapshots_and_cache, MockCalledOnce()) |
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?)