Merge lp:~daniel-thewatkins/cloud-init/lp1464253 into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Dan Watkins on 2015-06-12
Status: Merged
Merged at revision: 1118
Proposed branch: lp:~daniel-thewatkins/cloud-init/lp1464253
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 118 lines (+25/-40)
2 files modified
cloudinit/sources/DataSourceCloudStack.py (+10/-25)
tests/unittests/test_datasource/test_cloudstack.py (+15/-15)
To merge this branch: bzr merge lp:~daniel-thewatkins/cloud-init/lp1464253
Reviewer Review Type Date Requested Status
cloud-init commiters 2015-06-12 Pending
Review via email: mp+261849@code.launchpad.net
To post a comment you must log in.
Scott Moser (smoser) wrote :

Changes are fine, but you have to fix the tests also.
currently failing.

1115. By Dan Watkins on 2015-06-16

Use wget to fetch CloudStack passwords.

Different versions of the CloudStack password server respond
differently; wget handles these nicely for us, so it's easier to just
use wget.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/sources/DataSourceCloudStack.py'
2--- cloudinit/sources/DataSourceCloudStack.py 2015-02-23 09:36:36 +0000
3+++ cloudinit/sources/DataSourceCloudStack.py 2015-06-16 16:35:32 +0000
4@@ -29,8 +29,6 @@
5 from socket import inet_ntoa
6 from struct import pack
7
8-from six.moves import http_client
9-
10 from cloudinit import ec2_utils as ec2
11 from cloudinit import log as logging
12 from cloudinit import url_helper as uhelp
13@@ -47,35 +45,22 @@
14 has documentation about the system. This implementation is following that
15 found at
16 https://github.com/shankerbalan/cloudstack-scripts/blob/master/cloud-set-guest-password-debian
17-
18- The CloudStack password server is, essentially, a broken HTTP
19- server. It requires us to provide a valid HTTP request (including a
20- DomU_Request header, which is the meat of the request), but just
21- writes the text of its response on to the socket, without a status
22- line or any HTTP headers. This makes HTTP libraries sad, which
23- explains the screwiness of the implementation of this class.
24-
25- This should be fixed in CloudStack by commit
26- a72f14ea9cb832faaac946b3cf9f56856b50142a in December 2014.
27 """
28
29 def __init__(self, virtual_router_address):
30 self.virtual_router_address = virtual_router_address
31
32 def _do_request(self, domu_request):
33- # We have to provide a valid HTTP request, but a valid HTTP
34- # response is not returned. This means that getresponse() chokes,
35- # so we use the socket directly to read off the response.
36- # Because we're reading off the socket directly, we can't re-use the
37- # connection.
38- conn = http_client.HTTPConnection(self.virtual_router_address, 8080)
39- try:
40- conn.request('GET', '', headers={'DomU_Request': domu_request})
41- conn.sock.settimeout(30)
42- output = conn.sock.recv(1024).decode('utf-8').strip()
43- finally:
44- conn.close()
45- return output
46+ # The password server was in the past, a broken HTTP server, but is now
47+ # fixed. wget handles this seamlessly, so it's easier to shell out to
48+ # that rather than write our own handling code.
49+ output, _ = util.subp([
50+ 'wget', '--quiet', '--tries', '3', '--timeout', '20',
51+ '--output-document', '-', '--header',
52+ 'DomU_Request: {0}'.format(domu_request),
53+ '{0}:8080'.format(self.virtual_router_address)
54+ ])
55+ return output.strip()
56
57 def get_password(self):
58 password = self._do_request('send_my_password')
59
60=== modified file 'tests/unittests/test_datasource/test_cloudstack.py'
61--- tests/unittests/test_datasource/test_cloudstack.py 2015-02-20 10:30:36 +0000
62+++ tests/unittests/test_datasource/test_cloudstack.py 2015-06-16 16:35:32 +0000
63@@ -23,13 +23,11 @@
64 self.patches.enter_context(mock.patch('{0}.uhelp'.format(mod_name)))
65
66 def _set_password_server_response(self, response_string):
67- http_client = mock.MagicMock()
68- http_client.HTTPConnection.return_value.sock.recv.return_value = \
69- response_string.encode('utf-8')
70+ subp = mock.MagicMock(return_value=(response_string, ''))
71 self.patches.enter_context(
72- mock.patch('cloudinit.sources.DataSourceCloudStack.http_client',
73- http_client))
74- return http_client
75+ mock.patch('cloudinit.sources.DataSourceCloudStack.util.subp',
76+ subp))
77+ return subp
78
79 def test_empty_password_doesnt_create_config(self):
80 self._set_password_server_response('')
81@@ -55,26 +53,28 @@
82 ds = DataSourceCloudStack({}, None, helpers.Paths({}))
83 self.assertTrue(ds.get_data())
84
85- def assertRequestTypesSent(self, http_client, expected_request_types):
86- request_types = [
87- kwargs['headers']['DomU_Request']
88- for _, kwargs
89- in http_client.HTTPConnection.return_value.request.call_args_list]
90+ def assertRequestTypesSent(self, subp, expected_request_types):
91+ request_types = []
92+ for call in subp.call_args_list:
93+ args = call[0][0]
94+ for arg in args:
95+ if arg.startswith('DomU_Request'):
96+ request_types.append(arg.split()[1])
97 self.assertEqual(expected_request_types, request_types)
98
99 def test_valid_response_means_password_marked_as_saved(self):
100 password = 'SekritSquirrel'
101- http_client = self._set_password_server_response(password)
102+ subp = self._set_password_server_response(password)
103 ds = DataSourceCloudStack({}, None, helpers.Paths({}))
104 ds.get_data()
105- self.assertRequestTypesSent(http_client,
106+ self.assertRequestTypesSent(subp,
107 ['send_my_password', 'saved_password'])
108
109 def _check_password_not_saved_for(self, response_string):
110- http_client = self._set_password_server_response(response_string)
111+ subp = self._set_password_server_response(response_string)
112 ds = DataSourceCloudStack({}, None, helpers.Paths({}))
113 ds.get_data()
114- self.assertRequestTypesSent(http_client, ['send_my_password'])
115+ self.assertRequestTypesSent(subp, ['send_my_password'])
116
117 def test_password_not_saved_if_empty(self):
118 self._check_password_not_saved_for('')