Merge lp:~ltrager/maas/new_kernels 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: 5313
Proposed branch: lp:~ltrager/maas/new_kernels
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 1489 lines (+746/-141)
25 files modified
src/maasserver/bootresources.py (+8/-6)
src/maasserver/bootsources.py (+6/-1)
src/maasserver/migrations/builtin/maasserver/0081_allow_larger_bootsourcecache_fields.py (+42/-0)
src/maasserver/migrations/builtin/maasserver/0082_add_kflavor.py (+47/-0)
src/maasserver/models/bootresource.py (+24/-5)
src/maasserver/models/bootsourcecache.py (+17/-9)
src/maasserver/models/tests/test_bootresource.py (+23/-2)
src/maasserver/rpc/tests/test_regionservice_calls.py (+1/-2)
src/maasserver/static/js/angular/controllers/node_details.js (+1/-1)
src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+2/-2)
src/maasserver/static/js/angular/factories/general.js (+7/-0)
src/maasserver/static/js/angular/factories/tests/test_general.js (+6/-3)
src/maasserver/testing/architecture.py (+1/-0)
src/maasserver/testing/factory.py (+27/-7)
src/maasserver/testing/osystems.py (+5/-0)
src/maasserver/tests/test_bootresources.py (+27/-6)
src/maasserver/utils/osystems.py (+163/-40)
src/maasserver/utils/tests/test_osystems.py (+206/-18)
src/maasserver/websockets/handlers/general.py (+11/-0)
src/maasserver/websockets/handlers/tests/test_general.py (+49/-30)
src/maastesting/factory.py (+26/-0)
src/provisioningserver/import_images/download_descriptions.py (+9/-7)
src/provisioningserver/import_images/tests/test_boot_resources.py (+1/-0)
src/provisioningserver/import_images/tests/test_download_descriptions.py (+36/-2)
utilities/check-imports (+1/-0)
To merge this branch: bzr merge lp:~ltrager/maas/new_kernels
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Andres Rodriguez (community) Needs Information
Review via email: mp+303506@code.launchpad.net

Commit message

Add support for new style kernels(e.g hwe-16.04), lowlatency kernels, rolling kernels, and edge kernels. While the old method was able to use an algorithm based on the first letter of the release this looks up the release/version mapping using the UbuntuDistro library. This will require the region to be running on a system with an updated distro-info-data package, otherwise new releases added to the stream won't be deployable.

The new kernel name hwe-16.04-lowlatency-edge is 25 characters, because of this I had to bump the allowable lengths in the BootSourceCache table from 20 to 32.

Description of the change

This adds support for new style kernels(e.g hwe-16.04), lowlatency kernels, rolling kernels, and edge kernels. Old style kernels(e.g hwe-t) are still supported and can be used along side new style kernels.

To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) :
Revision history for this message
Andres Rodriguez (andreserl) :
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Lots of comments and identified with some key issues with this branch. Some decisions need to be made to make sure this is the correct path.

review: Needs Fixing
Revision history for this message
Lee Trager (ltrager) wrote :

I've updated the branch to include the kflavor in the BootResource and BootSourceCache tables. The kflavor field is now used instead of accessing extra['kflavor'] throughout the region. The rack currently broadly assumes the subarch is the kernel. From how the rack reads the SimpleStream created by the region, to how kernels are stored on the filesystem, and the TGT and GRUB config files are generated. If we make that much change it brings up questions as to how we're presenting hwe_kernels and flavors to the user as well.

I've also allowed any kernel type to be used as the min_hwe_kernel. If a min_hwe_kernel is set and the user does not set a hwe_kernel during deploy MAAS picks a kernel at or above the min_hwe_kernel with the same kflavor.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

I have one question, how will this change affect the directories kernels are placed on the system in the Rack Controller?

The real question I want to answer is:

If we are to upgrade from 2.0 to 2.1 and the user doesn't update their images/kernels (or doesn't update the streams to the new version), will MAAS be able to continue to deploy machines with the current images/kernels available already in the FS?

review: Needs Information
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (3.6 MiB)

The attempt to merge lp:~ltrager/maas/new_kernels 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]
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 190 kB in 0s (451 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...
archdetect-deb is already the newest version (1.117ubuntu2).
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 ve...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/bootresources.py'
2--- src/maasserver/bootresources.py 2016-08-19 10:22:29 +0000
3+++ src/maasserver/bootresources.py 2016-08-31 06:33:18 +0000
4@@ -458,8 +458,12 @@
5 os = get_os_from_product(product)
6 series = product['release']
7 arch = product['arch']
8- subarch = product['subarch']
9 name = '%s/%s' % (os, series)
10+ if 'kflavor' in product and product['kflavor'] != 'generic':
11+ subarch = "%s-%s" % (product['subarch'], product['kflavor'])
12+ else:
13+ subarch = product['subarch']
14+
15 architecture = '%s/%s' % (arch, subarch)
16
17 # Allow a generated resource to be replaced by a sycned resource. This
18@@ -488,16 +492,14 @@
19 # replaced with this synced image.
20 resource.rtype = BOOT_RESOURCE_TYPE.SYNCED
21
22+ resource.kflavor = product.get('kflavor')
23 # Simplestreams content from maas.io includes the following
24 # extra fields. Looping through the extra product data and adding it to
25 # extra will not work as the product data that is passed into this
26 # object store contains additional data that should not be stored into
27- # the database. If kflavor and/or subarches exist in the product then
28- # we store those values to expose in the simplestreams endpoint on the
29- # region.
30+ # the database. If subarches exist in the product then we store those
31+ # values to expose in the simplestreams endpoint on the region.
32 resource.extra = {}
33- if 'kflavor' in product:
34- resource.extra['kflavor'] = product['kflavor']
35 if 'subarches' in product:
36 resource.extra['subarches'] = product['subarches']
37
38
39=== modified file 'src/maasserver/bootsources.py'
40--- src/maasserver/bootsources.py 2016-06-16 20:51:55 +0000
41+++ src/maasserver/bootsources.py 2016-08-31 06:33:18 +0000
42@@ -165,9 +165,14 @@
43 BootSourceCache.objects.filter(boot_source=bootsource).delete()
44 if not descriptions.is_empty():
45 for spec, item in descriptions.mapping.items():
46+ kflavor = item.get('kflavor')
47+ if kflavor not in (None, 'generic'):
48+ subarch = "%s-%s" % (spec.subarch, kflavor)
49+ else:
50+ subarch = spec.subarch
51 BootSourceCache.objects.create(
52 boot_source=bootsource, os=spec.os,
53- arch=spec.arch, subarch=spec.subarch,
54+ arch=spec.arch, subarch=subarch, kflavor=kflavor,
55 release=spec.release, label=spec.label,
56 release_codename=item.get('release_codename'),
57 release_title=item.get('release_title'),
58
59=== added file 'src/maasserver/migrations/builtin/maasserver/0081_allow_larger_bootsourcecache_fields.py'
60--- src/maasserver/migrations/builtin/maasserver/0081_allow_larger_bootsourcecache_fields.py 1970-01-01 00:00:00 +0000
61+++ src/maasserver/migrations/builtin/maasserver/0081_allow_larger_bootsourcecache_fields.py 2016-08-31 06:33:18 +0000
62@@ -0,0 +1,42 @@
63+# -*- coding: utf-8 -*-
64+from __future__ import unicode_literals
65+
66+from django.db import (
67+ migrations,
68+ models,
69+)
70+
71+
72+class Migration(migrations.Migration):
73+
74+ dependencies = [
75+ ('maasserver', '0080_change_packagerepository_url_type'),
76+ ]
77+
78+ operations = [
79+ migrations.AlterField(
80+ model_name='bootsourcecache',
81+ name='arch',
82+ field=models.CharField(max_length=32),
83+ ),
84+ migrations.AlterField(
85+ model_name='bootsourcecache',
86+ name='label',
87+ field=models.CharField(max_length=32),
88+ ),
89+ migrations.AlterField(
90+ model_name='bootsourcecache',
91+ name='os',
92+ field=models.CharField(max_length=32),
93+ ),
94+ migrations.AlterField(
95+ model_name='bootsourcecache',
96+ name='release',
97+ field=models.CharField(max_length=32),
98+ ),
99+ migrations.AlterField(
100+ model_name='bootsourcecache',
101+ name='subarch',
102+ field=models.CharField(max_length=32),
103+ ),
104+ ]
105
106=== added file 'src/maasserver/migrations/builtin/maasserver/0082_add_kflavor.py'
107--- src/maasserver/migrations/builtin/maasserver/0082_add_kflavor.py 1970-01-01 00:00:00 +0000
108+++ src/maasserver/migrations/builtin/maasserver/0082_add_kflavor.py 2016-08-31 06:33:18 +0000
109@@ -0,0 +1,47 @@
110+# -*- coding: utf-8 -*-
111+from __future__ import unicode_literals
112+
113+from django.db import (
114+ migrations,
115+ models,
116+)
117+
118+
119+def add_kflavor_to_boot_resource(apps, schema_editor):
120+ BootResource = apps.get_model('maasserver', 'BootResource')
121+ for resource in BootResource.objects.all():
122+ if 'kflavor' in resource.extra:
123+ resource.kflavor = resource.extra['kflavor']
124+ del resource.extra['kflavor']
125+ resource.save()
126+
127+
128+def add_kflavor_to_boot_source_cache(apps, schema_editor):
129+ BootSourceCache = apps.get_model('maasserver', 'BootSourceCache')
130+ for bsc in BootSourceCache.objects.filter(os='ubuntu'):
131+ # The kflavor was never stored as part of the BootSourceCache
132+ # however previosuly we only had generic kernels in the stream.
133+ bsc.kflavor = 'generic'
134+ bsc.save()
135+
136+
137+class Migration(migrations.Migration):
138+
139+ dependencies = [
140+ ('maasserver', '0081_allow_larger_bootsourcecache_fields'),
141+ ]
142+
143+ operations = [
144+ migrations.AddField(
145+ model_name='bootresource',
146+ name='kflavor',
147+ field=models.CharField(max_length=32, blank=True, null=True),
148+ ),
149+ migrations.RunPython(add_kflavor_to_boot_resource),
150+ migrations.AddField(
151+ model_name='bootsourcecache',
152+ name='kflavor',
153+ field=models.CharField(max_length=32, blank=True, null=True),
154+ ),
155+ migrations.RunPython(add_kflavor_to_boot_source_cache),
156+ ]
157
158=== modified file 'src/maasserver/models/bootresource.py'
159--- src/maasserver/models/bootresource.py 2016-07-30 01:17:54 +0000
160+++ src/maasserver/models/bootresource.py 2016-08-31 06:33:18 +0000
161@@ -220,7 +220,8 @@
162 return False
163 return True
164
165- def get_usable_hwe_kernels(self, name=None, architecture=None):
166+ def get_usable_hwe_kernels(
167+ self, name=None, architecture=None, kflavor=None):
168 """Return the set of usable kernels for architecture and release."""
169 if not name:
170 name = ''
171@@ -229,6 +230,8 @@
172 kernels = set()
173 for resource in self.filter(
174 architecture__startswith=architecture, name__startswith=name):
175+ if kflavor is not None and resource.kflavor != kflavor:
176+ continue
177 resource_set = resource.get_latest_set()
178 if(resource_set is None or
179 not resource_set.commissionable or
180@@ -240,8 +243,22 @@
181 if "subarches" in resource.extra:
182 for subarch in resource.extra["subarches"].split(","):
183 if subarch.startswith("hwe-"):
184- kernels.add(subarch)
185- return sorted(kernels)
186+ if kflavor is None:
187+ kernels.add(subarch)
188+ else:
189+ # generic kflavors are not included in the subarch.
190+ if kflavor == 'generic':
191+ kparts = subarch.split('-')
192+ if len(kparts) == 2:
193+ kernels.add(subarch)
194+ else:
195+ if kflavor in subarch:
196+ kernels.add(subarch)
197+ # Make sure kernels named with a version come after the kernels named
198+ # with the first letter of release. This switched in Xenial so this
199+ # preserves the chronological order of the kernels.
200+ return sorted(
201+ kernels, key=lambda k: (k.replace('hwe-', '')[0].isdigit(), k))
202
203 def get_kpackage_for_node(self, node):
204 """Return the kernel package name for the kernel specified."""
205@@ -340,11 +357,13 @@
206 architecture = CharField(
207 max_length=255, blank=False, validators=[validate_architecture])
208
209+ kflavor = CharField(max_length=32, blank=True, null=True)
210+
211 extra = JSONObjectField(blank=True, default="", editable=False)
212
213 def __str__(self):
214- return "<BootResource name=%s, arch=%s>" % (
215- self.name, self.architecture)
216+ return "<BootResource name=%s, arch=%s, kflavor=%s>" % (
217+ self.name, self.architecture, self.kflavor)
218
219 @property
220 def display_rtype(self):
221
222=== modified file 'src/maasserver/models/bootsourcecache.py'
223--- src/maasserver/models/bootsourcecache.py 2016-06-22 17:03:02 +0000
224+++ src/maasserver/models/bootsourcecache.py 2016-08-31 06:33:18 +0000
225@@ -51,18 +51,26 @@
226
227 boot_source = ForeignKey(BootSource, blank=False)
228
229- os = CharField(max_length=20, blank=False, null=False)
230-
231- arch = CharField(max_length=20, blank=False, null=False)
232-
233- subarch = CharField(max_length=20, blank=False, null=False)
234-
235- release = CharField(max_length=20, blank=False, null=False)
236-
237- label = CharField(max_length=20, blank=False, null=False)
238+ os = CharField(max_length=32, blank=False, null=False)
239+
240+ arch = CharField(max_length=32, blank=False, null=False)
241+
242+ subarch = CharField(max_length=32, blank=False, null=False)
243+
244+ kflavor = CharField(max_length=32, blank=True, null=True)
245+
246+ release = CharField(max_length=32, blank=False, null=False)
247+
248+ label = CharField(max_length=32, blank=False, null=False)
249
250 release_codename = CharField(max_length=255, blank=True, null=True)
251
252 release_title = CharField(max_length=255, blank=True, null=True)
253
254 support_eol = DateField(null=True, blank=True)
255+
256+ def __str__(self):
257+ return (
258+ "<BootSourceCache os=%s, release=%s, arch=%s, subarch=%s, "
259+ "kflavor=%s>" % (
260+ self.os, self.release, self.arch, self.subarch, self.kflavor))
261
262=== modified file 'src/maasserver/models/tests/test_bootresource.py'
263--- src/maasserver/models/tests/test_bootresource.py 2016-05-25 21:03:50 +0000
264+++ src/maasserver/models/tests/test_bootresource.py 2016-08-31 06:33:18 +0000
265@@ -594,15 +594,30 @@
266 "arch": "armfh",
267 "subarch": "hardbank",
268 "kernels": [],
269- }))
270+ }),
271+ ("ubuntu/xenial", {
272+ "name": "ubuntu/xenial",
273+ "arch": "amd64",
274+ "subarch": "generic",
275+ "kernels": ["hwe-16.04", "hwe-16.04-lowlatency"],
276+ }),
277+ )
278
279 def test__returns_usable_kernels(self):
280 if self.subarch == "generic":
281+ generic_kernels = []
282 for i in self.kernels:
283+ kernel_parts = i.split("-")
284+ if len(kernel_parts) > 2:
285+ kflavor = kernel_parts[2]
286+ else:
287+ kflavor = "generic"
288+ generic_kernels.append(i)
289 factory.make_usable_boot_resource(
290 name=self.name, rtype=BOOT_RESOURCE_TYPE.SYNCED,
291- architecture="%s/%s" % (self.arch, i))
292+ architecture="%s/%s" % (self.arch, i), kflavor=kflavor)
293 else:
294+ generic_kernels = self.kernels
295 factory.make_usable_boot_resource(
296 name=self.name, rtype=BOOT_RESOURCE_TYPE.SYNCED,
297 architecture="%s/%s" % (self.arch, self.subarch))
298@@ -612,6 +627,12 @@
299 self.name, self.arch),
300 "%s should return %s as its usable kernel" % (
301 self.name, self.kernels))
302+ self.assertEqual(
303+ generic_kernels,
304+ BootResource.objects.get_usable_hwe_kernels(
305+ self.name, self.arch, 'generic'),
306+ "%s should return %s as its usable kernel" % (
307+ self.name, generic_kernels))
308
309
310 class TestGetKpackageForNode(MAASServerTestCase):
311
312=== modified file 'src/maasserver/rpc/tests/test_regionservice_calls.py'
313--- src/maasserver/rpc/tests/test_regionservice_calls.py 2016-08-19 18:11:11 +0000
314+++ src/maasserver/rpc/tests/test_regionservice_calls.py 2016-08-31 06:33:18 +0000
315@@ -47,7 +47,6 @@
316 from maasserver.rpc.regionservice import Region
317 from maasserver.rpc.services import update_services
318 from maasserver.security import get_shared_secret
319-from maasserver.testing.architecture import make_usable_architecture
320 from maasserver.testing.eventloop import RegionEventLoopFixture
321 from maasserver.testing.factory import factory
322 from maasserver.testing.testcase import MAASTransactionServerTestCase
323@@ -1104,7 +1103,7 @@
324 self.create_node)
325
326 params = {
327- 'architecture': make_usable_architecture(self),
328+ 'architecture': factory.make_name('arch'),
329 'power_type': factory.make_name('power_type'),
330 'power_parameters': dumps({}),
331 'mac_addresses': [factory.make_mac_address()],
332
333=== modified file 'src/maasserver/static/js/angular/controllers/node_details.js'
334--- src/maasserver/static/js/angular/controllers/node_details.js 2016-07-15 00:42:25 +0000
335+++ src/maasserver/static/js/angular/controllers/node_details.js 2016-08-31 06:33:18 +0000
336@@ -62,7 +62,7 @@
337 },
338 min_hwe_kernel: {
339 selected: null,
340- options: GeneralManager.getData("hwe_kernels")
341+ options: GeneralManager.getData("min_hwe_kernels")
342 },
343 zone: {
344 selected: null,
345
346=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details.js'
347--- src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2016-07-15 18:00:04 +0000
348+++ src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2016-08-31 06:33:18 +0000
349@@ -212,7 +212,7 @@
350 },
351 min_hwe_kernel: {
352 selected: null,
353- options: GeneralManager.getData("hwe_kernels")
354+ options: GeneralManager.getData("min_hwe_kernels")
355 },
356 zone: {
357 selected: null,
358@@ -223,7 +223,7 @@
359 expect($scope.summary.architecture.options).toBe(
360 GeneralManager.getData("architectures"));
361 expect($scope.summary.min_hwe_kernel.options).toBe(
362- GeneralManager.getData("hwe_kernels"));
363+ GeneralManager.getData("min_hwe_kernels"));
364 expect($scope.summary.zone.options).toBe(
365 ZonesManager.getItems());
366 });
367
368=== modified file 'src/maasserver/static/js/angular/factories/general.js'
369--- src/maasserver/static/js/angular/factories/general.js 2016-08-18 15:42:50 +0000
370+++ src/maasserver/static/js/angular/factories/general.js 2016-08-31 06:33:18 +0000
371@@ -84,6 +84,13 @@
372 polling: false,
373 nextPromise: null
374 },
375+ min_hwe_kernels: {
376+ method: "general.min_hwe_kernels",
377+ data: [],
378+ loaded: false,
379+ polling: false,
380+ nextPromise: null
381+ },
382 default_min_hwe_kernel: {
383 method: "general.default_min_hwe_kernel",
384 data: { text: '' },
385
386=== modified file 'src/maasserver/static/js/angular/factories/tests/test_general.js'
387--- src/maasserver/static/js/angular/factories/tests/test_general.js 2016-08-19 13:29:03 +0000
388+++ src/maasserver/static/js/angular/factories/tests/test_general.js 2016-08-31 06:33:18 +0000
389@@ -53,8 +53,9 @@
390 ["machine_actions", "device_actions", "region_controller_actions",
391 "rack_controller_actions", "region_and_rack_controller_actions",
392 "architectures", "known_architectures", "pockets_to_disable",
393- "hwe_kernels", "default_min_hwe_kernel", "osinfo", "bond_options",
394- "version", "power_types", "release_options"]);
395+ "hwe_kernels", "min_hwe_kernels", "default_min_hwe_kernel",
396+ "osinfo", "bond_options", "version", "power_types",
397+ "release_options"]);
398 });
399
400 it("_data.machine_actions has correct data", function() {
401@@ -321,6 +322,7 @@
402 GeneralManager._data.known_architectures.loaded = true;
403 GeneralManager._data.pockets_to_disable.loaded = true;
404 GeneralManager._data.hwe_kernels.loaded = true;
405+ GeneralManager._data.min_hwe_kernels.loaded = true;
406 GeneralManager._data.default_min_hwe_kernel.loaded = true;
407 GeneralManager._data.osinfo.loaded = true;
408 GeneralManager._data.bond_options.loaded = true;
409@@ -584,7 +586,7 @@
410 spyOn(GeneralManager, "_loadData").and.returnValue(
411 $q.defer().promise);
412 GeneralManager.loadItems();
413- expect(GeneralManager._loadData.calls.count()).toBe(15);
414+ expect(GeneralManager._loadData.calls.count()).toBe(16);
415 });
416
417 it("resolve defer once all resolve", function(done) {
418@@ -603,6 +605,7 @@
419 $q.defer(),
420 $q.defer(),
421 $q.defer(),
422+ $q.defer(),
423 $q.defer()
424 ];
425 var i = 0;
426
427=== modified file 'src/maasserver/testing/architecture.py'
428--- src/maasserver/testing/architecture.py 2016-03-28 13:54:47 +0000
429+++ src/maasserver/testing/architecture.py 2016-08-31 06:33:18 +0000
430@@ -22,6 +22,7 @@
431 """
432 if arch_name is None:
433 arch_name = factory.make_name('arch')
434+ factory.make_default_ubuntu_release_bootable(arch_name)
435 if with_subarch:
436 if subarch_name is None:
437 subarch_name = factory.make_name('sub')
438
439=== modified file 'src/maasserver/testing/factory.py'
440--- src/maasserver/testing/factory.py 2016-08-26 08:04:38 +0000
441+++ src/maasserver/testing/factory.py 2016-08-31 06:33:18 +0000
442@@ -22,6 +22,7 @@
443 from distro_info import UbuntuDistroInfo
444 from django.conf import settings
445 from django.contrib.auth.models import User
446+from django.db import transaction
447 from django.test.client import RequestFactory
448 from django.utils import timezone
449 from maasserver.clusterrpc.power_parameters import get_power_types
450@@ -56,6 +57,7 @@
451 BootSourceCache,
452 BootSourceSelection,
453 CacheSet,
454+ Config,
455 Device,
456 DHCPSnippet,
457 DNSData,
458@@ -112,6 +114,7 @@
459 from maasserver.testing import get_data
460 from maasserver.utils.converters import round_size_to_nearest_block
461 from maasserver.utils.orm import reload_object
462+from maasserver.utils.osystems import get_release_from_distro_info
463 from maasserver.worker_user import get_worker_user
464 import maastesting.factory
465 from maastesting.factory import TooManyRandomRetries
466@@ -1272,7 +1275,7 @@
467 def make_BootSourceCache(self, boot_source=None, os=None, arch=None,
468 subarch=None, release=None, label=None,
469 release_codename=None, release_title=None,
470- support_eol=None):
471+ support_eol=None, kflavor=None):
472 """Create a new `BootSourceCache`."""
473 if boot_source is None:
474 boot_source = self.make_BootSource()
475@@ -1290,7 +1293,7 @@
476 boot_source=boot_source, os=os, arch=arch,
477 subarch=subarch, release=release, label=label,
478 release_codename=release_codename, release_title=release_title,
479- support_eol=support_eol)
480+ support_eol=support_eol, kflavor=kflavor)
481
482 def make_many_BootSourceCaches(self, number, **kwargs):
483 caches = list()
484@@ -1404,12 +1407,9 @@
485 self.make_name('key'): self.make_name('value')
486 for _ in range(3)
487 }
488- if kflavor is None:
489- extra['kflavor'] = 'generic'
490- else:
491- extra['kflavor'] = kflavor
492 return BootResource.objects.create(
493- rtype=rtype, name=name, architecture=architecture, extra=extra)
494+ rtype=rtype, name=name, architecture=architecture, kflavor=kflavor,
495+ extra=extra)
496
497 def make_BootResourceSet(self, resource, version=None, label=None):
498 if version is None:
499@@ -1467,6 +1467,26 @@
500 resource_set, filename=filetype, filetype=filetype)
501 return resource
502
503+ def make_default_ubuntu_release_bootable(self, arch=None):
504+ if arch is None:
505+ arch = self.make_name('arch')
506+ default_osystem = Config.objects.get_config(
507+ name='commissioning_osystem')
508+ default_series = Config.objects.get_config(
509+ name='commissioning_distro_series')
510+ default_name = "%s/%s" % (default_osystem, default_series)
511+ release = get_release_from_distro_info(default_series)
512+ architecture = "%s/hwe-%s" % (arch, release['version'].split()[0])
513+ try:
514+ return BootResource.objects.get(
515+ name=default_name, architecture=architecture,
516+ rtype=BOOT_RESOURCE_TYPE.SYNCED)
517+ except BootResource.DoesNotExist:
518+ with transaction.atomic():
519+ return self.make_usable_boot_resource(
520+ name=default_name, architecture=architecture,
521+ kflavor='generic', rtype=BOOT_RESOURCE_TYPE.SYNCED)
522+
523 def make_BlockDevice(
524 self, node=None, name=None, id_path=None, size=None,
525 block_size=None, tags=None):
526
527=== modified file 'src/maasserver/testing/osystems.py'
528--- src/maasserver/testing/osystems.py 2015-12-01 18:12:59 +0000
529+++ src/maasserver/testing/osystems.py 2016-08-31 06:33:18 +0000
530@@ -14,6 +14,7 @@
531 make_rpc_osystem,
532 make_rpc_release,
533 )
534+from maasserver.models import Node
535 from maasserver.testing.factory import factory
536 from maasserver.utils import osystems as osystems_module
537
538@@ -34,6 +35,10 @@
539 make_rpc_release(release)
540 for release in releases
541 ]
542+ # If this is being used to test commissioning make sure the default
543+ # Ubuntu release is bootable for every known architecture.
544+ for node in Node.objects.all():
545+ factory.make_default_ubuntu_release_bootable(node.split_arch()[0])
546 return make_rpc_osystem(osystem_name, releases=rpc_releases)
547
548
549
550=== modified file 'src/maasserver/tests/test_bootresources.py'
551--- src/maasserver/tests/test_bootresources.py 2016-08-19 10:22:29 +0000
552+++ src/maasserver/tests/test_bootresources.py 2016-08-31 06:33:18 +0000
553@@ -582,11 +582,13 @@
554 AssertConnectionWrapper.connection.connection)
555
556
557-def make_product(ftype=None):
558+def make_product(ftype=None, kflavor=None):
559 """Make product dictionary that is just like the one provided
560 from simplsetreams."""
561 if ftype is None:
562 ftype = factory.pick_choice(BOOT_RESOURCE_FILE_TYPE_CHOICES)
563+ if kflavor is None:
564+ kflavor = 'generic'
565 subarch = factory.make_name('subarch')
566 subarches = [factory.make_name('subarch') for _ in range(3)]
567 subarches.insert(0, subarch)
568@@ -597,7 +599,7 @@
569 'arch': factory.make_name('arch'),
570 'subarch': subarch,
571 'release': factory.make_name('release'),
572- 'kflavor': factory.make_name('kflavor'),
573+ 'kflavor': kflavor,
574 'subarches': subarches,
575 'version_name': factory.make_name('version'),
576 'label': factory.make_name('label'),
577@@ -607,7 +609,11 @@
578 'path': '/path/to/%s' % name,
579 }
580 name = '%s/%s' % (product['os'], product['release'])
581- architecture = '%s/%s' % (product['arch'], product['subarch'])
582+ if kflavor == 'generic':
583+ subarch = product['subarch']
584+ else:
585+ subarch = "%s-%s" % (product['subarch'], kflavor)
586+ architecture = '%s/%s' % (product['arch'], subarch)
587 return name, architecture, product
588
589
590@@ -697,7 +703,7 @@
591 self.assertEqual(BOOT_RESOURCE_TYPE.SYNCED, resource.rtype)
592 self.assertEqual(name, resource.name)
593 self.assertEqual(architecture, resource.architecture)
594- self.assertEqual(product['kflavor'], resource.extra['kflavor'])
595+ self.assertEqual(product['kflavor'], resource.kflavor)
596 self.assertEqual(product['subarches'], resource.extra['subarches'])
597
598 def test_get_or_create_boot_resource_gets_resource(self):
599@@ -708,7 +714,7 @@
600 store = BootResourceStore()
601 resource = store.get_or_create_boot_resource(product)
602 self.assertEqual(expected, resource)
603- self.assertEqual(product['kflavor'], resource.extra['kflavor'])
604+ self.assertEqual(product['kflavor'], resource.kflavor)
605 self.assertEqual(product['subarches'], resource.extra['subarches'])
606
607 def test_get_or_create_boot_resource_calls_prevent_resource_deletion(self):
608@@ -736,6 +742,21 @@
609 self.assertThat(
610 mock_prevent, MockNotCalled())
611
612+ def test_get_or_create_boot_resource_adds_kflavor_to_subarch(self):
613+ kflavor = factory.make_name('kflavor')
614+ _, architecture, product = make_product(kflavor=kflavor)
615+ store = BootResourceStore()
616+ resource = store.get_or_create_boot_resource(product)
617+ self.assertEqual(architecture, reload_object(resource).architecture)
618+
619+ def test_get_or_create_boot_resources_add_no_kflavor_for_generic(self):
620+ _, architecture, product = make_product(kflavor='generic')
621+ store = BootResourceStore()
622+ resource = store.get_or_create_boot_resource(product)
623+ resource = reload_object(resource)
624+ self.assertEqual(architecture, resource.architecture)
625+ self.assertNotIn('generic', resource.architecture)
626+
627 def test_get_or_create_boot_resource_set_creates_resource_set(self):
628 self.useFixture(SignalsDisabled("largefiles"))
629 name, architecture, product = make_product()
630@@ -1019,7 +1040,7 @@
631 with transaction.atomic():
632 resource = factory.make_BootResource(
633 rtype=BOOT_RESOURCE_TYPE.SYNCED, name=name,
634- architecture=architecture)
635+ architecture=architecture, kflavor='generic')
636 release_name = resource.name.split('/')[1]
637 resource_set = factory.make_BootResourceSet(
638 resource, version=product['version_name'])
639
640=== modified file 'src/maasserver/utils/osystems.py'
641--- src/maasserver/utils/osystems.py 2016-06-29 09:52:24 +0000
642+++ src/maasserver/utils/osystems.py 2016-08-31 06:33:18 +0000
643@@ -5,15 +5,17 @@
644 __all__ = [
645 'get_distro_series_initial',
646 'get_release_requires_key',
647+ 'get_release_version_from_string',
648 'list_all_releases_requiring_keys',
649+ 'list_all_usable_hwe_kernels',
650 'list_all_usable_osystems',
651 'list_all_usable_releases',
652- 'list_all_usable_hwe_kernels',
653+ 'list_commissioning_choices',
654 'list_hwe_kernel_choices',
655 'list_osystem_choices',
656 'list_release_choices',
657- 'list_commissioning_choices',
658 'make_hwe_kernel_ui_text',
659+ 'release_a_newer_than_b',
660 'validate_hwe_kernel',
661 ]
662
663@@ -21,6 +23,7 @@
664
665 from distro_info import UbuntuDistroInfo
666 from django.core.exceptions import ValidationError
667+from django.db.models import Q
668 from maasserver.clusterrpc.osystems import gen_all_known_operating_systems
669 from maasserver.models import (
670 BootResource,
671@@ -85,18 +88,20 @@
672 def make_hwe_kernel_ui_text(hwe_kernel):
673 if not hwe_kernel:
674 return hwe_kernel
675- release_letter = hwe_kernel.replace('hwe-', '')
676- boot_sources = BootSourceCache.objects.filter(
677- release__startswith=release_letter,
678- subarch=hwe_kernel)
679- if len(boot_sources) > 0:
680- return "%s (%s)" % (boot_sources[0].release, hwe_kernel)
681- else:
682- ubuntu = UbuntuDistroInfo()
683- for release in ubuntu.all:
684- if release.startswith(release_letter):
685- return "%s (%s)" % (release, hwe_kernel)
686- return hwe_kernel
687+ # Fall back on getting it from DistroInfo.
688+ kernel_list = hwe_kernel.split('-')
689+ if len(kernel_list) >= 2:
690+ kernel = kernel_list[1]
691+ else:
692+ kernel = hwe_kernel
693+ # Try to get the release name from the SimpleStream
694+ ubuntu_release = get_release_from_db(kernel)
695+ if ubuntu_release is None:
696+ ubuntu_release = get_release_from_distro_info(kernel)
697+ if ubuntu_release is None:
698+ return hwe_kernel
699+ else:
700+ return "%s (%s)" % (ubuntu_release['series'], hwe_kernel)
701
702
703 def list_hwe_kernel_choices(hwe_kernels):
704@@ -248,32 +253,138 @@
705 return osystem, release
706
707
708+def get_release_from_distro_info(string):
709+ """Convert an Ubuntu release or version into a release dict.
710+
711+ This data is pulled from the UbuntuDistroInfo library which contains
712+ additional information such as the release, EOL, and code name."""
713+ ubuntu = UbuntuDistroInfo()
714+ release_found = False
715+ # We can only look at release names for 12.04+ as previous versions
716+ # have overlapping first letters(e.g Warty and Wily) which break looking
717+ # up old style kernels(e.g hwe-w).
718+ for row in ubuntu._rows:
719+ if (
720+ int(row['version'].split('.')[0]) >= 12 and
721+ row['series'].startswith(string) or
722+ row['version'].startswith(string)):
723+ release_found = True
724+ break
725+ if release_found:
726+ return row
727+ else:
728+ return None
729+
730+
731+def get_release_from_db(string):
732+ """Convert an Ubuntu release, version, or subarch into a release dict.
733+
734+ This does not contain the release, eol, or created dates like
735+ get_release_from_distro_info does."""
736+ bsc = BootSourceCache.objects.filter(
737+ (
738+ Q(subarch="hwe-%s" % string) &
739+ Q(release_title__startswith=string) | Q(release__startswith=string)
740+ ) |
741+ Q(release__startswith=string) |
742+ Q(release_title__startswith=string)).first()
743+ if bsc is None:
744+ return None
745+ elif None in (bsc.release_title, bsc.release, bsc.release_codename):
746+ return None
747+ else:
748+ return {
749+ 'version': bsc.release_title,
750+ 'eol-server': bsc.support_eol,
751+ 'series': bsc.release,
752+ 'codename': bsc.release_codename,
753+ }
754+
755+
756+def get_release_version_from_string(string):
757+ """Convert an Ubuntu release, version, or kernel into a version tuple.
758+
759+ Takes a string input represneting an Ubuntu release, version, or hwe_kernel
760+ and returns a version tuple. The return value is a three integer tuple
761+ representing an Ubuntu major, minor values(e.g 16, 4 for Xenial) and a
762+ weight. The weight is used to give edge kernels a higher value when
763+ compared to a non-edge kernel. Rolling kernels and releases are given a
764+ very high value (999, 999) to always be the higher value during comparison.
765+
766+ Input: xenial, 16.04, hwe-16.04, hwe-16.04-lowlatency
767+ Output: (16, 4, 0)
768+
769+ Input: hwe-16.04-edge
770+ Output: (16, 4, 1)
771+
772+ Input: rolling, hwe-rolling, hwe-rolling-lowlatency
773+ Output: (999, 999, 0)
774+
775+ Input: hwe-rolling-edge, hwe-rolling-lowlatency-edge
776+ Output: (999, 999, 1)
777+ """
778+ parts = string.split('-')
779+ parts_len = len(parts)
780+ if parts_len == 1:
781+ # Just the release name, e.g xenial or 16.04
782+ release = string
783+ edge = False
784+ elif parts_len == 2:
785+ # hwe kernel, e.g hwe-x or hwe-16.04
786+ release = parts[1]
787+ edge = False
788+ elif parts_len == 3:
789+ # hwe edge or lowlatency kernel,
790+ # e.g hwe-16.04-edge or hwe-16.04-lowlatency
791+ release = parts[1]
792+ if parts[2] == 'edge':
793+ edge = True
794+ else:
795+ edge = False
796+ elif parts_len == 4:
797+ # hwe edge lowlatency kernel, e.g hwe-16.04-lowlatency-edge
798+ release = parts[1]
799+ if parts[3] == 'edge':
800+ edge = True
801+ else:
802+ edge = False
803+ else:
804+ raise ValueError("Unknown release or kernel %s!" % string)
805+
806+ if release == 'rolling':
807+ # Rolling kernels are always the latest
808+ version = [999, 999]
809+ else:
810+ # First try to get release info from the SimpleStream
811+ ubuntu_release = get_release_from_db(release)
812+ if ubuntu_release is None:
813+ # Fall back on using the UbuntuDistroInfo library
814+ ubuntu_release = get_release_from_distro_info(release)
815+ if ubuntu_release is None:
816+ raise ValueError(
817+ "%s not found amoungst the known Ubuntu releases!" % string)
818+ # Remove 'LTS' from version if it exists
819+ version = ubuntu_release['version'].split(' ')[0]
820+ # Convert the version into a list of ints
821+ version = [int(seg) for seg in version.split('.')]
822+ if edge:
823+ # Ensure edge kernels are viewed as newer than non-edge
824+ version += [1]
825+ else:
826+ version += [0]
827+ return tuple(version)
828+
829+
830 def release_a_newer_than_b(a, b):
831 """Compare two Ubuntu releases and return true if a >= b.
832
833- The release names can be the full release name(e.g Precise, Trusty), or
834- a hardware enablement(e.g hwe-p, hwe-t). The function wraps around the
835- letter 'p' as Precise was the first version of Ubuntu MAAS supported
836+ The release names can be the full release name(e.g Precise, Trusty),
837+ release versions(e.g 12.04, 16.04), or an hwe kernel(e.g hwe-p, hwe-16.04,
838+ hwe-rolling-lowlatency-edge).
839 """
840- def get_release_num(release):
841- release = release.lower()
842- if 'hwe-' in release:
843- release = release.replace('hwe-', '')
844- return ord(release[0])
845-
846- # Compare release versions based off of the first letter of their
847- # release name or the letter in hwe-<letter>. Wrap around the letter
848- # 'p' as that is the first version of Ubuntu MAAS supported.
849- num_a = get_release_num(a)
850- num_b = get_release_num(b)
851- num_wrap = ord('p')
852-
853- if((num_a >= num_wrap and num_b >= num_wrap and num_a >= num_b) or
854- (num_a < num_wrap and num_b >= num_wrap and num_a < num_b) or
855- (num_a < num_wrap and num_b < num_wrap and num_a >= num_b)):
856- return True
857- else:
858- return False
859+ ver_a = get_release_version_from_string(a)
860+ ver_b = get_release_version_from_string(b)
861+ return ver_a >= ver_b
862
863
864 def validate_hwe_kernel(
865@@ -306,10 +417,10 @@
866 subarch)
867
868 os_release = osystem + '/' + distro_series
869- usable_kernels = BootResource.objects.get_usable_hwe_kernels(
870- os_release, arch)
871
872 if hwe_kernel and hwe_kernel.startswith('hwe-'):
873+ usable_kernels = BootResource.objects.get_usable_hwe_kernels(
874+ os_release, arch)
875 if hwe_kernel not in usable_kernels:
876 raise ValidationError(
877 '%s is not available for %s on %s.' %
878@@ -324,6 +435,13 @@
879 (hwe_kernel, min_hwe_kernel))
880 return hwe_kernel
881 elif(min_hwe_kernel and min_hwe_kernel.startswith('hwe-')):
882+ kernel_parts = min_hwe_kernel.split('-')
883+ if len(kernel_parts) == 2:
884+ kflavor = 'generic'
885+ else:
886+ kflavor = kernel_parts[2]
887+ usable_kernels = BootResource.objects.get_usable_hwe_kernels(
888+ os_release, arch, kflavor)
889 for i in usable_kernels:
890 if(release_a_newer_than_b(i, min_hwe_kernel) and
891 release_a_newer_than_b(i, distro_series)):
892@@ -331,7 +449,11 @@
893 raise ValidationError(
894 '%s has no kernels availible which meet min_hwe_kernel(%s).' %
895 (distro_series, min_hwe_kernel))
896- return 'hwe-' + distro_series[0]
897+ for kernel in BootResource.objects.get_usable_hwe_kernels(
898+ os_release, arch, 'generic'):
899+ if release_a_newer_than_b(kernel, distro_series):
900+ return kernel
901+ raise ValidationError('%s has no kernels available.' % distro_series)
902
903
904 def validate_min_hwe_kernel(min_hwe_kernel):
905@@ -341,4 +463,5 @@
906 usable_kernels = BootResource.objects.get_usable_hwe_kernels()
907 if min_hwe_kernel not in usable_kernels:
908 raise ValidationError('%s is not a usable kernel.' % min_hwe_kernel)
909- return min_hwe_kernel
910+ else:
911+ return min_hwe_kernel
912
913=== modified file 'src/maasserver/utils/tests/test_osystems.py'
914--- src/maasserver/utils/tests/test_osystems.py 2016-06-29 09:52:24 +0000
915+++ src/maasserver/utils/tests/test_osystems.py 2016-08-31 06:33:18 +0000
916@@ -25,7 +25,10 @@
917 from maasserver.utils import osystems as osystems_module
918 from maasserver.utils.osystems import (
919 get_distro_series_initial,
920+ get_release_from_db,
921+ get_release_from_distro_info,
922 get_release_requires_key,
923+ get_release_version_from_string,
924 list_all_releases_requiring_keys,
925 list_all_usable_osystems,
926 list_all_usable_releases,
927@@ -35,6 +38,7 @@
928 make_hwe_kernel_ui_text,
929 release_a_newer_than_b,
930 validate_hwe_kernel,
931+ validate_min_hwe_kernel,
932 validate_osystem_and_distro_series,
933 )
934 from maastesting.matchers import MockAnyCall
935@@ -290,13 +294,10 @@
936 self.assertEqual('trusty (hwe-t)', make_hwe_kernel_ui_text('hwe-t'))
937
938 def test_make_hwe_kernel_ui_returns_kernel_when_none_found(self):
939- # Since this is testing that our fall final fall back returns just the
940- # kernel name when the release isn't found in BootSourceCache or
941- # UbuntuDistroInfo we patch out UbuntuDistroInfo so nothing is found.
942- self.patch(UbuntuDistroInfo, 'all').value = []
943+ unknown_kernel = factory.make_name('kernel')
944 self.assertEqual(
945- 'hwe-m',
946- make_hwe_kernel_ui_text('hwe-m'))
947+ unknown_kernel,
948+ make_hwe_kernel_ui_text(unknown_kernel))
949
950
951 class TestValidateOsystemAndDistroSeries(MAASServerTestCase):
952@@ -339,20 +340,102 @@
953 validate_osystem_and_distro_series(osystem['name'], release + '*'))
954
955
956+class TestGetReleaseVersionFromString(MAASServerTestCase):
957+
958+ def __init__(self, *args, **kwargs):
959+ super().__init__(*args, **kwargs)
960+ ubuntu = UbuntuDistroInfo()
961+ # We can't test with releases older than Precise as they have duplicate
962+ # names(e.g Wily and Warty) which will break the old style kernel
963+ # tests.
964+ valid_releases = [
965+ row for row in ubuntu._rows
966+ if int(row['version'].split('.')[0]) >= 12
967+ ]
968+ release = random.choice(valid_releases)
969+ # Remove 'LTS' from version if it exists
970+ version_str = release['version'].split(' ')[0]
971+ # Convert the version into a list of ints
972+ version_tuple = tuple([int(seg) for seg in version_str.split('.')])
973+
974+ self.scenarios = (
975+ ("Release name", {
976+ "string": release['series'],
977+ "expected": version_tuple + tuple([0]),
978+ }),
979+ ("Release version", {
980+ "string": version_str,
981+ "expected": version_tuple + tuple([0]),
982+ }),
983+ ("Old style kernel", {
984+ "string": "hwe-%s" % release['series'][0],
985+ "expected": version_tuple + tuple([0]),
986+ }),
987+ ("New style kernel", {
988+ "string": "hwe-%s" % version_str,
989+ "expected": version_tuple + tuple([0]),
990+ }),
991+ ("New style edge kernel", {
992+ "string": "hwe-%s-edge" % version_str,
993+ "expected": version_tuple + tuple([1]),
994+ }),
995+ ("New style low latency kernel", {
996+ "string": "hwe-%s-lowlatency" % version_str,
997+ "expected": version_tuple + tuple([0]),
998+ }),
999+ ("New style edge low latency kernel", {
1000+ "string": "hwe-%s-lowlatency-edge" % version_str,
1001+ "expected": version_tuple + tuple([1]),
1002+ }),
1003+ ("Rolling kernel", {
1004+ "string": "hwe-rolling",
1005+ "expected": tuple([999, 999, 0]),
1006+ }),
1007+ ("Rolling edge kernel", {
1008+ "string": "hwe-rolling-edge",
1009+ "expected": tuple([999, 999, 1]),
1010+ }),
1011+ ("Rolling lowlatency kernel", {
1012+ "string": "hwe-rolling-lowlatency",
1013+ "expected": tuple([999, 999, 0]),
1014+ }),
1015+ ("Rolling lowlatency edge kernel", {
1016+ "string": "hwe-rolling-lowlatency-edge",
1017+ "expected": tuple([999, 999, 1]),
1018+ }),
1019+ )
1020+
1021+ def test_get_release_version_from_string(self):
1022+ self.assertEquals(
1023+ self.expected,
1024+ get_release_version_from_string(self.string))
1025+
1026+
1027 class TestReleaseANewerThanB(MAASServerTestCase):
1028
1029- def test_release_a_newer_than_b(self):
1030- # Since we wrap around 'p' we want to use 'p' as our starting point
1031- alphabet = ([chr(i) for i in range(ord('p'), ord('z') + 1)] +
1032- [chr(i) for i in range(ord('a'), ord('p'))])
1033- previous_true = 0
1034- for i in alphabet:
1035- true_count = 0
1036- for j in alphabet:
1037- if release_a_newer_than_b('hwe-' + i, j):
1038- true_count += 1
1039- previous_true += 1
1040- self.assertEqual(previous_true, true_count)
1041+ def test_a_newer_than_b_true(self):
1042+ self.assertTrue(
1043+ release_a_newer_than_b(
1044+ 'hwe-rolling',
1045+ factory.make_kernel_string(can_be_release_or_version=True)))
1046+
1047+ def test_a_equal_to_b_true(self):
1048+ string = factory.make_kernel_string(can_be_release_or_version=True)
1049+ self.assertTrue(release_a_newer_than_b(string, string))
1050+
1051+ def test_a_less_than_b_false(self):
1052+ self.assertFalse(
1053+ release_a_newer_than_b(
1054+ factory.make_kernel_string(can_be_release_or_version=True),
1055+ 'hwe-rolling'))
1056+
1057+ def test_accounts_for_edge(self):
1058+ self.assertFalse(
1059+ release_a_newer_than_b('hwe-rolling', 'hwe-rolling-edge'))
1060+
1061+ def test_kernel_flavor_doesnt_make_difference(self):
1062+ self.assertTrue(release_a_newer_than_b(
1063+ 'hwe-rolling', 'hwe-rolling-lowlatency'))
1064
1065
1066 class TestValidateHweKernel(MAASServerTestCase):
1067@@ -474,3 +557,108 @@
1068 self.assertThat(
1069 mock_get_config, MockAnyCall('commissioning_distro_series'))
1070 self.assertEqual('hwe-v', kernel)
1071+
1072+
1073+class TestValidateMinHweKernel(MAASServerTestCase):
1074+
1075+ def test_validates_kernel(self):
1076+ kernel = factory.make_kernel_string(generic_only=True)
1077+ self.patch(
1078+ BootResource.objects,
1079+ 'get_usable_hwe_kernels').return_value = (kernel,)
1080+ self.assertEquals(kernel, validate_min_hwe_kernel(kernel))
1081+
1082+ def test_returns_empty_string_when_none(self):
1083+ self.assertEquals("", validate_min_hwe_kernel(None))
1084+
1085+ def test_raises_exception_when_not_found(self):
1086+ self.assertRaises(
1087+ ValidationError,
1088+ validate_min_hwe_kernel, factory.make_kernel_string())
1089+
1090+ def test_raises_exception_when_lowlatency(self):
1091+ self.assertRaises(
1092+ ValidationError, validate_min_hwe_kernel, 'hwe-16.04-lowlatency')
1093+
1094+
1095+class TestGetReleaseFromDistroInfo(MAASServerTestCase):
1096+
1097+ def pick_release(self):
1098+ ubuntu = UbuntuDistroInfo()
1099+ supported_releases = [
1100+ release for release in ubuntu._rows
1101+ if int(release['version'].split('.')[0]) >= 12
1102+ ]
1103+ return random.choice(supported_releases)
1104+
1105+ def test_finds_by_series(self):
1106+ release = self.pick_release()
1107+ self.assertItemsEqual(
1108+ release, get_release_from_distro_info(release['series']))
1109+
1110+ def test_finds_by_series_first_letter(self):
1111+ release = self.pick_release()
1112+ self.assertItemsEqual(
1113+ release, get_release_from_distro_info(release['series'][0]))
1114+
1115+ def test_finds_by_version(self):
1116+ release = self.pick_release()
1117+ self.assertItemsEqual(
1118+ release, get_release_from_distro_info(release['version']))
1119+
1120+ def test_returns_none_when_not_found(self):
1121+ self.assertIsNone(
1122+ get_release_from_distro_info(factory.make_name('string')))
1123+
1124+
1125+class TestGetReleaseFromDB(MAASServerTestCase):
1126+
1127+ def make_boot_source_cache(self):
1128+ # Disable boot sources signals otherwise the test fails due to unrun
1129+ # post-commit tasks at the end of the test.
1130+ self.useFixture(SignalsDisabled("bootsources"))
1131+ ubuntu = UbuntuDistroInfo()
1132+ supported_releases = [
1133+ release for release in ubuntu._rows
1134+ if int(release['version'].split('.')[0]) >= 12
1135+ ]
1136+ release = random.choice(supported_releases)
1137+ subarch = "hwe-%s" % release['version'].split(' ')[0]
1138+ factory.make_BootSourceCache(
1139+ os='ubuntu',
1140+ arch=factory.make_name('arch'),
1141+ subarch=subarch,
1142+ release=release['series'],
1143+ release_codename=release['codename'],
1144+ release_title=release['version'],
1145+ support_eol=release['eol-server'],
1146+ )
1147+ return release
1148+
1149+ def test_finds_by_subarch(self):
1150+ release = self.make_boot_source_cache()
1151+ self.assertEquals(
1152+ release['series'],
1153+ get_release_from_db(release['version'].split(' ')[0])['series'])
1154+
1155+ def test_finds_by_release(self):
1156+ release = self.make_boot_source_cache()
1157+ self.assertEquals(
1158+ release['version'],
1159+ get_release_from_db(release['series'])['version'])
1160+
1161+ def test_finds_by_release_first_letter(self):
1162+ release = self.make_boot_source_cache()
1163+ self.assertEquals(
1164+ release['version'],
1165+ get_release_from_db(release['series'][0])['version'])
1166+
1167+ def test_finds_by_version(self):
1168+ release = self.make_boot_source_cache()
1169+ self.assertItemsEqual(
1170+ release['series'],
1171+ get_release_from_db(release['version'])['series'])
1172+
1173+ def test_returns_none_when_not_found(self):
1174+ self.assertIsNone(
1175+ get_release_from_db(factory.make_name('string')))
1176
1177=== modified file 'src/maasserver/websockets/handlers/general.py'
1178--- src/maasserver/websockets/handlers/general.py 2016-08-18 15:42:50 +0000
1179+++ src/maasserver/websockets/handlers/general.py 2016-08-31 06:33:18 +0000
1180@@ -46,6 +46,7 @@
1181 'known_architectures',
1182 'pockets_to_disable',
1183 'hwe_kernels',
1184+ 'min_hwe_kernels',
1185 'default_min_hwe_kernel',
1186 'osinfo',
1187 'machine_actions',
1188@@ -77,6 +78,16 @@
1189 return list_hwe_kernel_choices(
1190 BootResource.objects.get_usable_hwe_kernels())
1191
1192+ def min_hwe_kernels(self, params):
1193+ """Return all supported min_hwe_kernels.
1194+
1195+ This filters out all non-generic kernel flavors. The user can select
1196+ the flavor during deployment.
1197+ """
1198+ return list_hwe_kernel_choices(
1199+ BootResource.objects.get_usable_hwe_kernels()
1200+ )
1201+
1202 def default_min_hwe_kernel(self, params):
1203 """Return the default_min_hwe_kernel."""
1204 return Config.objects.get_config('default_min_hwe_kernel')
1205
1206=== modified file 'src/maasserver/websockets/handlers/tests/test_general.py'
1207--- src/maasserver/websockets/handlers/tests/test_general.py 2016-08-18 15:42:50 +0000
1208+++ src/maasserver/websockets/handlers/tests/test_general.py 2016-08-31 06:33:18 +0000
1209@@ -29,6 +29,12 @@
1210
1211 class TestGeneralHandler(MAASServerTestCase):
1212
1213+ def setUp(self):
1214+ super().setUp()
1215+ # Disable boot sources signals otherwise the test fails due to unrun
1216+ # post-commit tasks at the end of the test.
1217+ self.useFixture(SignalsDisabled("bootsources"))
1218+
1219 def dehydrate_actions(self, actions, node_type=None):
1220 return [
1221 {
1222@@ -40,6 +46,41 @@
1223 if node_type is None or node_type in action.for_type
1224 ]
1225
1226+ def make_boot_sources(self):
1227+ kernels = []
1228+ ubuntu = UbuntuDistroInfo()
1229+ for row in ubuntu._rows:
1230+ release_year = int(row['version'].split('.')[0])
1231+ if release_year < 12:
1232+ continue
1233+ elif release_year < 16:
1234+ style = row['series'][0]
1235+ else:
1236+ style = row['version']
1237+ for kflavor in [
1238+ 'generic', 'lowlatency', 'edge', 'lowlatency-edge']:
1239+ if kflavor == 'generic':
1240+ kernel = "hwe-%s" % style
1241+ else:
1242+ kernel = "hwe-%s-%s" % (style, kflavor)
1243+ arch = factory.make_name('arch')
1244+ architecture = "%s/%s" % (arch, kernel)
1245+ release = row['series'].split(' ')[0]
1246+ factory.make_usable_boot_resource(
1247+ name="ubuntu/" + release,
1248+ kflavor=kflavor,
1249+ extra={'subarches': kernel},
1250+ architecture=architecture,
1251+ rtype=BOOT_RESOURCE_TYPE.SYNCED)
1252+ factory.make_BootSourceCache(
1253+ os="ubuntu",
1254+ arch=arch,
1255+ subarch=kernel,
1256+ release=release)
1257+ kernels.append(
1258+ (kernel, '%s (%s)' % (release, kernel)))
1259+ return kernels
1260+
1261 def test_architectures(self):
1262 arches = [
1263 "%s/%s" % (factory.make_name("arch"), factory.make_name("subarch"))
1264@@ -63,41 +104,19 @@
1265 handler.pockets_to_disable({}))
1266
1267 def test_hwe_kernels(self):
1268- ubuntu_releases = UbuntuDistroInfo()
1269- expected_output = []
1270- # Disable boot sources signals otherwise the test fails due to unrun
1271- # post-commit tasks at the end of the test.
1272- self.useFixture(SignalsDisabled("bootsources"))
1273- # Start with the first release MAAS supported. We do this
1274- # because the lookup between hwe- kernel and release can fail
1275- # when multiple releases start with the same letter. For
1276- # example both warty(4.10) and wily(15.10) will have an hwe-w
1277- # kernel. Because of this the mapping between kernel and
1278- # release will pick the release which was downloaded
1279- # first. Since precise no release has used the same first
1280- # letter so we do not have this problem with supported
1281- # releases.
1282- for release in ubuntu_releases.all[
1283- ubuntu_releases.all.index('precise'):]:
1284- kernel = 'hwe-' + release[0]
1285- arch = factory.make_name('arch')
1286- architecture = "%s/%s" % (arch, kernel)
1287- factory.make_usable_boot_resource(
1288- name="ubuntu/" + release,
1289- extra={'subarches': kernel},
1290- architecture=architecture,
1291- rtype=BOOT_RESOURCE_TYPE.SYNCED)
1292- factory.make_BootSourceCache(
1293- os="ubuntu",
1294- arch=arch,
1295- subarch=kernel,
1296- release=release)
1297- expected_output.append((kernel, '%s (%s)' % (release, kernel)))
1298+ expected_output = self.make_boot_sources()
1299 handler = GeneralHandler(factory.make_User(), {})
1300 self.assertItemsEqual(
1301 sorted(expected_output, key=lambda choice: choice[0]),
1302 sorted(handler.hwe_kernels({}), key=lambda choice: choice[0]))
1303
1304+ def test_hwe_min_kernels(self):
1305+ expected_output = self.make_boot_sources()
1306+ handler = GeneralHandler(factory.make_User(), {})
1307+ self.assertItemsEqual(
1308+ sorted(expected_output, key=lambda choice: choice[0]),
1309+ sorted(handler.min_hwe_kernels({}), key=lambda choice: choice[0]))
1310+
1311 def test_osinfo(self):
1312 handler = GeneralHandler(factory.make_User(), {})
1313 osystem = make_osystem_with_releases(self)
1314
1315=== modified file 'src/maastesting/factory.py'
1316--- src/maastesting/factory.py 2016-08-18 11:02:26 +0000
1317+++ src/maastesting/factory.py 2016-08-31 06:33:18 +0000
1318@@ -32,6 +32,7 @@
1319 import urllib.request
1320 from uuid import uuid1
1321
1322+from distro_info import UbuntuDistroInfo
1323 from maastesting.fixtures import TempDirectory
1324 from netaddr import (
1325 IPAddress,
1326@@ -619,5 +620,30 @@
1327 cmd=[self.make_name("command")],
1328 output=factory.make_bytes())
1329
1330+ def make_kernel_string(
1331+ self, can_be_release_or_version=False, generic_only=False):
1332+ ubuntu = UbuntuDistroInfo()
1333+ # Only select from MAAS supported releases so we don't have to deal
1334+ # with versions name overlap(e.g Warty and Wily).
1335+ supported_releases = [
1336+ release for release in ubuntu._rows
1337+ if int(release['version'].split('.')[0]) >= 12
1338+ ]
1339+ release = random.choice(supported_releases)
1340+ # Remove 'LTS' from version if it exists
1341+ version_str = release['version'].split(' ')[0]
1342+ strings = [
1343+ "hwe-%s" % release['series'][0], "hwe-%s" % version_str,
1344+ "hwe-%s-edge" % version_str,
1345+ ]
1346+ if not generic_only:
1347+ strings += [
1348+ "hwe-%s-lowlatency" % version_str,
1349+ "hwe-%s-lowlatency-edge" % version_str,
1350+ ]
1351+ if can_be_release_or_version:
1352+ strings += [release['series'], version_str]
1353+ return random.choice(strings)
1354+
1355 # Create factory singleton.
1356 factory = Factory()
1357
1358=== modified file 'src/provisioningserver/import_images/download_descriptions.py'
1359--- src/provisioningserver/import_images/download_descriptions.py 2016-06-16 20:51:55 +0000
1360+++ src/provisioningserver/import_images/download_descriptions.py 2016-08-31 06:33:18 +0000
1361@@ -36,7 +36,7 @@
1362 """Return a subset of dict `item` for storing in a boot images dict."""
1363 keys_to_keep = [
1364 'content_id', 'product_name', 'version_name', 'path', 'subarches',
1365- 'release_codename', 'release_title', 'support_eol']
1366+ 'release_codename', 'release_title', 'support_eol', 'kflavor']
1367 compact_item = {
1368 key: item[key]
1369 for key in keys_to_keep
1370@@ -94,12 +94,14 @@
1371 self.boot_images_dict.set(
1372 base_image._replace(subarch=subarch), compact_item)
1373
1374- # HWE resources with generic, should map to the HWE that ships with
1375- # that release.
1376- hwe_arch = 'hwe-%s' % release[0]
1377- if subarch == hwe_arch and 'generic' in subarches:
1378- self.boot_images_dict.set(
1379- base_image._replace(subarch='generic'), compact_item)
1380+ if os == 'ubuntu' and item.get('version') is not None:
1381+ # HWE resources with generic, should map to the HWE that ships with
1382+ # that release. Starting with Xenial kernels changed from using the
1383+ # naming format hwe-<letter> to hwe-<release>. Look for both.
1384+ hwe_archs = ["hwe-%s" % item['version'], "hwe-%s" % release[0]]
1385+ if subarch in hwe_archs and 'generic' in subarches:
1386+ self.boot_images_dict.set(
1387+ base_image._replace(subarch='generic'), compact_item)
1388
1389 def sync(self, reader, path):
1390 try:
1391
1392=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
1393--- src/provisioningserver/import_images/tests/test_boot_resources.py 2016-07-06 18:30:19 +0000
1394+++ src/provisioningserver/import_images/tests/test_boot_resources.py 2016-08-31 06:33:18 +0000
1395@@ -476,6 +476,7 @@
1396 self.assertItemsEqual(
1397 [
1398 'content_id',
1399+ 'kflavor',
1400 'path',
1401 'product_name',
1402 'version_name',
1403
1404=== modified file 'src/provisioningserver/import_images/tests/test_download_descriptions.py'
1405--- src/provisioningserver/import_images/tests/test_download_descriptions.py 2016-07-06 18:43:07 +0000
1406+++ src/provisioningserver/import_images/tests/test_download_descriptions.py 2016-08-31 06:33:18 +0000
1407@@ -234,12 +234,14 @@
1408 class TestRepoDumper(MAASTestCase):
1409 """Tests for `RepoDumper`."""
1410
1411- def make_item(self, os=None, release=None, arch=None,
1412+ def make_item(self, os=None, release=None, version=None, arch=None,
1413 subarch=None, subarches=None, label=None):
1414 if os is None:
1415 os = factory.make_name('os')
1416 if release is None:
1417 release = factory.make_name('release')
1418+ if version is None:
1419+ version = factory.make_name('version')
1420 if arch is None:
1421 arch = factory.make_name('arch')
1422 if subarch is None:
1423@@ -257,6 +259,7 @@
1424 'path': factory.make_name('path'),
1425 'os': os,
1426 'release': release,
1427+ 'version': version,
1428 'arch': arch,
1429 'subarch': subarch,
1430 'subarches': ','.join(subarches),
1431@@ -307,7 +310,7 @@
1432 label=item['label'])
1433 self.assertEqual(compat_item, boot_images_dict.mapping[image_spec])
1434
1435- def test_insert_item_sets_generic_to_release_item_for_hwe(self):
1436+ def test_insert_item_sets_generic_to_release_item_for_hwe_letter(self):
1437 boot_images_dict = BootImageMapping()
1438 dumper = RepoDumper(boot_images_dict)
1439 os = 'ubuntu'
1440@@ -338,6 +341,37 @@
1441 label=label)
1442 self.assertEqual(compat_item, boot_images_dict.mapping[image_spec])
1443
1444+ def test_insert_item_sets_generic_to_release_item_for_hwe_version(self):
1445+ boot_images_dict = BootImageMapping()
1446+ dumper = RepoDumper(boot_images_dict)
1447+ os = 'ubuntu'
1448+ release = 'xenial'
1449+ arch = 'amd64'
1450+ label = 'release'
1451+ hwep_subarch = 'hwe-16.04'
1452+ hwep_subarches = ['generic', 'hwe-16.04', 'hwe-16.10']
1453+ hwes_subarch = 'hwe-16.10'
1454+ hwes_subarches = ['generic', 'hwe-16.04', 'hwe-16.10']
1455+ hwep_item, compat_item = self.make_item(
1456+ os=os, release=release,
1457+ arch=arch, subarch=hwep_subarch,
1458+ subarches=hwep_subarches, label=label)
1459+ hwes_item, _ = self.make_item(
1460+ os=os, release=release,
1461+ arch=arch, subarch=hwes_subarch,
1462+ subarches=hwes_subarches, label=label)
1463+ self.patch(
1464+ download_descriptions,
1465+ 'products_exdata').side_effect = [hwep_item, hwes_item]
1466+ for _ in range(2):
1467+ dumper.insert_item(
1468+ sentinel.data, sentinel.src, sentinel.target,
1469+ sentinel.pedigree, sentinel.contentsource)
1470+ image_spec = make_image_spec(
1471+ os=os, release=release, arch=arch, subarch='generic',
1472+ label=label)
1473+ self.assertEqual(compat_item, boot_images_dict.mapping[image_spec])
1474+
1475 def test_sync_does_not_propagate_ioerror(self):
1476 mock_sync = self.patch(download_descriptions.BasicMirrorWriter, "sync")
1477 mock_sync.side_effect = IOError()
1478
1479=== modified file 'utilities/check-imports'
1480--- utilities/check-imports 2016-08-15 16:41:18 +0000
1481+++ utilities/check-imports 2016-08-31 06:33:18 +0000
1482@@ -147,6 +147,7 @@
1483 StandardLibraries = Pattern(
1484 map("{0}|{0}.**".format, python_standard_libs))
1485 TestingLibraries = Pattern(
1486+ "distro_info.UbuntuDistroInfo",
1487 "django_nose|django_nose.**",
1488 "dns|dns.**",
1489 "fixtures|fixtures.**",