Merge branch '5313-node-manager-gce-fixes-wip'
[arvados.git] / services / nodemanager / tests / test_computenode_driver_gce.py
1 #!/usr/bin/env python
2
3 from __future__ import absolute_import, print_function
4
5 import json
6 import time
7 import unittest
8
9 import mock
10
11 import arvnodeman.computenode.driver.gce as gce
12 from . import testutil
13
14 class GCEComputeNodeDriverTestCase(testutil.DriverTestMixin, unittest.TestCase):
15     TEST_CLASS = gce.ComputeNodeDriver
16
17     def test_driver_instantiation(self):
18         kwargs = {'user_id': 'foo'}
19         driver = self.new_driver(auth_kwargs=kwargs)
20         self.assertTrue(self.driver_mock.called)
21         self.assertEqual(kwargs, self.driver_mock.call_args[1])
22
23     def test_create_image_loaded_at_initialization_by_name(self):
24         image_mocks = [testutil.cloud_object_mock(c) for c in 'abc']
25         list_method = self.driver_mock().list_images
26         list_method.return_value = image_mocks
27         driver = self.new_driver(create_kwargs={'image': 'B'})
28         self.assertEqual(1, list_method.call_count)
29
30     def test_list_sizes_requires_location_match(self):
31         locations = [testutil.cloud_object_mock(name)
32                      for name in ['there', 'here', 'other']]
33         self.driver_mock().list_locations.return_value = locations
34         driver = self.new_driver(create_kwargs={'location': 'HERE'})
35         driver.list_sizes()
36         self.assertIs(locations[1],
37                       self.driver_mock().list_sizes.call_args[0][0])
38
39     def test_create_includes_ping_secret(self):
40         arv_node = testutil.arvados_node_mock(info={'ping_secret': 'ssshh'})
41         driver = self.new_driver()
42         driver.create_node(testutil.MockSize(1), arv_node)
43         metadata = self.driver_mock().create_node.call_args[1]['ex_metadata']
44         self.assertIn('ping_secret=ssshh', metadata.get('arv-ping-url'))
45
46     def test_create_sets_default_hostname(self):
47         driver = self.new_driver()
48         driver.create_node(testutil.MockSize(1),
49                            testutil.arvados_node_mock(254, hostname=None))
50         create_kwargs = self.driver_mock().create_node.call_args[1]
51         self.assertEqual('compute-0000000000000fe-zzzzz',
52                          create_kwargs.get('name'))
53         self.assertEqual('dynamic.compute.zzzzz.arvadosapi.com',
54                          create_kwargs.get('ex_metadata', {}).get('hostname'))
55
56     def test_create_tags_from_list_tags(self):
57         driver = self.new_driver(list_kwargs={'tags': 'testA, testB'})
58         driver.create_node(testutil.MockSize(1), testutil.arvados_node_mock())
59         self.assertEqual(['testA', 'testB'],
60                          self.driver_mock().create_node.call_args[1]['ex_tags'])
61
62     def test_list_nodes_requires_tags_match(self):
63         # A node matches if our list tags are a subset of the node's tags.
64         # Test behavior with no tags, no match, partial matches, different
65         # order, and strict supersets.
66         cloud_mocks = [
67             testutil.cloud_node_mock(node_num, tags=tag_set)
68             for node_num, tag_set in enumerate(
69                 [[], ['bad'], ['good'], ['great'], ['great', 'ok'],
70                  ['great', 'good'], ['good', 'fantastic', 'great']])]
71         cloud_mocks.append(testutil.cloud_node_mock())
72         self.driver_mock().list_nodes.return_value = cloud_mocks
73         driver = self.new_driver(list_kwargs={'tags': 'good, great'})
74         self.assertItemsEqual(['5', '6'], [n.id for n in driver.list_nodes()])
75
76     def test_destroy_node_destroys_disk(self):
77         driver = self.new_driver()
78         driver.destroy_node(testutil.cloud_node_mock())
79         self.assertTrue(self.driver_mock().destroy_node.call_args[1].get(
80                 'destroy_boot_disk'))
81
82     def build_gce_metadata(self, metadata_dict):
83         # Convert a plain metadata dictionary to the GCE data structure.
84         return {
85             'kind': 'compute#metadata',
86             'fingerprint': 'testprint',
87             'items': [{'key': key, 'value': metadata_dict[key]}
88                       for key in metadata_dict],
89             }
90
91     def check_sync_node_updates_hostname_tag(self, plain_metadata):
92         start_metadata = self.build_gce_metadata(plain_metadata)
93         arv_node = testutil.arvados_node_mock(1)
94         cloud_node = testutil.cloud_node_mock(
95             2, metadata=start_metadata.copy(),
96             zone=testutil.cloud_object_mock('testzone'))
97         driver = self.new_driver()
98         driver.sync_node(cloud_node, arv_node)
99         args, kwargs = self.driver_mock().connection.async_request.call_args
100         self.assertEqual('/zones/TESTZONE/instances/2/setMetadata', args[0])
101         for key in ['kind', 'fingerprint']:
102             self.assertEqual(start_metadata[key], kwargs['data'][key])
103         plain_metadata['hostname'] = 'compute1.zzzzz.arvadosapi.com'
104         self.assertEqual(
105             plain_metadata,
106             {item['key']: item['value'] for item in kwargs['data']['items']})
107
108     def test_sync_node_updates_hostname_tag(self):
109         self.check_sync_node_updates_hostname_tag(
110             {'testkey': 'testvalue', 'hostname': 'startvalue'})
111
112     def test_sync_node_adds_hostname_tag(self):
113         self.check_sync_node_updates_hostname_tag({'testkey': 'testval'})
114
115     def test_sync_node_raises_exception_on_failure(self):
116         arv_node = testutil.arvados_node_mock(8)
117         cloud_node = testutil.cloud_node_mock(
118             9, metadata={}, zone=testutil.cloud_object_mock('failzone'))
119         mock_response = self.driver_mock().connection.async_request()
120         mock_response.success.return_value = False
121         mock_response.error = 'sync error test'
122         driver = self.new_driver()
123         with self.assertRaises(Exception) as err_check:
124             driver.sync_node(cloud_node, arv_node)
125         self.assertIs(err_check.exception.__class__, Exception)
126         self.assertIn('sync error test', str(err_check.exception))
127
128     def test_node_create_time_zero_for_unknown_nodes(self):
129         node = testutil.cloud_node_mock()
130         self.assertEqual(0, gce.ComputeNodeDriver.node_start_time(node))
131
132     def test_node_create_time_for_known_node(self):
133         node = testutil.cloud_node_mock(metadata=self.build_gce_metadata(
134                 {'booted_at': '1970-01-01T00:01:05Z'}))
135         self.assertEqual(65, gce.ComputeNodeDriver.node_start_time(node))
136
137     def test_node_create_time_recorded_when_node_boots(self):
138         start_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
139         arv_node = testutil.arvados_node_mock()
140         driver = self.new_driver()
141         driver.create_node(testutil.MockSize(1), arv_node)
142         metadata = self.driver_mock().create_node.call_args[1]['ex_metadata']
143         self.assertLessEqual(start_time, metadata.get('booted_at'))
144
145     def test_deliver_ssh_key_in_metadata(self):
146         test_ssh_key = 'ssh-rsa-foo'
147         arv_node = testutil.arvados_node_mock(1)
148         with mock.patch('__builtin__.open',
149                         mock.mock_open(read_data=test_ssh_key)) as mock_file:
150             driver = self.new_driver(create_kwargs={'ssh_key': 'ssh-key-file'})
151         mock_file.assert_called_once_with('ssh-key-file')
152         driver.create_node(testutil.MockSize(1), arv_node)
153         metadata = self.driver_mock().create_node.call_args[1]['ex_metadata']
154         self.assertEqual('root:ssh-rsa-foo', metadata.get('sshKeys'))
155
156     def test_create_driver_with_service_accounts(self):
157         service_accounts = {'email': 'foo@bar', 'scopes': ['storage-full']}
158         srv_acct_config = {'service_accounts': json.dumps(service_accounts)}
159         arv_node = testutil.arvados_node_mock(1)
160         driver = self.new_driver(create_kwargs=srv_acct_config)
161         driver.create_node(testutil.MockSize(1), arv_node)
162         self.assertEqual(
163             service_accounts,
164             self.driver_mock().create_node.call_args[1]['ex_service_accounts'])