3 from __future__ import absolute_import, print_function
8 import arvados.errors as arverror
13 import arvnodeman.computenode as cnode
14 from . import testutil
16 class ComputeNodeSetupActorTestCase(testutil.ActorTestMixin, unittest.TestCase):
17 def make_mocks(self, arvados_effect=None, cloud_effect=None):
18 if arvados_effect is None:
19 arvados_effect = [testutil.arvados_node_mock()]
20 self.arvados_effect = arvados_effect
21 self.timer = testutil.MockTimer()
22 self.api_client = mock.MagicMock(name='api_client')
23 self.api_client.nodes().create().execute.side_effect = arvados_effect
24 self.api_client.nodes().update().execute.side_effect = arvados_effect
25 self.cloud_client = mock.MagicMock(name='cloud_client')
26 self.cloud_client.create_node.return_value = testutil.cloud_node_mock(1)
28 def make_actor(self, arv_node=None):
29 if not hasattr(self, 'timer'):
30 self.make_mocks(arvados_effect=[arv_node])
31 self.setup_actor = cnode.ComputeNodeSetupActor.start(
32 self.timer, self.api_client, self.cloud_client,
33 testutil.MockSize(1), arv_node).proxy()
35 def test_creation_without_arvados_node(self):
37 self.assertEqual(self.arvados_effect[-1],
38 self.setup_actor.arvados_node.get(self.TIMEOUT))
39 self.assertTrue(self.api_client.nodes().create().execute.called)
40 self.assertEqual(self.cloud_client.create_node(),
41 self.setup_actor.cloud_node.get(self.TIMEOUT))
43 def test_creation_with_arvados_node(self):
44 self.make_actor(testutil.arvados_node_mock())
45 self.assertEqual(self.arvados_effect[-1],
46 self.setup_actor.arvados_node.get(self.TIMEOUT))
47 self.assertTrue(self.api_client.nodes().update().execute.called)
48 self.assertEqual(self.cloud_client.create_node(),
49 self.setup_actor.cloud_node.get(self.TIMEOUT))
51 def test_failed_calls_retried(self):
53 arverror.ApiError(httplib2.Response({'status': '500'}), ""),
54 testutil.arvados_node_mock(),
57 self.wait_for_assignment(self.setup_actor, 'cloud_node')
59 def test_stop_when_no_cloud_node(self):
61 arverror.ApiError(httplib2.Response({'status': '500'}), ""))
63 self.setup_actor.stop_if_no_cloud_node()
65 self.setup_actor.actor_ref.actor_stopped.wait(self.TIMEOUT))
67 def test_no_stop_when_cloud_node(self):
69 self.wait_for_assignment(self.setup_actor, 'cloud_node')
70 self.setup_actor.stop_if_no_cloud_node().get(self.TIMEOUT)
71 self.assertTrue(self.stop_proxy(self.setup_actor),
72 "actor was stopped by stop_if_no_cloud_node")
74 def test_subscribe(self):
76 arverror.ApiError(httplib2.Response({'status': '500'}), ""))
78 subscriber = mock.Mock(name='subscriber_mock')
79 self.setup_actor.subscribe(subscriber)
80 self.api_client.nodes().create().execute.side_effect = [
81 testutil.arvados_node_mock()]
82 self.wait_for_assignment(self.setup_actor, 'cloud_node')
83 self.assertEqual(self.setup_actor.actor_ref.actor_urn,
84 subscriber.call_args[0][0].actor_ref.actor_urn)
86 def test_late_subscribe(self):
88 subscriber = mock.Mock(name='subscriber_mock')
89 self.wait_for_assignment(self.setup_actor, 'cloud_node')
90 self.setup_actor.subscribe(subscriber).get(self.TIMEOUT)
91 self.stop_proxy(self.setup_actor)
92 self.assertEqual(self.setup_actor.actor_ref.actor_urn,
93 subscriber.call_args[0][0].actor_ref.actor_urn)
96 class ComputeNodeShutdownActorTestCase(testutil.ActorTestMixin,
98 def make_mocks(self, cloud_node=None):
99 self.timer = testutil.MockTimer()
100 self.cloud_client = mock.MagicMock(name='cloud_client')
101 if cloud_node is None:
102 cloud_node = testutil.cloud_node_mock()
103 self.cloud_node = cloud_node
105 def make_actor(self, arv_node=None):
106 if not hasattr(self, 'timer'):
108 self.shutdown_actor = cnode.ComputeNodeShutdownActor.start(
109 self.timer, self.cloud_client, self.cloud_node).proxy()
111 def test_easy_shutdown(self):
113 self.shutdown_actor.cloud_node.get(self.TIMEOUT)
114 self.stop_proxy(self.shutdown_actor)
115 self.assertTrue(self.cloud_client.destroy_node.called)
118 class ComputeNodeUpdateActorTestCase(testutil.ActorTestMixin,
120 def make_actor(self):
121 self.driver = mock.MagicMock(name='driver_mock')
122 self.updater = cnode.ComputeNodeUpdateActor.start(self.driver).proxy()
124 def test_node_sync(self):
126 cloud_node = testutil.cloud_node_mock()
127 arv_node = testutil.arvados_node_mock()
128 self.updater.sync_node(cloud_node, arv_node).get(self.TIMEOUT)
129 self.driver().sync_node.assert_called_with(cloud_node, arv_node)
132 @mock.patch('time.time', return_value=1)
133 class ShutdownTimerTestCase(unittest.TestCase):
134 def test_two_length_window(self, time_mock):
135 timer = cnode.ShutdownTimer(time_mock.return_value, [8, 2])
136 self.assertEqual(481, timer.next_opening())
137 self.assertFalse(timer.window_open())
138 time_mock.return_value += 500
139 self.assertEqual(1081, timer.next_opening())
140 self.assertTrue(timer.window_open())
141 time_mock.return_value += 200
142 self.assertEqual(1081, timer.next_opening())
143 self.assertFalse(timer.window_open())
145 def test_three_length_window(self, time_mock):
146 timer = cnode.ShutdownTimer(time_mock.return_value, [6, 3, 1])
147 self.assertEqual(361, timer.next_opening())
148 self.assertFalse(timer.window_open())
149 time_mock.return_value += 400
150 self.assertEqual(961, timer.next_opening())
151 self.assertTrue(timer.window_open())
152 time_mock.return_value += 200
153 self.assertEqual(961, timer.next_opening())
154 self.assertFalse(timer.window_open())
157 class ComputeNodeMonitorActorTestCase(testutil.ActorTestMixin,
159 class MockShutdownTimer(object):
160 def _set_state(self, is_open, next_opening):
161 self.window_open = lambda: is_open
162 self.next_opening = lambda: next_opening
165 def make_mocks(self, node_num):
166 self.shutdowns = self.MockShutdownTimer()
167 self.shutdowns._set_state(False, 300)
168 self.timer = mock.MagicMock(name='timer_mock')
169 self.updates = mock.MagicMock(name='update_mock')
170 self.cloud_mock = testutil.cloud_node_mock(node_num)
171 self.subscriber = mock.Mock(name='subscriber_mock')
173 def make_actor(self, node_num=1, arv_node=None, start_time=None):
174 if not hasattr(self, 'cloud_mock'):
175 self.make_mocks(node_num)
176 if start_time is None:
177 start_time = time.time()
178 self.node_actor = cnode.ComputeNodeMonitorActor.start(
179 self.cloud_mock, start_time, self.shutdowns, self.timer,
180 self.updates, arv_node).proxy()
181 self.subscription = self.node_actor.subscribe(self.subscriber)
183 def test_init_shutdown_scheduling(self):
185 self.subscription.get(self.TIMEOUT)
186 self.assertTrue(self.timer.schedule.called)
187 self.assertEqual(300, self.timer.schedule.call_args[0][0])
189 def test_shutdown_subscription(self):
191 self.shutdowns._set_state(True, 600)
192 self.node_actor.consider_shutdown().get(self.TIMEOUT)
193 self.assertTrue(self.subscriber.called)
194 self.assertEqual(self.node_actor.actor_ref.actor_urn,
195 self.subscriber.call_args[0][0].actor_ref.actor_urn)
197 def test_shutdown_without_arvados_node(self):
199 self.shutdowns._set_state(True, 600)
200 self.node_actor.consider_shutdown().get(self.TIMEOUT)
201 self.assertTrue(self.subscriber.called)
203 def test_no_shutdown_without_arvados_node_and_old_cloud_node(self):
204 self.make_actor(start_time=0)
205 self.shutdowns._set_state(True, 600)
206 self.node_actor.consider_shutdown().get(self.TIMEOUT)
207 self.assertFalse(self.subscriber.called)
209 def check_shutdown_rescheduled(self, window_open, next_window,
211 self.shutdowns._set_state(window_open, next_window)
212 self.timer.schedule.reset_mock()
213 self.node_actor.consider_shutdown().get(self.TIMEOUT)
214 self.stop_proxy(self.node_actor)
215 self.assertTrue(self.timer.schedule.called)
216 if schedule_time is not None:
217 self.assertEqual(schedule_time, self.timer.schedule.call_args[0][0])
218 self.assertFalse(self.subscriber.called)
220 def test_shutdown_window_close_scheduling(self):
222 self.check_shutdown_rescheduled(False, 600, 600)
224 def test_no_shutdown_when_node_running_job(self):
225 self.make_actor(4, testutil.arvados_node_mock(4, job_uuid=True))
226 self.check_shutdown_rescheduled(True, 600)
228 def test_no_shutdown_when_node_state_unknown(self):
229 self.make_actor(5, testutil.arvados_node_mock(5, info={}))
230 self.check_shutdown_rescheduled(True, 600)
232 def test_no_shutdown_when_node_state_stale(self):
233 self.make_actor(6, testutil.arvados_node_mock(6, age=900))
234 self.check_shutdown_rescheduled(True, 600)
236 def test_arvados_node_match(self):
238 arv_node = testutil.arvados_node_mock(
239 2, hostname='compute-two.zzzzz.arvadosapi.com')
240 pair_id = self.node_actor.offer_arvados_pair(arv_node).get(self.TIMEOUT)
241 self.assertEqual(self.cloud_mock.id, pair_id)
242 self.stop_proxy(self.node_actor)
243 self.updates.sync_node.assert_called_with(self.cloud_mock, arv_node)
245 def test_arvados_node_mismatch(self):
247 arv_node = testutil.arvados_node_mock(1)
249 self.node_actor.offer_arvados_pair(arv_node).get(self.TIMEOUT))
251 def test_update_cloud_node(self):
254 self.cloud_mock.id = '1'
255 self.node_actor.update_cloud_node(self.cloud_mock)
256 current_cloud = self.node_actor.cloud_node.get(self.TIMEOUT)
257 self.assertEqual([testutil.ip_address_mock(2)],
258 current_cloud.private_ips)
260 def test_missing_cloud_node_update(self):
262 self.node_actor.update_cloud_node(None)
263 current_cloud = self.node_actor.cloud_node.get(self.TIMEOUT)
264 self.assertEqual([testutil.ip_address_mock(1)],
265 current_cloud.private_ips)
267 def test_update_arvados_node(self):
269 job_uuid = 'zzzzz-jjjjj-updatejobnode00'
270 new_arvados = testutil.arvados_node_mock(3, job_uuid)
271 self.node_actor.update_arvados_node(new_arvados)
272 current_arvados = self.node_actor.arvados_node.get(self.TIMEOUT)
273 self.assertEqual(job_uuid, current_arvados['job_uuid'])
275 def test_missing_arvados_node_update(self):
276 self.make_actor(4, testutil.arvados_node_mock(4))
277 self.node_actor.update_arvados_node(None)
278 current_arvados = self.node_actor.arvados_node.get(self.TIMEOUT)
279 self.assertEqual(testutil.ip_address_mock(4),
280 current_arvados['ip_address'])