Merge lp:~jtv/maas/extract-compose_URL into lp:~maas-committers/maas/trunk
- extract-compose_URL
- Merge into trunk
Proposed by
Jeroen T. Vermeulen
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3344 |
Proposed branch: | lp:~jtv/maas/extract-compose_URL |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
481 lines (+172/-143) 10 files modified
src/maas/development.py (+1/-1) src/maas/settings.py (+1/-1) src/maasserver/preseed.py (+2/-4) src/maasserver/tests/test_dhcp.py (+1/-1) src/provisioningserver/drivers/hardware/seamicro.py (+6/-5) src/provisioningserver/drivers/hardware/tests/test_seamicro.py (+2/-7) src/provisioningserver/utils/__init__.py (+0/-33) src/provisioningserver/utils/tests/test_url.py (+107/-0) src/provisioningserver/utils/tests/test_utils.py (+1/-91) src/provisioningserver/utils/url.py (+51/-0) |
To merge this branch: | bzr merge lp:~jtv/maas/extract-compose_URL |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | Approve | ||
Review via email: mp+241076@code.launchpad.net |
Commit message
Extract provisioningser
Description of the change
There are no substantial changes here, beyond moving code and changing some imports. The imports changes did affect some patch calls in tests, but nothing dramatic.
Jeroen
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maas/development.py' |
2 | --- src/maas/development.py 2014-10-08 11:01:44 +0000 |
3 | +++ src/maas/development.py 2014-11-07 13:40:43 +0000 |
4 | @@ -25,7 +25,7 @@ |
5 | from maas.customise_test_db import patch_db_creation |
6 | from metadataserver.address import guess_server_host |
7 | import provisioningserver.config |
8 | -from provisioningserver.utils import compose_URL |
9 | +from provisioningserver.utils.url import compose_URL |
10 | from psycopg2.extensions import ISOLATION_LEVEL_READ_COMMITTED |
11 | |
12 | # We expect the following settings to be overridden. They are mentioned here |
13 | |
14 | === modified file 'src/maas/settings.py' |
15 | --- src/maas/settings.py 2014-10-30 18:24:50 +0000 |
16 | +++ src/maas/settings.py 2014-11-07 13:40:43 +0000 |
17 | @@ -25,7 +25,7 @@ |
18 | ) |
19 | from maas.monkey import patch_get_script_prefix |
20 | from metadataserver.address import guess_server_host |
21 | -from provisioningserver.utils import compose_URL |
22 | +from provisioningserver.utils.url import compose_URL |
23 | from psycopg2.extensions import ISOLATION_LEVEL_READ_COMMITTED |
24 | |
25 | |
26 | |
27 | === modified file 'src/maasserver/preseed.py' |
28 | --- src/maasserver/preseed.py 2014-10-02 05:07:20 +0000 |
29 | +++ src/maasserver/preseed.py 2014-11-07 13:40:43 +0000 |
30 | @@ -61,11 +61,9 @@ |
31 | from metadataserver.user_data.snippets import get_snippet_context |
32 | from netaddr import IPAddress |
33 | from provisioningserver.rpc.exceptions import NoConnectionsAvailable |
34 | -from provisioningserver.utils import ( |
35 | - compose_URL, |
36 | - locate_config, |
37 | - ) |
38 | +from provisioningserver.utils import locate_config |
39 | from provisioningserver.utils.fs import read_text_file |
40 | +from provisioningserver.utils.url import compose_URL |
41 | import tempita |
42 | import yaml |
43 | |
44 | |
45 | === modified file 'src/maasserver/tests/test_dhcp.py' |
46 | --- src/maasserver/tests/test_dhcp.py 2014-09-19 03:12:47 +0000 |
47 | +++ src/maasserver/tests/test_dhcp.py 2014-11-07 13:40:43 +0000 |
48 | @@ -55,7 +55,7 @@ |
49 | ConfigureDHCPv6, |
50 | ) |
51 | from provisioningserver.rpc.testing import always_succeed_with |
52 | -from provisioningserver.utils import compose_URL |
53 | +from provisioningserver.utils.url import compose_URL |
54 | from testtools.matchers import ( |
55 | AllMatch, |
56 | ContainsAll, |
57 | |
58 | === modified file 'src/provisioningserver/drivers/hardware/seamicro.py' |
59 | --- src/provisioningserver/drivers/hardware/seamicro.py 2014-09-10 16:20:31 +0000 |
60 | +++ src/provisioningserver/drivers/hardware/seamicro.py 2014-11-07 13:40:43 +0000 |
61 | @@ -23,7 +23,8 @@ |
62 | import urlparse |
63 | |
64 | from provisioningserver.logger import get_maas_logger |
65 | -import provisioningserver.utils as utils |
66 | +from provisioningserver.utils import create_node |
67 | +from provisioningserver.utils.url import compose_URL |
68 | from seamicroclient import exceptions as seamicro_exceptions |
69 | from seamicroclient.v2 import client as seamicro_client |
70 | |
71 | @@ -202,7 +203,7 @@ |
72 | :returns: api for version, None if version not supported |
73 | """ |
74 | if version == 'v0.9': |
75 | - api = SeaMicroAPIV09(utils.compose_URL('http:///v0.9/', ip)) |
76 | + api = SeaMicroAPIV09(compose_URL('http:///v0.9/', ip)) |
77 | try: |
78 | api.login(username, password) |
79 | except urllib2.URLError: |
80 | @@ -210,7 +211,7 @@ |
81 | return None |
82 | return api |
83 | elif version == 'v2.0': |
84 | - url = utils.compose_URL('http:///v2.0', ip) |
85 | + url = compose_URL('http:///v2.0', ip) |
86 | try: |
87 | api = seamicro_client.Client( |
88 | auth_url=url, username=username, password=password) |
89 | @@ -288,13 +289,13 @@ |
90 | maaslog.info( |
91 | "Found seamicro15k node with macs %s; adding to MAAS with " |
92 | "params : %s", macs, params) |
93 | - utils.create_node(macs, 'amd64', 'sm15k', params) |
94 | + create_node(macs, 'amd64', 'sm15k', params) |
95 | |
96 | |
97 | def power_control_seamicro15k_v09(ip, username, password, server_id, |
98 | power_change, retry_count=5, retry_wait=1): |
99 | server_id = '%s/0' % server_id |
100 | - api = SeaMicroAPIV09(utils.compose_URL('http:///v0.9/', ip)) |
101 | + api = SeaMicroAPIV09(compose_URL('http:///v0.9/', ip)) |
102 | |
103 | while retry_count > 0: |
104 | api.login(username, password) |
105 | |
106 | === modified file 'src/provisioningserver/drivers/hardware/tests/test_seamicro.py' |
107 | --- src/provisioningserver/drivers/hardware/tests/test_seamicro.py 2014-09-18 12:44:38 +0000 |
108 | +++ src/provisioningserver/drivers/hardware/tests/test_seamicro.py 2014-11-07 13:40:43 +0000 |
109 | @@ -41,7 +41,6 @@ |
110 | SeaMicroError, |
111 | select_seamicro15k_api_version, |
112 | ) |
113 | -import provisioningserver.utils as utils |
114 | |
115 | |
116 | class FakeResponse: |
117 | @@ -334,9 +333,7 @@ |
118 | self.patch( |
119 | SeaMicroAPIV09, 'get', |
120 | Mock(return_value=result)) |
121 | - mock_create_node = self.patch( |
122 | - utils, |
123 | - 'create_node') |
124 | + mock_create_node = self.patch(seamicro, 'create_node') |
125 | |
126 | probe_seamicro15k_and_enlist( |
127 | ip, username, password, power_control='restapi') |
128 | @@ -418,9 +415,7 @@ |
129 | seamicro, |
130 | 'get_seamicro15k_api') |
131 | mock_get_api.return_value = fake_client |
132 | - mock_create_node = self.patch( |
133 | - utils, |
134 | - 'create_node') |
135 | + mock_create_node = self.patch(seamicro, 'create_node') |
136 | |
137 | probe_seamicro15k_and_enlist( |
138 | ip, username, password, power_control='restapi2') |
139 | |
140 | === modified file 'src/provisioningserver/utils/__init__.py' |
141 | --- src/provisioningserver/utils/__init__.py 2014-09-16 11:43:25 +0000 |
142 | +++ src/provisioningserver/utils/__init__.py 2014-11-07 13:40:43 +0000 |
143 | @@ -13,7 +13,6 @@ |
144 | |
145 | __metaclass__ = type |
146 | __all__ = [ |
147 | - "compose_URL", |
148 | "create_node", |
149 | "filter_dict", |
150 | "flatten", |
151 | @@ -36,11 +35,6 @@ |
152 | import re |
153 | import sys |
154 | from sys import _getframe as getframe |
155 | -import urllib |
156 | -from urlparse import ( |
157 | - urlparse, |
158 | - urlunparse, |
159 | - ) |
160 | from warnings import warn |
161 | |
162 | import bson |
163 | @@ -381,33 +375,6 @@ |
164 | return matched, other |
165 | |
166 | |
167 | -def compose_URL(base_url, host): |
168 | - """Produce a URL on a given hostname or IP address. |
169 | - |
170 | - This is straightforward if the IP address is a hostname or an IPv4 |
171 | - address; but if it's an IPv6 address, the URL must contain the IP address |
172 | - in square brackets as per RFC 3986. |
173 | - |
174 | - :param base_url: URL without the host part, e.g. `http:///path'. |
175 | - :param host: Host name or IP address to insert in the host part of the URL. |
176 | - :return: A URL string with the host part taken from `host`, and all others |
177 | - from `base_url`. |
178 | - """ |
179 | - if re.match('[:.0-9a-fA-F]+(?:%.+)?$', host) and host.count(':') > 0: |
180 | - # IPv6 address, without the brackets. Add square brackets. |
181 | - # In case there's a zone index (introduced by a % sign), escape it. |
182 | - netloc_host = '[%s]' % urllib.quote(host, safe=':') |
183 | - else: |
184 | - # IPv4 address, hostname, or IPv6 with brackets. Keep as-is. |
185 | - netloc_host = host |
186 | - parsed_url = urlparse(base_url) |
187 | - if parsed_url.port is None: |
188 | - netloc = netloc_host |
189 | - else: |
190 | - netloc = '%s:%d' % (netloc_host, parsed_url.port) |
191 | - return urlunparse(parsed_url._replace(netloc=netloc)) |
192 | - |
193 | - |
194 | def warn_deprecated(alternative=None): |
195 | """Issue a `DeprecationWarning` for the calling function. |
196 | |
197 | |
198 | === added file 'src/provisioningserver/utils/tests/test_url.py' |
199 | --- src/provisioningserver/utils/tests/test_url.py 1970-01-01 00:00:00 +0000 |
200 | +++ src/provisioningserver/utils/tests/test_url.py 2014-11-07 13:40:43 +0000 |
201 | @@ -0,0 +1,107 @@ |
202 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
203 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
204 | + |
205 | +"""Test utilities for URL handling.""" |
206 | + |
207 | +from __future__ import ( |
208 | + absolute_import, |
209 | + print_function, |
210 | + unicode_literals, |
211 | + ) |
212 | + |
213 | +str = None |
214 | + |
215 | +__metaclass__ = type |
216 | +__all__ = [] |
217 | + |
218 | +from random import randint |
219 | + |
220 | +from maastesting.factory import factory |
221 | +from maastesting.testcase import MAASTestCase |
222 | +from provisioningserver.utils.url import compose_URL |
223 | + |
224 | + |
225 | +class TestComposeURL(MAASTestCase): |
226 | + |
227 | + def make_path(self): |
228 | + """Return an arbitrary URL path part.""" |
229 | + return '%s/%s' % (factory.make_name('root'), factory.make_name('sub')) |
230 | + |
231 | + def make_network_interface(self): |
232 | + return 'eth%d' % randint(0, 100) |
233 | + |
234 | + def test__inserts_IPv4(self): |
235 | + ip = factory.make_ipv4_address() |
236 | + path = self.make_path() |
237 | + self.assertEqual( |
238 | + 'http://%s/%s' % (ip, path), |
239 | + compose_URL('http:///%s' % path, ip)) |
240 | + |
241 | + def test__inserts_IPv6_with_brackets(self): |
242 | + ip = factory.make_ipv6_address() |
243 | + path = self.make_path() |
244 | + self.assertEqual( |
245 | + 'http://[%s]/%s' % (ip, path), |
246 | + compose_URL('http:///%s' % path, ip)) |
247 | + |
248 | + def test__escapes_IPv6_zone_index(self): |
249 | + ip = factory.make_ipv6_address() |
250 | + zone = self.make_network_interface() |
251 | + hostname = '%s%%%s' % (ip, zone) |
252 | + path = self.make_path() |
253 | + self.assertEqual( |
254 | + 'http://[%s%%25%s]/%s' % (ip, zone, path), |
255 | + compose_URL('http:///%s' % path, hostname)) |
256 | + |
257 | + def test__inserts_bracketed_IPv6_unchanged(self): |
258 | + ip = factory.make_ipv6_address() |
259 | + hostname = '[%s]' % ip |
260 | + path = self.make_path() |
261 | + self.assertEqual( |
262 | + 'http://%s/%s' % (hostname, path), |
263 | + compose_URL('http:///%s' % path, hostname)) |
264 | + |
265 | + def test__does_not_escape_bracketed_IPv6_zone_index(self): |
266 | + ip = factory.make_ipv6_address() |
267 | + zone = self.make_network_interface() |
268 | + path = self.make_path() |
269 | + hostname = '[%s%%25%s]' % (ip, zone) |
270 | + self.assertEqual( |
271 | + 'http://%s/%s' % (hostname, path), |
272 | + compose_URL('http:///%s' % path, hostname)) |
273 | + |
274 | + def test__inserts_hostname(self): |
275 | + hostname = factory.make_name('host') |
276 | + path = self.make_path() |
277 | + self.assertEqual( |
278 | + 'http://%s/%s' % (hostname, path), |
279 | + compose_URL('http:///%s' % path, hostname)) |
280 | + |
281 | + def test__preserves_query(self): |
282 | + ip = factory.make_ipv4_address() |
283 | + key = factory.make_name('key') |
284 | + value = factory.make_name('value') |
285 | + self.assertEqual( |
286 | + 'https://%s?%s=%s' % (ip, key, value), |
287 | + compose_URL('https://?%s=%s' % (key, value), ip)) |
288 | + |
289 | + def test__preserves_port_with_IPv4(self): |
290 | + ip = factory.make_ipv4_address() |
291 | + port = factory.pick_port() |
292 | + self.assertEqual( |
293 | + 'https://%s:%s/' % (ip, port), |
294 | + compose_URL('https://:%s/' % port, ip)) |
295 | + |
296 | + def test__preserves_port_with_IPv6(self): |
297 | + ip = factory.make_ipv6_address() |
298 | + port = factory.pick_port() |
299 | + self.assertEqual( |
300 | + 'https://[%s]:%s/' % (ip, port), |
301 | + compose_URL('https://:%s/' % port, ip)) |
302 | + |
303 | + def test__preserves_port_with_hostname(self): |
304 | + hostname = factory.make_name('host') |
305 | + port = factory.pick_port() |
306 | + self.assertEqual( |
307 | + 'https://%s:%s/' % (hostname, port), |
308 | + compose_URL('https://:%s/' % port, hostname)) |
309 | |
310 | === modified file 'src/provisioningserver/utils/tests/test_utils.py' |
311 | --- src/provisioningserver/utils/tests/test_utils.py 2014-09-18 12:44:38 +0000 |
312 | +++ src/provisioningserver/utils/tests/test_utils.py 2014-11-07 13:40:43 +0000 |
313 | @@ -18,10 +18,7 @@ |
314 | from cStringIO import StringIO |
315 | import json |
316 | import os |
317 | -from random import ( |
318 | - choice, |
319 | - randint, |
320 | - ) |
321 | +from random import choice |
322 | from textwrap import dedent |
323 | |
324 | from fixtures import EnvironmentVariableFixture |
325 | @@ -44,7 +41,6 @@ |
326 | import provisioningserver.utils |
327 | from provisioningserver.utils import ( |
328 | classify, |
329 | - compose_URL, |
330 | create_node, |
331 | escape_py_literal, |
332 | filter_dict, |
333 | @@ -544,92 +540,6 @@ |
334 | "exists.", macs)) |
335 | |
336 | |
337 | -class TestComposeURL(MAASTestCase): |
338 | - |
339 | - def make_path(self): |
340 | - """Return an arbitrary URL path part.""" |
341 | - return '%s/%s' % (factory.make_name('root'), factory.make_name('sub')) |
342 | - |
343 | - def make_network_interface(self): |
344 | - return 'eth%d' % randint(0, 100) |
345 | - |
346 | - def test__inserts_IPv4(self): |
347 | - ip = factory.make_ipv4_address() |
348 | - path = self.make_path() |
349 | - self.assertEqual( |
350 | - 'http://%s/%s' % (ip, path), |
351 | - compose_URL('http:///%s' % path, ip)) |
352 | - |
353 | - def test__inserts_IPv6_with_brackets(self): |
354 | - ip = factory.make_ipv6_address() |
355 | - path = self.make_path() |
356 | - self.assertEqual( |
357 | - 'http://[%s]/%s' % (ip, path), |
358 | - compose_URL('http:///%s' % path, ip)) |
359 | - |
360 | - def test__escapes_IPv6_zone_index(self): |
361 | - ip = factory.make_ipv6_address() |
362 | - zone = self.make_network_interface() |
363 | - hostname = '%s%%%s' % (ip, zone) |
364 | - path = self.make_path() |
365 | - self.assertEqual( |
366 | - 'http://[%s%%25%s]/%s' % (ip, zone, path), |
367 | - compose_URL('http:///%s' % path, hostname)) |
368 | - |
369 | - def test__inserts_bracketed_IPv6_unchanged(self): |
370 | - ip = factory.make_ipv6_address() |
371 | - hostname = '[%s]' % ip |
372 | - path = self.make_path() |
373 | - self.assertEqual( |
374 | - 'http://%s/%s' % (hostname, path), |
375 | - compose_URL('http:///%s' % path, hostname)) |
376 | - |
377 | - def test__does_not_escape_bracketed_IPv6_zone_index(self): |
378 | - ip = factory.make_ipv6_address() |
379 | - zone = self.make_network_interface() |
380 | - path = self.make_path() |
381 | - hostname = '[%s%%25%s]' % (ip, zone) |
382 | - self.assertEqual( |
383 | - 'http://%s/%s' % (hostname, path), |
384 | - compose_URL('http:///%s' % path, hostname)) |
385 | - |
386 | - def test__inserts_hostname(self): |
387 | - hostname = factory.make_name('host') |
388 | - path = self.make_path() |
389 | - self.assertEqual( |
390 | - 'http://%s/%s' % (hostname, path), |
391 | - compose_URL('http:///%s' % path, hostname)) |
392 | - |
393 | - def test__preserves_query(self): |
394 | - ip = factory.make_ipv4_address() |
395 | - key = factory.make_name('key') |
396 | - value = factory.make_name('value') |
397 | - self.assertEqual( |
398 | - 'https://%s?%s=%s' % (ip, key, value), |
399 | - compose_URL('https://?%s=%s' % (key, value), ip)) |
400 | - |
401 | - def test__preserves_port_with_IPv4(self): |
402 | - ip = factory.make_ipv4_address() |
403 | - port = factory.pick_port() |
404 | - self.assertEqual( |
405 | - 'https://%s:%s/' % (ip, port), |
406 | - compose_URL('https://:%s/' % port, ip)) |
407 | - |
408 | - def test__preserves_port_with_IPv6(self): |
409 | - ip = factory.make_ipv6_address() |
410 | - port = factory.pick_port() |
411 | - self.assertEqual( |
412 | - 'https://[%s]:%s/' % (ip, port), |
413 | - compose_URL('https://:%s/' % port, ip)) |
414 | - |
415 | - def test__preserves_port_with_hostname(self): |
416 | - hostname = factory.make_name('host') |
417 | - port = factory.pick_port() |
418 | - self.assertEqual( |
419 | - 'https://%s:%s/' % (hostname, port), |
420 | - compose_URL('https://:%s/' % port, hostname)) |
421 | - |
422 | - |
423 | class TestGetClusterConfig(MAASTestCase): |
424 | scenarios = [ |
425 | ('Variable with quoted value', dict( |
426 | |
427 | === added file 'src/provisioningserver/utils/url.py' |
428 | --- src/provisioningserver/utils/url.py 1970-01-01 00:00:00 +0000 |
429 | +++ src/provisioningserver/utils/url.py 2014-11-07 13:40:43 +0000 |
430 | @@ -0,0 +1,51 @@ |
431 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
432 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
433 | + |
434 | +"""Utilities for URL handling.""" |
435 | + |
436 | +from __future__ import ( |
437 | + absolute_import, |
438 | + print_function, |
439 | + unicode_literals, |
440 | + ) |
441 | + |
442 | +str = None |
443 | + |
444 | +__metaclass__ = type |
445 | +__all__ = [ |
446 | + 'compose_URL', |
447 | + ] |
448 | + |
449 | +import re |
450 | +import urllib |
451 | +from urlparse import ( |
452 | + urlparse, |
453 | + urlunparse, |
454 | + ) |
455 | + |
456 | + |
457 | +def compose_URL(base_url, host): |
458 | + """Produce a URL on a given hostname or IP address. |
459 | + |
460 | + This is straightforward if the IP address is a hostname or an IPv4 |
461 | + address; but if it's an IPv6 address, the URL must contain the IP address |
462 | + in square brackets as per RFC 3986. |
463 | + |
464 | + :param base_url: URL without the host part, e.g. `http:///path'. |
465 | + :param host: Host name or IP address to insert in the host part of the URL. |
466 | + :return: A URL string with the host part taken from `host`, and all others |
467 | + from `base_url`. |
468 | + """ |
469 | + if re.match('[:.0-9a-fA-F]+(?:%.+)?$', host) and host.count(':') > 0: |
470 | + # IPv6 address, without the brackets. Add square brackets. |
471 | + # In case there's a zone index (introduced by a % sign), escape it. |
472 | + netloc_host = '[%s]' % urllib.quote(host, safe=':') |
473 | + else: |
474 | + # IPv4 address, hostname, or IPv6 with brackets. Keep as-is. |
475 | + netloc_host = host |
476 | + parsed_url = urlparse(base_url) |
477 | + if parsed_url.port is None: |
478 | + netloc = netloc_host |
479 | + else: |
480 | + netloc = '%s:%d' % (netloc_host, parsed_url.port) |
481 | + return urlunparse(parsed_url._replace(netloc=netloc)) |
Easy karma indeed.