Merge branch '3381-job-progress-bar-bug' closes #3381
[arvados.git] / services / nodemanager / tests / test_daemon.py
1 #!/usr/bin/env python
2
3 from __future__ import absolute_import, print_function
4
5 import time
6 import unittest
7
8 import mock
9
10 import arvnodeman.daemon as nmdaemon
11 from . import testutil
12
13 class NodeManagerDaemonActorTestCase(testutil.ActorTestMixin,
14                                      unittest.TestCase):
15     def make_daemon(self, cloud_nodes=[], arvados_nodes=[], want_sizes=[]):
16         for name in ['cloud_nodes', 'arvados_nodes', 'server_wishlist']:
17             setattr(self, name + '_poller', mock.MagicMock(name=name + '_mock'))
18         self.arv_factory = mock.MagicMock(name='arvados_mock')
19         self.cloud_factory = mock.MagicMock(name='cloud_mock')
20         self.cloud_factory().node_start_time.return_value = time.time()
21         self.cloud_updates = mock.MagicMock(name='updates_mock')
22         self.timer = testutil.MockTimer()
23         self.node_factory = mock.MagicMock(name='factory_mock')
24         self.node_setup = mock.MagicMock(name='setup_mock')
25         self.node_shutdown = mock.MagicMock(name='shutdown_mock')
26         self.daemon = nmdaemon.NodeManagerDaemonActor.start(
27             self.server_wishlist_poller, self.arvados_nodes_poller,
28             self.cloud_nodes_poller, self.cloud_updates, self.timer,
29             self.arv_factory, self.cloud_factory,
30             [54, 5, 1], 8, 600, 3600,
31             self.node_setup, self.node_shutdown, self.node_factory).proxy()
32         if cloud_nodes is not None:
33             self.daemon.update_cloud_nodes(cloud_nodes)
34         if arvados_nodes is not None:
35             self.daemon.update_arvados_nodes(arvados_nodes)
36         if want_sizes is not None:
37             self.daemon.update_server_wishlist(want_sizes)
38
39     def test_easy_node_creation(self):
40         size = testutil.MockSize(1)
41         self.make_daemon(want_sizes=[size])
42         self.wait_for_call(self.node_setup.start)
43
44     def test_node_pairing(self):
45         cloud_node = testutil.cloud_node_mock(1)
46         arv_node = testutil.arvados_node_mock(1)
47         self.make_daemon([cloud_node], [arv_node])
48         self.wait_for_call(self.node_factory.start)
49         pair_func = self.node_factory.start().proxy().offer_arvados_pair
50         self.wait_for_call(pair_func)
51         pair_func.assert_called_with(arv_node)
52
53     def test_node_pairing_after_arvados_update(self):
54         cloud_node = testutil.cloud_node_mock(2)
55         arv_node = testutil.arvados_node_mock(2, ip_address=None)
56         self.make_daemon([cloud_node], None)
57         pair_func = self.node_factory.start().proxy().offer_arvados_pair
58         pair_func().get.return_value = None
59         self.daemon.update_arvados_nodes([arv_node]).get(self.TIMEOUT)
60         pair_func.assert_called_with(arv_node)
61
62         pair_func().get.return_value = cloud_node.id
63         pair_func.reset_mock()
64         arv_node = testutil.arvados_node_mock(2)
65         self.daemon.update_arvados_nodes([arv_node]).get(self.TIMEOUT)
66         pair_func.assert_called_with(arv_node)
67
68     def test_old_arvados_node_not_double_assigned(self):
69         arv_node = testutil.arvados_node_mock(3, age=9000)
70         size = testutil.MockSize(3)
71         self.make_daemon(arvados_nodes=[arv_node], want_sizes=[size, size])
72         node_starter = self.node_setup.start
73         deadline = time.time() + self.TIMEOUT
74         while (time.time() < deadline) and (node_starter.call_count < 2):
75             time.sleep(.1)
76         self.assertEqual(2, node_starter.call_count)
77         used_nodes = [call[1].get('arvados_node')
78                       for call in node_starter.call_args_list]
79         self.assertIn(arv_node, used_nodes)
80         self.assertIn(None, used_nodes)
81
82     def test_node_count_satisfied(self):
83         self.make_daemon([testutil.cloud_node_mock()])
84         self.daemon.update_server_wishlist(
85             [testutil.MockSize(1)]).get(self.TIMEOUT)
86         self.assertFalse(self.node_setup.called)
87
88     def test_booting_nodes_counted(self):
89         cloud_node = testutil.cloud_node_mock(1)
90         arv_node = testutil.arvados_node_mock(1)
91         server_wishlist = [testutil.MockSize(1)] * 2
92         self.make_daemon([cloud_node], [arv_node], server_wishlist)
93         self.wait_for_call(self.node_setup.start)
94         self.node_setup.reset_mock()
95         self.daemon.update_server_wishlist(server_wishlist).get(self.TIMEOUT)
96         self.assertFalse(self.node_setup.called)
97
98     def test_no_duplication_when_booting_node_listed_fast(self):
99         # Test that we don't start two ComputeNodeMonitorActors when
100         # we learn about a booting node through a listing before we
101         # get the "node up" message from CloudNodeSetupActor.
102         cloud_node = testutil.cloud_node_mock(1)
103         self.make_daemon(want_sizes=[testutil.MockSize(1)])
104         self.wait_for_call(self.node_setup.start)
105         setup = mock.MagicMock(name='setup_node_mock')
106         setup.actor_ref = self.node_setup.start().proxy().actor_ref
107         setup.cloud_node.get.return_value = cloud_node
108         setup.arvados_node.get.return_value = testutil.arvados_node_mock(1)
109         self.daemon.update_cloud_nodes([cloud_node])
110         self.wait_for_call(self.node_factory.start)
111         self.node_factory.reset_mock()
112         self.daemon.node_up(setup).get(self.TIMEOUT)
113         self.assertFalse(self.node_factory.start.called)
114
115     def test_booting_nodes_shut_down(self):
116         self.make_daemon(want_sizes=[testutil.MockSize(1)])
117         self.wait_for_call(self.node_setup.start)
118         self.daemon.update_server_wishlist([])
119         self.wait_for_call(
120             self.node_setup.start().proxy().stop_if_no_cloud_node)
121
122     def test_shutdown_declined_at_wishlist_capacity(self):
123         cloud_node = testutil.cloud_node_mock(1)
124         size = testutil.MockSize(1)
125         self.make_daemon(cloud_nodes=[cloud_node], want_sizes=[size])
126         node_actor = self.node_factory().proxy()
127         self.daemon.node_can_shutdown(node_actor).get(self.TIMEOUT)
128         self.assertFalse(node_actor.shutdown.called)
129
130     def test_shutdown_accepted_below_capacity(self):
131         self.make_daemon(cloud_nodes=[testutil.cloud_node_mock()])
132         node_actor = self.node_factory().proxy()
133         self.daemon.node_can_shutdown(node_actor)
134         self.wait_for_call(self.node_shutdown.start)
135
136     def test_clean_shutdown_waits_for_node_setup_finish(self):
137         self.make_daemon(want_sizes=[testutil.MockSize(1)])
138         self.wait_for_call(self.node_setup.start)
139         new_node = self.node_setup.start().proxy()
140         self.daemon.shutdown()
141         self.wait_for_call(new_node.stop_if_no_cloud_node)
142         self.daemon.node_up(new_node)
143         self.wait_for_call(new_node.stop)
144         self.assertTrue(
145             self.daemon.actor_ref.actor_stopped.wait(self.TIMEOUT))
146
147     def test_wishlist_ignored_after_shutdown(self):
148         size = testutil.MockSize(2)
149         self.make_daemon(want_sizes=[size])
150         node_starter = self.node_setup.start
151         self.wait_for_call(node_starter)
152         node_starter.reset_mock()
153         self.daemon.shutdown()
154         self.daemon.update_server_wishlist([size] * 2).get(self.TIMEOUT)
155         # Send another message and wait for a response, to make sure all
156         # internal messages generated by the wishlist update are processed.
157         self.daemon.update_server_wishlist([size] * 2).get(self.TIMEOUT)
158         self.assertFalse(node_starter.called)