Merge lp:~abentley/juju-release-tools/azure-arm-streams-latest into lp:juju-release-tools

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 360
Proposed branch: lp:~abentley/juju-release-tools/azure-arm-streams-latest
Merge into: lp:juju-release-tools
Prerequisite: lp:~abentley/juju-release-tools/azure-streams-tweaks
Diff against target: 360 lines (+166/-22)
4 files modified
azure_image_streams.py (+72/-16)
make_image_streams.py (+6/-1)
tests/test_azure_image_streams.py (+86/-4)
tests/test_make_image_streams.py (+2/-1)
To merge this branch: bzr merge lp:~abentley/juju-release-tools/azure-arm-streams-latest
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+313024@code.launchpad.net

Commit message

Get ubuntu images by sku

Description of the change

This branch changes the azurm-arm streams to use sku to look up ubuntu images, rather than attempting to convert cloudware images.

It also causes them to use 'latest' rather than a specific version, which matches the current Juju behaviour. Supplying Ubuntu images this way is intended as a stopgap until Cloudware can publish Azure streams.

It also forces the id to be provided at the lowest level, even in cases where this is redundant. This works around a Juju bug.

Importing simplestreams has the side effect of configuring logging (in a not-helpful way, i.e. disabling all output). This branch works around this by running logging.basicConfig before simplestreams is imported.

It also adds some windows image types and expected-missing cloudware images.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'azure_image_streams.py'
2--- azure_image_streams.py 2016-12-12 14:24:50 +0000
3+++ azure_image_streams.py 2016-12-12 14:24:50 +0000
4@@ -1,6 +1,5 @@
5 import logging
6 import re
7-import sys
8
9 # Tested on Azure 2.0 rc5 API
10 from azure.common.credentials import ServicePrincipalCredentials
11@@ -16,6 +15,8 @@
12 from simplestreams import mirrors
13 from simplestreams import util
14
15+from build_package import juju_series
16+
17
18 CANONICAL = 'Canonical'
19 MS_VSTUDIO = 'MicrosoftVisualStudio'
20@@ -24,10 +25,12 @@
21 WINDOWS = 'Windows'
22 WINDOWS_SERVER = 'WindowsServer'
23 IMAGE_SPEC = [
24- ('win81', MS_VSTUDIO, WINDOWS, '8.1-Enterprise-N'),
25- ('win10', MS_VSTUDIO, WINDOWS, '10-Enterprise-N'),
26+ ('win81', MS_VSTUDIO, WINDOWS, 'Win8.1-Ent-N'),
27+ ('win10', MS_VSTUDIO, WINDOWS, 'Windows-10-N-x64'),
28 ('win2012', MS_SERVER, WINDOWS_SERVER, '2012-Datacenter'),
29 ('win2012r2', MS_SERVER, WINDOWS_SERVER, '2012-R2-Datacenter'),
30+ ('win2016', MS_SERVER, WINDOWS_SERVER, '2016-Datacenter'),
31+ ('win2016nano', MS_SERVER, WINDOWS_SERVER, '2016-Nano-Server'),
32 ('centos7', 'OpenLogic', 'CentOS', '7.1'),
33 ]
34
35@@ -64,9 +67,17 @@
36 }
37
38
39+def logger():
40+ return logging.getLogger('azure_image_streams')
41+
42+
43 # Thorough investigation has not found an equivalent for these in the
44 # Azure-ARM image repository.
45-EXPECTED_MISSING = frozenset({('12.04.2-LTS', '12.04.201212180')})
46+EXPECTED_MISSING = frozenset({
47+ ('12.04.2-LTS', '12.04.201212180'),
48+ ('16.04.0-LTS', '16.04.201611220'),
49+ ('16.04.0-LTS', '16.04.201611300'),
50+ })
51
52
53 class MissingImage(Exception):
54@@ -202,14 +213,14 @@
55 spec = full_spec[1:]
56 location_versions = {}
57 for location in locations:
58- logging.info('Retrieving image data in {}'.format(
59+ logger().debug('Retrieving image data in {}'.format(
60 location.display_name))
61 try:
62 versions = client.virtual_machine_images.list(location.name,
63 *spec)
64 except CloudError:
65 template = 'Could not find {} {} {} in {location}'
66- logging.warning(template.format(
67+ logger().warning(template.format(
68 *spec, location=location.display_name))
69 continue
70 for version in versions:
71@@ -226,7 +237,8 @@
72 endpoint)
73
74
75-def make_item(version_name, urn_version, full_spec, location_name, endpoint):
76+def make_item(version_name, urn_version, full_spec, location_name, endpoint,
77+ stream='released', item_version=None, release=None):
78 """Make a simplestreams Item for a version.
79
80 Version name is the simplestreams version_name.
81@@ -239,8 +251,12 @@
82 """
83 URN = ':'.join(full_spec[1:] + (urn_version,))
84 product_name = 'com.ubuntu.cloud:server:{}:amd64'.format(full_spec[0])
85+ if release is None:
86+ release = full_spec[0]
87+ if item_version is None:
88+ item_version = full_spec[0]
89 return Item(
90- 'com.ubuntu.cloud:released:azure',
91+ 'com.ubuntu.cloud:{}:azure'.format(stream),
92 product_name,
93 version_name, ITEM_NAMES[location_name], {
94 'arch': 'amd64',
95@@ -249,8 +265,8 @@
96 'id': URN,
97 'label': 'release',
98 'endpoint': endpoint,
99- 'release': full_spec[0],
100- 'version': full_spec[0],
101+ 'release': release,
102+ 'version': item_version,
103 }
104 )
105
106@@ -292,16 +308,56 @@
107 sub_client = SubscriptionClient(credentials)
108 client = ComputeManagementClient(credentials, subscription_id)
109 locations = sub_client.subscriptions.list_locations(subscription_id)
110- ci_items = ItemList.items_from_url(
111- 'http://cloud-images.ubuntu.com/releases/streams/v1')
112- items, unknown_locations = convert_cloud_images_items(
113- client, locations, ci_items)
114- sys.stderr.write('Unknown locations: {}\n'.format(', '.join(
115- sorted(unknown_locations))))
116+ items = find_ubuntu_items(client, locations)
117 items.extend(find_spec_items(client, locations))
118 return items
119
120
121+def make_ubuntu_item(endpoint, location_name, sku_name):
122+ match = re.match(r'(\d\d\.\d\d)(\.\d+)?-?(.*)', sku_name)
123+ if match is None:
124+ logger().info('Skipping {}'.format(sku_name))
125+ return None
126+ tag = match.group(3)
127+ if tag in ('DAILY', 'DAILY-LTS'):
128+ stream = 'daily'
129+ elif tag in ('', 'LTS'):
130+ stream = 'released'
131+ else:
132+ logger().info('Skipping {}'.format(sku_name))
133+ return None
134+ minor_version = match.group(1)
135+ try:
136+ release = juju_series.get_name(minor_version)
137+ except KeyError:
138+ logger().warning("Can't find name for {}".format(release))
139+ return None
140+ full_spec = (minor_version, CANONICAL, UBUNTU_SERVER, sku_name)
141+ return make_item(sku_name, 'latest', full_spec, location_name,
142+ endpoint, stream=stream, release=release)
143+
144+
145+def find_ubuntu_items(client, locations):
146+ """Make simplestreams Items for existing Azure images.
147+
148+ All versions of all images matching IMAGE_SPEC will be returned.
149+
150+ all_credentials is a dict of credentials in the credentials.yaml
151+ structure, used to create Azure credentials.
152+ """
153+ items = []
154+ for location in locations:
155+ skus = client.virtual_machine_images.list_skus(
156+ location.name, CANONICAL, UBUNTU_SERVER)
157+ for sku in skus:
158+ item = make_ubuntu_item(client.config.base_url, location.name,
159+ sku.name)
160+ if item is None:
161+ continue
162+ items.append(item)
163+ return items
164+
165+
166 def find_spec_items(client, locations):
167 """Make simplestreams Items for existing Azure images.
168
169
170=== modified file 'make_image_streams.py'
171--- make_image_streams.py 2016-07-21 19:33:35 +0000
172+++ make_image_streams.py 2016-12-12 14:24:50 +0000
173@@ -4,6 +4,9 @@
174 from argparse import ArgumentParser
175 from copy import deepcopy
176 from datetime import datetime
177+import logging
178+# Done early to prevent Simplestreams from messing with the log configuration.
179+logging.basicConfig(level=logging.INFO)
180 import os
181 import sys
182 from textwrap import dedent
183@@ -22,6 +25,8 @@
184 )
185 from simplestreams import util
186
187+log = logging.getLogger(
188+ "requests.packages.urllib3.connectionpool").setLevel(logging.WARNING)
189
190 AWS = 'aws'
191 AZURE = 'azure'
192@@ -215,7 +220,7 @@
193 data = {'updated': updated, 'datatype': 'image-ids'}
194 trees = items2content_trees(items, data)
195 write_juju_streams(out_dir, trees, updated, [
196- 'path', 'sha256', 'md5', 'size', 'virt', 'root_store'])
197+ 'path', 'sha256', 'md5', 'size', 'virt', 'root_store', 'id'])
198
199
200 def write_juju_streams(out_d, trees, updated, sticky):
201
202=== modified file 'tests/test_azure_image_streams.py'
203--- tests/test_azure_image_streams.py 2016-12-12 14:24:50 +0000
204+++ tests/test_azure_image_streams.py 2016-12-12 14:24:50 +0000
205@@ -13,12 +13,14 @@
206 CANONICAL,
207 convert_cloud_images_items,
208 convert_item_to_arm,
209+ find_ubuntu_items,
210 get_azure_credentials,
211 IMAGE_SPEC,
212 make_spec_items,
213 MissingImage,
214 make_item,
215 make_azure_items,
216+ make_ubuntu_item,
217 parse_id,
218 UBUNTU_SERVER,
219 UnexpectedImage,
220@@ -287,7 +289,11 @@
221 client = Mock(spec=['config', 'virtual_machine_images'])
222 client.virtual_machine_images.list.return_value = [
223 mock_version(v) for v in versions]
224+ client.virtual_machine_images.list_skus.return_value = [
225+ mock_sku('12.04.2-LTS'),
226+ ]
227 client.config.base_url = 'http://example.com/arm'
228+
229 return client
230
231
232@@ -303,6 +309,12 @@
233 return location
234
235
236+def mock_sku(name):
237+ sku = Mock()
238+ sku.name = name
239+ return sku
240+
241+
242 def make_expected(client, versions, specs):
243 expected_items = []
244 expected_calls = []
245@@ -351,19 +363,89 @@
246 expected_calls, expected_items = make_expected(client, ['3'],
247 IMAGE_SPEC)
248 location = mock_location('canadaeast', 'Canada East')
249- old_item, spec, expected_item = make_item_expected(
250- region=location.display_name, endpoint=client.config.base_url)
251- expected_items.insert(0, expected_item)
252- with self.mai_cxt(location, client, [old_item]):
253+ expected_items.insert(
254+ 0, make_item('12.04.2-LTS', 'latest', (
255+ '12.04', CANONICAL, UBUNTU_SERVER, '12.04.2-LTS'
256+ ), 'canadaeast', client.config.base_url, release='precise'))
257+ with self.mai_cxt(location, client, []):
258 items = make_azure_items(all_credentials)
259 self.assertEqual(expected_items, items)
260
261 def test_make_azure_items_no_ubuntu(self):
262 all_credentials = make_all_credentials()
263 client = mock_compute_client(['3'])
264+ client.virtual_machine_images.list_skus.return_value = []
265 expected_calls, expected_items = make_expected(client, ['3'],
266 IMAGE_SPEC)
267 location = mock_location('canadaeast', 'Canada East')
268 with self.mai_cxt(location, client, []):
269 items = make_azure_items(all_credentials)
270 self.assertEqual(expected_items, items)
271+
272+
273+class TestMakeUbuntuItem(TestCase):
274+
275+ def make_item(self, full_version='12.04.5-LTS', daily=False):
276+ stream = 'daily' if daily else 'released'
277+ return make_item(full_version, 'latest', (
278+ '12.04', CANONICAL, UBUNTU_SERVER, full_version,
279+ ), 'canadaeast', 'http://example.com', release='precise',
280+ stream=stream)
281+
282+ def test_make_ubuntu_item(self):
283+ item = make_ubuntu_item('http://example.com', 'canadaeast',
284+ '12.04.5-LTS')
285+ self.assertEqual(item, self.make_item())
286+
287+ def test_no_lts(self):
288+ item = make_ubuntu_item('http://example.com', 'canadaeast',
289+ '12.04.5')
290+ self.assertEqual(item, self.make_item('12.04.5'))
291+
292+ def test_daily(self):
293+ item = make_ubuntu_item('http://example.com', 'canadaeast',
294+ '12.04.5-DAILY')
295+ self.assertEqual(item.content_id, 'com.ubuntu.cloud:daily:azure')
296+
297+ def test_daily_lts(self):
298+ item = make_ubuntu_item('http://example.com', 'canadaeast',
299+ '12.04.5-DAILY-LTS')
300+ self.assertEqual(item.content_id, 'com.ubuntu.cloud:daily:azure')
301+
302+ def test_unknown_tag(self):
303+ item = make_ubuntu_item('http://example.com', 'canadaeast',
304+ '12.04.5-FOOBAR')
305+ self.assertIs(item, None)
306+
307+ def test_not_a_version(self):
308+ item = make_ubuntu_item('http://example.com', 'canadaeast',
309+ '12.q.5')
310+ self.assertIs(item, None)
311+
312+ def test_xenial(self):
313+ item = make_ubuntu_item('http://example.com', 'canadaeast',
314+ '16.04.5-LTS')
315+ self.assertEqual('xenial', item.data['release'])
316+
317+ def test_version(self):
318+ item = make_ubuntu_item('http://example.com', 'canadaeast',
319+ '16.04.5-LTS')
320+ self.assertEqual('16.04', item.data['version'])
321+ self.assertIn(':16.04:', item.product_name)
322+
323+
324+class TestFindUbuntuItems(TestCase):
325+
326+ def test_find_ubuntu_items(self):
327+ sku = Mock()
328+ sku.name = '16.04.3-LTS'
329+ bad_sku = Mock()
330+ bad_sku.name = '16.0q.3-LTS'
331+ client = Mock()
332+ client.virtual_machine_images.list_skus.return_value = [sku, bad_sku]
333+ client.config.base_url = 'http://example.com'
334+ location = Mock()
335+ location.name = 'canadaeast'
336+ items = find_ubuntu_items(client, [location])
337+ self.assertEqual(items, [make_ubuntu_item(
338+ 'http://example.com', 'canadaeast', '16.04.3-LTS')])
339
340=== modified file 'tests/test_make_image_streams.py'
341--- tests/test_make_image_streams.py 2016-07-20 14:00:52 +0000
342+++ tests/test_make_image_streams.py 2016-12-12 14:24:50 +0000
343@@ -355,15 +355,16 @@
344 'version': 'centos7',
345 'release': 'centos7',
346 'os': 'centos',
347- 'id': 'qux',
348 'versions': {'20010203': {
349 'items': {
350 'usww1he': {
351+ 'id': 'qux',
352 'region': 'us-west-1',
353 'root_store': 'ebs',
354 'virt': 'hvm',
355 },
356 'usee1he': {
357+ 'id': 'qux',
358 'region': 'us-east-1',
359 'root_store': 'ebs',
360 'virt': 'hvm',

Subscribers

People subscribed via source and target branches