~oddbloke/cloud-init/fix-gce-az

« back to all changes in this revision

Viewing changes to tests/unittests/test_datasource/test_azure_helper.py

  • Committer: Daniel Watkins
  • Date: 2015-07-22 12:06:34 UTC
  • Revision ID: daniel.watkins@canonical.com-20150722120634-wsg8rwzcaanhc2pn
Make full data source available to code that handles mirror selection.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
import struct
 
3
import unittest
 
4
 
 
5
from cloudinit.sources.helpers import azure as azure_helper
 
6
from ..helpers import TestCase
 
7
 
 
8
try:
 
9
    from unittest import mock
 
10
except ImportError:
 
11
    import mock
 
12
 
 
13
try:
 
14
    from contextlib import ExitStack
 
15
except ImportError:
 
16
    from contextlib2 import ExitStack
 
17
 
 
18
 
 
19
GOAL_STATE_TEMPLATE = """\
 
20
<?xml version="1.0" encoding="utf-8"?>
 
21
<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 
22
 xsi:noNamespaceSchemaLocation="goalstate10.xsd">
 
23
  <Version>2012-11-30</Version>
 
24
  <Incarnation>{incarnation}</Incarnation>
 
25
  <Machine>
 
26
    <ExpectedState>Started</ExpectedState>
 
27
    <StopRolesDeadlineHint>300000</StopRolesDeadlineHint>
 
28
    <LBProbePorts>
 
29
      <Port>16001</Port>
 
30
    </LBProbePorts>
 
31
    <ExpectHealthReport>FALSE</ExpectHealthReport>
 
32
  </Machine>
 
33
  <Container>
 
34
    <ContainerId>{container_id}</ContainerId>
 
35
    <RoleInstanceList>
 
36
      <RoleInstance>
 
37
        <InstanceId>{instance_id}</InstanceId>
 
38
        <State>Started</State>
 
39
        <Configuration>
 
40
          <HostingEnvironmentConfig>
 
41
            http://100.86.192.70:80/...hostingEnvironmentConfig...
 
42
          </HostingEnvironmentConfig>
 
43
          <SharedConfig>{shared_config_url}</SharedConfig>
 
44
          <ExtensionsConfig>
 
45
            http://100.86.192.70:80/...extensionsConfig...
 
46
          </ExtensionsConfig>
 
47
          <FullConfig>http://100.86.192.70:80/...fullConfig...</FullConfig>
 
48
          <Certificates>{certificates_url}</Certificates>
 
49
          <ConfigName>68ce47.0.68ce47.0.utl-trusty--292258.1.xml</ConfigName>
 
50
        </Configuration>
 
51
      </RoleInstance>
 
52
    </RoleInstanceList>
 
53
  </Container>
 
54
</GoalState>
 
55
"""
 
56
 
 
57
 
 
58
class TestReadAzureSharedConfig(unittest.TestCase):
 
59
 
 
60
    def test_valid_content(self):
 
61
        xml = """<?xml version="1.0" encoding="utf-8"?>
 
62
            <SharedConfig>
 
63
             <Deployment name="MY_INSTANCE_ID">
 
64
              <Service name="myservice"/>
 
65
              <ServiceInstance name="INSTANCE_ID.0" guid="{abcd-uuid}" />
 
66
             </Deployment>
 
67
            <Incarnation number="1"/>
 
68
            </SharedConfig>"""
 
69
        ret = azure_helper.iid_from_shared_config_content(xml)
 
70
        self.assertEqual("MY_INSTANCE_ID", ret)
 
71
 
 
72
 
 
73
class TestFindEndpoint(TestCase):
 
74
 
 
75
    def setUp(self):
 
76
        super(TestFindEndpoint, self).setUp()
 
77
        patches = ExitStack()
 
78
        self.addCleanup(patches.close)
 
79
 
 
80
        self.load_file = patches.enter_context(
 
81
            mock.patch.object(azure_helper.util, 'load_file'))
 
82
 
 
83
    def test_missing_file(self):
 
84
        self.load_file.side_effect = IOError
 
85
        self.assertRaises(IOError,
 
86
                          azure_helper.WALinuxAgentShim.find_endpoint)
 
87
 
 
88
    def test_missing_special_azure_line(self):
 
89
        self.load_file.return_value = ''
 
90
        self.assertRaises(Exception,
 
91
                          azure_helper.WALinuxAgentShim.find_endpoint)
 
92
 
 
93
    def _build_lease_content(self, ip_address, use_hex=True):
 
94
        ip_address_repr = ':'.join(
 
95
            [hex(int(part)).replace('0x', '')
 
96
             for part in ip_address.split('.')])
 
97
        if not use_hex:
 
98
            ip_address_repr = struct.pack(
 
99
                '>L', int(ip_address_repr.replace(':', ''), 16))
 
100
            ip_address_repr = '"{0}"'.format(ip_address_repr.decode('utf-8'))
 
101
        return '\n'.join([
 
102
            'lease {',
 
103
            ' interface "eth0";',
 
104
            ' option unknown-245 {0};'.format(ip_address_repr),
 
105
            '}'])
 
106
 
 
107
    def test_hex_string(self):
 
108
        ip_address = '98.76.54.32'
 
109
        file_content = self._build_lease_content(ip_address)
 
110
        self.load_file.return_value = file_content
 
111
        self.assertEqual(ip_address,
 
112
                         azure_helper.WALinuxAgentShim.find_endpoint())
 
113
 
 
114
    def test_hex_string_with_single_character_part(self):
 
115
        ip_address = '4.3.2.1'
 
116
        file_content = self._build_lease_content(ip_address)
 
117
        self.load_file.return_value = file_content
 
118
        self.assertEqual(ip_address,
 
119
                         azure_helper.WALinuxAgentShim.find_endpoint())
 
120
 
 
121
    def test_packed_string(self):
 
122
        ip_address = '98.76.54.32'
 
123
        file_content = self._build_lease_content(ip_address, use_hex=False)
 
124
        self.load_file.return_value = file_content
 
125
        self.assertEqual(ip_address,
 
126
                         azure_helper.WALinuxAgentShim.find_endpoint())
 
127
 
 
128
    def test_latest_lease_used(self):
 
129
        ip_addresses = ['4.3.2.1', '98.76.54.32']
 
130
        file_content = '\n'.join([self._build_lease_content(ip_address)
 
131
                                  for ip_address in ip_addresses])
 
132
        self.load_file.return_value = file_content
 
133
        self.assertEqual(ip_addresses[-1],
 
134
                         azure_helper.WALinuxAgentShim.find_endpoint())
 
135
 
 
136
 
 
137
class TestGoalStateParsing(TestCase):
 
138
 
 
139
    default_parameters = {
 
140
        'incarnation': 1,
 
141
        'container_id': 'MyContainerId',
 
142
        'instance_id': 'MyInstanceId',
 
143
        'shared_config_url': 'MySharedConfigUrl',
 
144
        'certificates_url': 'MyCertificatesUrl',
 
145
    }
 
146
 
 
147
    def _get_goal_state(self, http_client=None, **kwargs):
 
148
        if http_client is None:
 
149
            http_client = mock.MagicMock()
 
150
        parameters = self.default_parameters.copy()
 
151
        parameters.update(kwargs)
 
152
        xml = GOAL_STATE_TEMPLATE.format(**parameters)
 
153
        if parameters['certificates_url'] is None:
 
154
            new_xml_lines = []
 
155
            for line in xml.splitlines():
 
156
                if 'Certificates' in line:
 
157
                    continue
 
158
                new_xml_lines.append(line)
 
159
            xml = '\n'.join(new_xml_lines)
 
160
        return azure_helper.GoalState(xml, http_client)
 
161
 
 
162
    def test_incarnation_parsed_correctly(self):
 
163
        incarnation = '123'
 
164
        goal_state = self._get_goal_state(incarnation=incarnation)
 
165
        self.assertEqual(incarnation, goal_state.incarnation)
 
166
 
 
167
    def test_container_id_parsed_correctly(self):
 
168
        container_id = 'TestContainerId'
 
169
        goal_state = self._get_goal_state(container_id=container_id)
 
170
        self.assertEqual(container_id, goal_state.container_id)
 
171
 
 
172
    def test_instance_id_parsed_correctly(self):
 
173
        instance_id = 'TestInstanceId'
 
174
        goal_state = self._get_goal_state(instance_id=instance_id)
 
175
        self.assertEqual(instance_id, goal_state.instance_id)
 
176
 
 
177
    def test_shared_config_xml_parsed_and_fetched_correctly(self):
 
178
        http_client = mock.MagicMock()
 
179
        shared_config_url = 'TestSharedConfigUrl'
 
180
        goal_state = self._get_goal_state(
 
181
            http_client=http_client, shared_config_url=shared_config_url)
 
182
        shared_config_xml = goal_state.shared_config_xml
 
183
        self.assertEqual(1, http_client.get.call_count)
 
184
        self.assertEqual(shared_config_url, http_client.get.call_args[0][0])
 
185
        self.assertEqual(http_client.get.return_value.contents,
 
186
                         shared_config_xml)
 
187
 
 
188
    def test_certificates_xml_parsed_and_fetched_correctly(self):
 
189
        http_client = mock.MagicMock()
 
190
        certificates_url = 'TestSharedConfigUrl'
 
191
        goal_state = self._get_goal_state(
 
192
            http_client=http_client, certificates_url=certificates_url)
 
193
        certificates_xml = goal_state.certificates_xml
 
194
        self.assertEqual(1, http_client.get.call_count)
 
195
        self.assertEqual(certificates_url, http_client.get.call_args[0][0])
 
196
        self.assertTrue(http_client.get.call_args[1].get('secure', False))
 
197
        self.assertEqual(http_client.get.return_value.contents,
 
198
                         certificates_xml)
 
199
 
 
200
    def test_missing_certificates_skips_http_get(self):
 
201
        http_client = mock.MagicMock()
 
202
        goal_state = self._get_goal_state(
 
203
            http_client=http_client, certificates_url=None)
 
204
        certificates_xml = goal_state.certificates_xml
 
205
        self.assertEqual(0, http_client.get.call_count)
 
206
        self.assertIsNone(certificates_xml)
 
207
 
 
208
 
 
209
class TestAzureEndpointHttpClient(TestCase):
 
210
 
 
211
    regular_headers = {
 
212
        'x-ms-agent-name': 'WALinuxAgent',
 
213
        'x-ms-version': '2012-11-30',
 
214
    }
 
215
 
 
216
    def setUp(self):
 
217
        super(TestAzureEndpointHttpClient, self).setUp()
 
218
        patches = ExitStack()
 
219
        self.addCleanup(patches.close)
 
220
 
 
221
        self.read_file_or_url = patches.enter_context(
 
222
            mock.patch.object(azure_helper.util, 'read_file_or_url'))
 
223
 
 
224
    def test_non_secure_get(self):
 
225
        client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
 
226
        url = 'MyTestUrl'
 
227
        response = client.get(url, secure=False)
 
228
        self.assertEqual(1, self.read_file_or_url.call_count)
 
229
        self.assertEqual(self.read_file_or_url.return_value, response)
 
230
        self.assertEqual(mock.call(url, headers=self.regular_headers),
 
231
                         self.read_file_or_url.call_args)
 
232
 
 
233
    def test_secure_get(self):
 
234
        url = 'MyTestUrl'
 
235
        certificate = mock.MagicMock()
 
236
        expected_headers = self.regular_headers.copy()
 
237
        expected_headers.update({
 
238
            "x-ms-cipher-name": "DES_EDE3_CBC",
 
239
            "x-ms-guest-agent-public-x509-cert": certificate,
 
240
        })
 
241
        client = azure_helper.AzureEndpointHttpClient(certificate)
 
242
        response = client.get(url, secure=True)
 
243
        self.assertEqual(1, self.read_file_or_url.call_count)
 
244
        self.assertEqual(self.read_file_or_url.return_value, response)
 
245
        self.assertEqual(mock.call(url, headers=expected_headers),
 
246
                         self.read_file_or_url.call_args)
 
247
 
 
248
    def test_post(self):
 
249
        data = mock.MagicMock()
 
250
        url = 'MyTestUrl'
 
251
        client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
 
252
        response = client.post(url, data=data)
 
253
        self.assertEqual(1, self.read_file_or_url.call_count)
 
254
        self.assertEqual(self.read_file_or_url.return_value, response)
 
255
        self.assertEqual(
 
256
            mock.call(url, data=data, headers=self.regular_headers),
 
257
            self.read_file_or_url.call_args)
 
258
 
 
259
    def test_post_with_extra_headers(self):
 
260
        url = 'MyTestUrl'
 
261
        client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
 
262
        extra_headers = {'test': 'header'}
 
263
        client.post(url, extra_headers=extra_headers)
 
264
        self.assertEqual(1, self.read_file_or_url.call_count)
 
265
        expected_headers = self.regular_headers.copy()
 
266
        expected_headers.update(extra_headers)
 
267
        self.assertEqual(
 
268
            mock.call(mock.ANY, data=mock.ANY, headers=expected_headers),
 
269
            self.read_file_or_url.call_args)
 
270
 
 
271
 
 
272
class TestOpenSSLManager(TestCase):
 
273
 
 
274
    def setUp(self):
 
275
        super(TestOpenSSLManager, self).setUp()
 
276
        patches = ExitStack()
 
277
        self.addCleanup(patches.close)
 
278
 
 
279
        self.subp = patches.enter_context(
 
280
            mock.patch.object(azure_helper.util, 'subp'))
 
281
        try:
 
282
            self.open = patches.enter_context(
 
283
                mock.patch('__builtin__.open'))
 
284
        except ImportError:
 
285
            self.open = patches.enter_context(
 
286
                mock.patch('builtins.open'))
 
287
 
 
288
    @mock.patch.object(azure_helper, 'cd', mock.MagicMock())
 
289
    @mock.patch.object(azure_helper.tempfile, 'mkdtemp')
 
290
    def test_openssl_manager_creates_a_tmpdir(self, mkdtemp):
 
291
        manager = azure_helper.OpenSSLManager()
 
292
        self.assertEqual(mkdtemp.return_value, manager.tmpdir)
 
293
 
 
294
    def test_generate_certificate_uses_tmpdir(self):
 
295
        subp_directory = {}
 
296
 
 
297
        def capture_directory(*args, **kwargs):
 
298
            subp_directory['path'] = os.getcwd()
 
299
 
 
300
        self.subp.side_effect = capture_directory
 
301
        manager = azure_helper.OpenSSLManager()
 
302
        self.assertEqual(manager.tmpdir, subp_directory['path'])
 
303
 
 
304
    @mock.patch.object(azure_helper, 'cd', mock.MagicMock())
 
305
    @mock.patch.object(azure_helper.tempfile, 'mkdtemp', mock.MagicMock())
 
306
    @mock.patch.object(azure_helper.util, 'del_dir')
 
307
    def test_clean_up(self, del_dir):
 
308
        manager = azure_helper.OpenSSLManager()
 
309
        manager.clean_up()
 
310
        self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list)
 
311
 
 
312
 
 
313
class TestWALinuxAgentShim(TestCase):
 
314
 
 
315
    def setUp(self):
 
316
        super(TestWALinuxAgentShim, self).setUp()
 
317
        patches = ExitStack()
 
318
        self.addCleanup(patches.close)
 
319
 
 
320
        self.AzureEndpointHttpClient = patches.enter_context(
 
321
            mock.patch.object(azure_helper, 'AzureEndpointHttpClient'))
 
322
        self.find_endpoint = patches.enter_context(
 
323
            mock.patch.object(
 
324
                azure_helper.WALinuxAgentShim, 'find_endpoint'))
 
325
        self.GoalState = patches.enter_context(
 
326
            mock.patch.object(azure_helper, 'GoalState'))
 
327
        self.iid_from_shared_config_content = patches.enter_context(
 
328
            mock.patch.object(azure_helper, 'iid_from_shared_config_content'))
 
329
        self.OpenSSLManager = patches.enter_context(
 
330
            mock.patch.object(azure_helper, 'OpenSSLManager'))
 
331
        patches.enter_context(
 
332
            mock.patch.object(azure_helper.time, 'sleep', mock.MagicMock()))
 
333
 
 
334
    def test_http_client_uses_certificate(self):
 
335
        shim = azure_helper.WALinuxAgentShim()
 
336
        shim.register_with_azure_and_fetch_data()
 
337
        self.assertEqual(
 
338
            [mock.call(self.OpenSSLManager.return_value.certificate)],
 
339
            self.AzureEndpointHttpClient.call_args_list)
 
340
 
 
341
    def test_correct_url_used_for_goalstate(self):
 
342
        self.find_endpoint.return_value = 'test_endpoint'
 
343
        shim = azure_helper.WALinuxAgentShim()
 
344
        shim.register_with_azure_and_fetch_data()
 
345
        get = self.AzureEndpointHttpClient.return_value.get
 
346
        self.assertEqual(
 
347
            [mock.call('http://test_endpoint/machine/?comp=goalstate')],
 
348
            get.call_args_list)
 
349
        self.assertEqual(
 
350
            [mock.call(get.return_value.contents,
 
351
                       self.AzureEndpointHttpClient.return_value)],
 
352
            self.GoalState.call_args_list)
 
353
 
 
354
    def test_certificates_used_to_determine_public_keys(self):
 
355
        shim = azure_helper.WALinuxAgentShim()
 
356
        data = shim.register_with_azure_and_fetch_data()
 
357
        self.assertEqual(
 
358
            [mock.call(self.GoalState.return_value.certificates_xml)],
 
359
            self.OpenSSLManager.return_value.parse_certificates.call_args_list)
 
360
        self.assertEqual(
 
361
            self.OpenSSLManager.return_value.parse_certificates.return_value,
 
362
            data['public-keys'])
 
363
 
 
364
    def test_absent_certificates_produces_empty_public_keys(self):
 
365
        self.GoalState.return_value.certificates_xml = None
 
366
        shim = azure_helper.WALinuxAgentShim()
 
367
        data = shim.register_with_azure_and_fetch_data()
 
368
        self.assertEqual([], data['public-keys'])
 
369
 
 
370
    def test_instance_id_returned_in_data(self):
 
371
        shim = azure_helper.WALinuxAgentShim()
 
372
        data = shim.register_with_azure_and_fetch_data()
 
373
        self.assertEqual(
 
374
            [mock.call(self.GoalState.return_value.shared_config_xml)],
 
375
            self.iid_from_shared_config_content.call_args_list)
 
376
        self.assertEqual(self.iid_from_shared_config_content.return_value,
 
377
                         data['instance-id'])
 
378
 
 
379
    def test_correct_url_used_for_report_ready(self):
 
380
        self.find_endpoint.return_value = 'test_endpoint'
 
381
        shim = azure_helper.WALinuxAgentShim()
 
382
        shim.register_with_azure_and_fetch_data()
 
383
        expected_url = 'http://test_endpoint/machine?comp=health'
 
384
        self.assertEqual(
 
385
            [mock.call(expected_url, data=mock.ANY, extra_headers=mock.ANY)],
 
386
            self.AzureEndpointHttpClient.return_value.post.call_args_list)
 
387
 
 
388
    def test_goal_state_values_used_for_report_ready(self):
 
389
        self.GoalState.return_value.incarnation = 'TestIncarnation'
 
390
        self.GoalState.return_value.container_id = 'TestContainerId'
 
391
        self.GoalState.return_value.instance_id = 'TestInstanceId'
 
392
        shim = azure_helper.WALinuxAgentShim()
 
393
        shim.register_with_azure_and_fetch_data()
 
394
        posted_document = (
 
395
            self.AzureEndpointHttpClient.return_value.post.call_args[1]['data']
 
396
        )
 
397
        self.assertIn('TestIncarnation', posted_document)
 
398
        self.assertIn('TestContainerId', posted_document)
 
399
        self.assertIn('TestInstanceId', posted_document)
 
400
 
 
401
    def test_clean_up_can_be_called_at_any_time(self):
 
402
        shim = azure_helper.WALinuxAgentShim()
 
403
        shim.clean_up()
 
404
 
 
405
    def test_clean_up_will_clean_up_openssl_manager_if_instantiated(self):
 
406
        shim = azure_helper.WALinuxAgentShim()
 
407
        shim.register_with_azure_and_fetch_data()
 
408
        shim.clean_up()
 
409
        self.assertEqual(
 
410
            1, self.OpenSSLManager.return_value.clean_up.call_count)
 
411
 
 
412
    def test_failure_to_fetch_goalstate_bubbles_up(self):
 
413
        class SentinelException(Exception):
 
414
            pass
 
415
        self.AzureEndpointHttpClient.return_value.get.side_effect = (
 
416
            SentinelException)
 
417
        shim = azure_helper.WALinuxAgentShim()
 
418
        self.assertRaises(SentinelException,
 
419
                          shim.register_with_azure_and_fetch_data)
 
420
 
 
421
 
 
422
class TestGetMetadataFromFabric(TestCase):
 
423
 
 
424
    @mock.patch.object(azure_helper, 'WALinuxAgentShim')
 
425
    def test_data_from_shim_returned(self, shim):
 
426
        ret = azure_helper.get_metadata_from_fabric()
 
427
        self.assertEqual(
 
428
            shim.return_value.register_with_azure_and_fetch_data.return_value,
 
429
            ret)
 
430
 
 
431
    @mock.patch.object(azure_helper, 'WALinuxAgentShim')
 
432
    def test_success_calls_clean_up(self, shim):
 
433
        azure_helper.get_metadata_from_fabric()
 
434
        self.assertEqual(1, shim.return_value.clean_up.call_count)
 
435
 
 
436
    @mock.patch.object(azure_helper, 'WALinuxAgentShim')
 
437
    def test_failure_in_registration_calls_clean_up(self, shim):
 
438
        class SentinelException(Exception):
 
439
            pass
 
440
        shim.return_value.register_with_azure_and_fetch_data.side_effect = (
 
441
            SentinelException)
 
442
        self.assertRaises(SentinelException,
 
443
                          azure_helper.get_metadata_from_fabric)
 
444
        self.assertEqual(1, shim.return_value.clean_up.call_count)