1# Copyright 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import mox
6import unittest
7
8import common
9
10import django.core.exceptions
11from autotest_lib.client.common_lib.cros.network import ping_runner
12from autotest_lib.frontend import setup_django_environment
13from autotest_lib.frontend.server import models as server_models
14from autotest_lib.site_utils import server_manager
15from autotest_lib.site_utils import server_manager_utils
16from autotest_lib.site_utils.lib import infra
17
18
19class QueriableList(list):
20    """A mock list object supports queries including filter and all.
21    """
22
23    def filter(self, **kwargs):
24        """Mock the filter call in django model.
25        """
26        raise NotImplementedError()
27
28
29    def get(self, **kwargs):
30        """Mock the get call in django model.
31        """
32        raise NotImplementedError()
33
34
35    def all(self):
36        """Return all items in the list.
37
38        @return: All items in the list.
39        """
40        return [item for item in self]
41
42
43class ServerManagerUnittests(mox.MoxTestBase):
44    """Unittest for testing server_manager module.
45    """
46
47    def setUp(self):
48        """Initialize the unittest."""
49        super(ServerManagerUnittests, self).setUp()
50
51        # Initialize test objects.
52        self.DRONE_ROLE = mox.MockObject(
53                server_models.ServerRole,
54                attrs={'role': server_models.ServerRole.ROLE.DRONE})
55        self.SCHEDULER_ROLE = mox.MockObject(
56                server_models.ServerRole,
57                attrs={'role': server_models.ServerRole.ROLE.SCHEDULER})
58        self.DRONE_ATTRIBUTE = mox.MockObject(
59                server_models.ServerAttribute,
60                attrs={'attribute': 'max_processes', 'value':1})
61        self.PRIMARY_DRONE = mox.MockObject(
62                server_models.Server,
63                attrs={'hostname': 'primary_drone_hostname',
64                       'status': server_models.Server.STATUS.PRIMARY,
65                       'roles': QueriableList([self.DRONE_ROLE]),
66                       'attributes': QueriableList([self.DRONE_ATTRIBUTE])})
67        self.REPAIR_REQUIRED_DRONE = mox.MockObject(
68                server_models.Server,
69                attrs={'hostname': 'repair_required_drone_hostname',
70                       'status': server_models.Server.STATUS.REPAIR_REQUIRED,
71                       'roles': QueriableList([self.DRONE_ROLE]),
72                       'attributes': QueriableList([self.DRONE_ATTRIBUTE])})
73        self.PRIMARY_SCHEDULER = mox.MockObject(
74                server_models.Server,
75                attrs={'hostname': 'primary_scheduler_hostname',
76                       'status': server_models.Server.STATUS.PRIMARY,
77                       'roles': QueriableList([self.SCHEDULER_ROLE]),
78                       'attributes': QueriableList([])})
79        self.REPAIR_REQUIRED_SCHEDULER = mox.MockObject(
80                server_models.Server,
81                attrs={'hostname': 'repair_required_scheduler_hostname',
82                       'status': server_models.Server.STATUS.REPAIR_REQUIRED,
83                       'roles': QueriableList([self.SCHEDULER_ROLE]),
84                       'attributes': QueriableList([])})
85
86        self.mox.StubOutWithMock(server_manager_utils, 'check_server')
87        self.mox.StubOutWithMock(server_manager_utils, 'warn_missing_role')
88        self.mox.StubOutWithMock(server_manager_utils, 'use_server_db')
89        self.mox.StubOutWithMock(server_models.Server, 'get_role_names')
90        self.mox.StubOutWithMock(server_models.Server.objects, 'create')
91        self.mox.StubOutWithMock(server_models.Server.objects, 'filter')
92        self.mox.StubOutWithMock(server_models.Server.objects, 'get')
93        self.mox.StubOutWithMock(server_models.ServerRole, 'delete')
94        self.mox.StubOutWithMock(server_models.ServerRole.objects, 'create')
95        self.mox.StubOutWithMock(server_models.ServerRole.objects, 'filter')
96        self.mox.StubOutWithMock(server_models.ServerAttribute.objects,
97                                 'create')
98        self.mox.StubOutWithMock(server_models.ServerAttribute.objects,
99                                 'filter')
100        self.mox.StubOutWithMock(infra, 'execute_command')
101        self.mox.StubOutWithMock(ping_runner.PingRunner, 'simple_ping')
102
103
104    def testCreateServerSuccess(self):
105        """Test create method can create a server successfully.
106        """
107        ping_runner.PingRunner().simple_ping(self.PRIMARY_DRONE.hostname
108                                             ).AndReturn(True)
109        server_models.Server.objects.get(
110                hostname=self.PRIMARY_DRONE.hostname
111                ).AndRaise(django.core.exceptions.ObjectDoesNotExist)
112        server_models.Server.objects.create(
113                hostname=mox.IgnoreArg(), status=mox.IgnoreArg(),
114                date_created=mox.IgnoreArg(), note=mox.IgnoreArg()
115                ).AndReturn(self.PRIMARY_DRONE)
116        server_models.ServerRole.objects.create(
117                server=mox.IgnoreArg(), role=server_models.ServerRole.ROLE.DRONE
118                ).AndReturn(self.DRONE_ROLE)
119        self.mox.ReplayAll()
120        drone = server_manager.create(hostname=self.PRIMARY_DRONE.hostname,
121                                      role=server_models.ServerRole.ROLE.DRONE)
122
123
124    def testAddRoleToRepairRequiredSuccess(self):
125        """Test manager can add a role to a repair_failed server successfully.
126
127        Confirm that database call is made, and no action is taken, e.g.,
128        restart scheduler to activate a new devserver.
129        """
130        server_models.validate(role=server_models.ServerRole.ROLE.DEVSERVER)
131        server_manager_utils.check_server(mox.IgnoreArg(),
132                                          mox.IgnoreArg()).AndReturn(True)
133        server_manager_utils.use_server_db().MultipleTimes(
134                ).AndReturn(True)
135        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE, 'get_role_names')
136        self.REPAIR_REQUIRED_DRONE.get_role_names().AndReturn(
137                [server_models.ServerRole.ROLE.DRONE])
138        server_models.ServerRole.objects.create(
139                server=mox.IgnoreArg(),
140                role=server_models.ServerRole.ROLE.DEVSERVER
141                ).AndReturn(self.DRONE_ROLE)
142        self.mox.ReplayAll()
143        server_manager._add_role(server=self.REPAIR_REQUIRED_DRONE,
144                                 role=server_models.ServerRole.ROLE.DEVSERVER,
145                                 action=True)
146
147
148    def testAddRoleToRepairRequiredFail_RoleAlreadyExists(self):
149        """Test manager fails to add a role to a repair_required server if
150        server already has the given role.
151        """
152        server_models.validate(role=server_models.ServerRole.ROLE.DRONE)
153        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE, 'get_role_names')
154        self.REPAIR_REQUIRED_DRONE.get_role_names().AndReturn(
155                [server_models.ServerRole.ROLE.DRONE])
156        self.mox.ReplayAll()
157        self.assertRaises(server_manager_utils.ServerActionError,
158                          server_manager._add_role,
159                          server=self.REPAIR_REQUIRED_DRONE,
160                          role=server_models.ServerRole.ROLE.DRONE,
161                          action=True)
162
163
164    def testDeleteRoleFromRepairRequiredSuccess(self):
165        """Test manager can delete a role from a repair_required server
166        successfully.
167
168        Confirm that database call is made, and no action is taken, e.g.,
169        restart scheduler to delete an existing devserver.
170        """
171        server_models.validate(role=server_models.ServerRole.ROLE.DRONE)
172        server_manager_utils.use_server_db().MultipleTimes(
173                ).AndReturn(True)
174        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE, 'get_role_names')
175        self.REPAIR_REQUIRED_DRONE.get_role_names().MultipleTimes().AndReturn(
176                [server_models.ServerRole.ROLE.DRONE])
177        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE.roles, 'get')
178        self.REPAIR_REQUIRED_DRONE.roles.get(
179                role=server_models.ServerRole.ROLE.DRONE
180                ).AndReturn(self.DRONE_ROLE)
181        self.mox.ReplayAll()
182        server_manager._delete_role(server=self.REPAIR_REQUIRED_DRONE,
183                                    role=server_models.ServerRole.ROLE.DRONE,
184                                    action=True)
185
186
187    def testDeleteRoleFromRepairRequiredFail_RoleNotExist(self):
188        """Test manager fails to delete a role from a repair_required server if
189        the server does not have the given role.
190        """
191        server_models.validate(role=server_models.ServerRole.ROLE.DEVSERVER)
192        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE, 'get_role_names')
193        self.REPAIR_REQUIRED_DRONE.get_role_names().AndReturn(
194                [server_models.ServerRole.ROLE.DRONE])
195        self.mox.ReplayAll()
196        self.assertRaises(server_manager_utils.ServerActionError,
197                          server_manager._delete_role,
198                          server=self.REPAIR_REQUIRED_DRONE,
199                          role=server_models.ServerRole.ROLE.DEVSERVER,
200                          action=True)
201
202
203    def testChangeStatusSuccess_RepairFailedToPrimary(self):
204        """Test manager can change the status of a repair_required server to
205        primary.
206        """
207        server_models.validate(status=server_models.Server.STATUS.PRIMARY)
208        server_manager_utils.use_server_db().MultipleTimes(
209                ).AndReturn(True)
210        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE, 'get_role_names')
211        self.REPAIR_REQUIRED_DRONE.get_role_names().MultipleTimes().AndReturn(
212                [server_models.ServerRole.ROLE.DRONE])
213        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE.roles, 'filter')
214        self.REPAIR_REQUIRED_DRONE.roles.filter(
215                role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE
216                ).AndReturn(None)
217        server_models.Server.objects.filter(
218                roles__role=server_models.ServerRole.ROLE.SCHEDULER,
219                status=server_models.Server.STATUS.PRIMARY
220                ).AndReturn([self.PRIMARY_SCHEDULER])
221        infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg())
222        self.mox.ReplayAll()
223        server_manager._change_status(
224                server=self.REPAIR_REQUIRED_DRONE,
225                status=server_models.Server.STATUS.PRIMARY,
226                action=True)
227
228
229    def testChangeStatusSuccess_PrimaryToRepairFailed(self):
230        """Test manager can change the status of a primary server to
231        repair_required.
232        """
233        server_models.validate(
234                status=server_models.Server.STATUS.REPAIR_REQUIRED)
235        self.mox.StubOutWithMock(self.PRIMARY_DRONE.roles, 'filter')
236        self.mox.StubOutWithMock(self.PRIMARY_DRONE, 'get_role_names')
237        self.PRIMARY_DRONE.get_role_names().MultipleTimes().AndReturn(
238                [server_models.ServerRole.ROLE.DRONE])
239        self.PRIMARY_DRONE.roles.filter(
240                role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE
241                ).AndReturn(None)
242        server_manager_utils.use_server_db().MultipleTimes().AndReturn(True)
243        server_manager_utils.warn_missing_role(
244                server_models.ServerRole.ROLE.DRONE, self.PRIMARY_DRONE)
245        server_models.Server.objects.filter(
246                roles__role=server_models.ServerRole.ROLE.SCHEDULER,
247                status=server_models.Server.STATUS.PRIMARY
248                ).AndReturn([self.PRIMARY_SCHEDULER])
249        infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg())
250        self.mox.ReplayAll()
251        server_manager._change_status(
252                server=self.PRIMARY_DRONE,
253                status=server_models.Server.STATUS.REPAIR_REQUIRED,
254                action=True)
255
256
257    def testChangeStatusFail_StatusNoChange(self):
258        """Test manager cannot change the status of a server with the same
259        status.
260        """
261        server_models.validate(
262                status=server_models.Server.STATUS.REPAIR_REQUIRED)
263        self.mox.ReplayAll()
264        self.assertRaises(server_manager_utils.ServerActionError,
265                          server_manager._change_status,
266                          server=self.REPAIR_REQUIRED_DRONE,
267                          status=server_models.Server.STATUS.REPAIR_REQUIRED,
268                          action=True)
269
270
271    def testChangeStatusFail_UniqueInstance(self):
272        """Test manager cannot change the status of a server from
273        repair_required to primary if there is already a primary exists for
274        role doesn't allow multiple instances.
275        """
276        server_models.validate(status=server_models.Server.STATUS.PRIMARY)
277        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_SCHEDULER.roles, 'filter')
278        self.REPAIR_REQUIRED_SCHEDULER.roles.filter(
279                role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE
280                ).AndReturn(QueriableList([self.SCHEDULER_ROLE]))
281        server_models.Server.objects.filter(
282                roles__role=self.SCHEDULER_ROLE.role,
283                status=server_models.Server.STATUS.PRIMARY
284                ).AndReturn(QueriableList([self.PRIMARY_SCHEDULER]))
285        self.mox.ReplayAll()
286        self.assertRaises(server_manager_utils.ServerActionError,
287                          server_manager._change_status,
288                          server=self.REPAIR_REQUIRED_SCHEDULER,
289                          status=server_models.Server.STATUS.PRIMARY,
290                          action=True)
291
292
293    def testAddRoleToRepairFailedFail_CheckServerFail(self):
294        """Test manager fails to add a role to a repair_required server if check
295        server is failed.
296        """
297        server_manager_utils.check_server(mox.IgnoreArg(),
298                                          mox.IgnoreArg()).AndReturn(False)
299        server_models.validate(role=server_models.ServerRole.ROLE.DRONE)
300        self.mox.StubOutWithMock(self.REPAIR_REQUIRED_DRONE, 'get_role_names')
301        self.REPAIR_REQUIRED_DRONE.get_role_names().MultipleTimes().AndReturn(
302                [server_models.ServerRole.ROLE.DRONE])
303        self.mox.ReplayAll()
304        self.assertRaises(server_manager_utils.ServerActionError,
305                          server_manager._add_role,
306                          server=self.REPAIR_REQUIRED_DRONE,
307                          role=server_models.ServerRole.ROLE.SCHEDULER,
308                          action=True)
309
310
311    def testAddRoleToPrimarySuccess(self):
312        """Test manager can add a role to a primary server successfully.
313
314        Confirm that actions needs to be taken, e.g., restart scheduler for
315        new drone to be added.
316        """
317        server_models.validate(role=server_models.ServerRole.ROLE.DRONE)
318        server_manager_utils.check_server(mox.IgnoreArg(),
319                                          mox.IgnoreArg()).AndReturn(True)
320        server_manager_utils.use_server_db().MultipleTimes().AndReturn(True)
321        self.mox.StubOutWithMock(self.PRIMARY_SCHEDULER, 'get_role_names')
322        self.PRIMARY_SCHEDULER.get_role_names().AndReturn(
323                [server_models.ServerRole.ROLE.SCHEDULER])
324        server_models.ServerRole.objects.create(
325                server=self.PRIMARY_SCHEDULER,
326                role=server_models.ServerRole.ROLE.DRONE
327                ).AndReturn(self.DRONE_ROLE)
328        server_models.Server.objects.filter(
329                roles__role=server_models.ServerRole.ROLE.SCHEDULER,
330                status=server_models.Server.STATUS.PRIMARY
331                ).AndReturn([self.PRIMARY_SCHEDULER])
332        infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg())
333        self.mox.ReplayAll()
334        server_manager._add_role(self.PRIMARY_SCHEDULER,
335                                 server_models.ServerRole.ROLE.DRONE,
336                                 action=True)
337
338
339    def testDeleteRoleFromPrimarySuccess(self):
340        """Test manager can delete a role from a primary server successfully.
341
342        Confirm that database call is made, and actions are taken, e.g.,
343        restart scheduler to delete an existing drone.
344        """
345        server_manager_utils.use_server_db().MultipleTimes().AndReturn(True)
346        server_models.validate(role=server_models.ServerRole.ROLE.DRONE)
347        self.mox.StubOutWithMock(self.PRIMARY_DRONE, 'get_role_names')
348        self.PRIMARY_DRONE.get_role_names().MultipleTimes().AndReturn(
349                [server_models.ServerRole.ROLE.DRONE])
350
351        self.mox.StubOutWithMock(self.PRIMARY_DRONE.roles, 'get')
352        self.PRIMARY_DRONE.roles.get(
353                role=server_models.ServerRole.ROLE.DRONE
354                ).AndReturn(self.DRONE_ROLE)
355
356        server_models.Server.objects.filter(
357                roles__role=server_models.ServerRole.ROLE.SCHEDULER,
358                status=server_models.Server.STATUS.PRIMARY
359                ).AndReturn([self.PRIMARY_SCHEDULER])
360        server_manager.server_manager_utils.warn_missing_role(
361                server_models.ServerRole.ROLE.DRONE, self.PRIMARY_DRONE)
362        infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg())
363        self.mox.ReplayAll()
364        server_manager._delete_role(self.PRIMARY_DRONE,
365                                    server_models.ServerRole.ROLE.DRONE,
366                                    action=True)
367
368
369    def testDeleteRoleFromPrimarySuccess_NoAction(self):
370        """Test manager can delete a role from a primary server successfully.
371
372        Confirm that database call is made, and no action is taken as action
373        is set to False.
374        """
375        server_manager_utils.use_server_db().MultipleTimes().AndReturn(True)
376        server_models.validate(role=server_models.ServerRole.ROLE.DRONE)
377        self.mox.StubOutWithMock(self.PRIMARY_DRONE, 'get_role_names')
378        self.PRIMARY_DRONE.get_role_names().MultipleTimes().AndReturn(
379                [server_models.ServerRole.ROLE.DRONE])
380
381        self.mox.StubOutWithMock(self.PRIMARY_DRONE.roles, 'get')
382        self.PRIMARY_DRONE.roles.get(
383                role=server_models.ServerRole.ROLE.DRONE
384                ).AndReturn(self.DRONE_ROLE)
385
386        server_manager.server_manager_utils.warn_missing_role(
387                server_models.ServerRole.ROLE.DRONE, self.PRIMARY_DRONE)
388        self.mox.ReplayAll()
389        server_manager._delete_role(self.PRIMARY_DRONE,
390                                    server_models.ServerRole.ROLE.DRONE,
391                                    action=False)
392
393
394if __name__ == "__main__":
395    unittest.main()
396