1784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi# Copyright 2014 The Chromium OS Authors. All rights reserved. 2784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi# Use of this source code is governed by a BSD-style license that can be 3784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi# found in the LICENSE file. 4784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 5784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiimport mox 6784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiimport unittest 7784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 8784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiimport common 9784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 10784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiimport django.core.exceptions 110697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shifrom autotest_lib.client.common_lib.cros.network import ping_runner 12784df0c730f522dc4de83fa81c0f6fe211247673Dan Shifrom autotest_lib.frontend import setup_django_environment 13784df0c730f522dc4de83fa81c0f6fe211247673Dan Shifrom autotest_lib.frontend.server import models as server_models 14784df0c730f522dc4de83fa81c0f6fe211247673Dan Shifrom autotest_lib.site_utils import server_manager 1556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shifrom autotest_lib.site_utils import server_manager_utils 1656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shifrom autotest_lib.site_utils.lib import infra 17784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 18784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 19784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiclass QueriableList(list): 20784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """A mock list object supports queries including filter and all. 21784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 22784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 23784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def filter(self, **kwargs): 24784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Mock the filter call in django model. 25784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 26784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi raise NotImplementedError() 27784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 28784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 2956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi def get(self, **kwargs): 3056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """Mock the get call in django model. 3156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """ 3256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi raise NotImplementedError() 3356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 3456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 35784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def all(self): 36784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Return all items in the list. 37784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 38784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi @return: All items in the list. 39784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 40784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi return [item for item in self] 41784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 42784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 43784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiclass ServerManagerUnittests(mox.MoxTestBase): 44784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Unittest for testing server_manager module. 45784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 46784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 47784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def setUp(self): 48784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Initialize the unittest.""" 49784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi super(ServerManagerUnittests, self).setUp() 50784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 51784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi # Initialize test objects. 52784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.DRONE_ROLE = mox.MockObject( 53784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.ServerRole, 54784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'role': server_models.ServerRole.ROLE.DRONE}) 55784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.SCHEDULER_ROLE = mox.MockObject( 56784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.ServerRole, 57784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'role': server_models.ServerRole.ROLE.SCHEDULER}) 58784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.DRONE_ATTRIBUTE = mox.MockObject( 59784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.ServerAttribute, 60784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'attribute': 'max_processes', 'value':1}) 61784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.PRIMARY_DRONE = mox.MockObject( 62784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server, 63784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'hostname': 'primary_drone_hostname', 64784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'status': server_models.Server.STATUS.PRIMARY, 65784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'roles': QueriableList([self.DRONE_ROLE]), 66784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'attributes': QueriableList([self.DRONE_ATTRIBUTE])}) 67784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.BACKUP_DRONE = mox.MockObject( 68784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server, 69784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'hostname': 'backup_drone_hostname', 70784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'status': server_models.Server.STATUS.BACKUP, 71784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'roles': QueriableList([self.DRONE_ROLE]), 72784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'attributes': QueriableList([self.DRONE_ATTRIBUTE])}) 73784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.PRIMARY_SCHEDULER = mox.MockObject( 74784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server, 75784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'hostname': 'primary_scheduler_hostname', 76784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'status': server_models.Server.STATUS.PRIMARY, 77784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'roles': QueriableList([self.SCHEDULER_ROLE]), 78784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'attributes': QueriableList([])}) 79784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.BACKUP_SCHEDULER = mox.MockObject( 80784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server, 81784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi attrs={'hostname': 'backup_scheduler_hostname', 82784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'status': server_models.Server.STATUS.BACKUP, 83784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'roles': QueriableList([self.SCHEDULER_ROLE]), 84784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'attributes': QueriableList([])}) 85784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 8656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(server_manager_utils, 'check_server') 8756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(server_manager_utils, 'warn_missing_role') 8856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(server_manager_utils, 'use_server_db') 8956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(server_models.Server, 'get_role_names') 90784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.Server.objects, 'create') 91784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.Server.objects, 'filter') 92784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.Server.objects, 'get') 9356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(server_models.ServerRole, 'delete') 94784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.ServerRole.objects, 'create') 95784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.ServerRole.objects, 'filter') 96784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.ServerAttribute.objects, 97784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'create') 98784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(server_models.ServerAttribute.objects, 99784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 'filter') 10056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(infra, 'execute_command') 1010697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shi self.mox.StubOutWithMock(ping_runner.PingRunner, 'simple_ping') 102784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 103784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 104784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testCreateServerSuccess(self): 105784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test create method can create a server successfully. 106784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 1070697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shi ping_runner.PingRunner().simple_ping(self.BACKUP_DRONE.hostname 1080697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shi ).AndReturn(True) 109784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server.objects.get( 110784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi hostname=self.BACKUP_DRONE.hostname 111784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndRaise(django.core.exceptions.ObjectDoesNotExist) 112784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server.objects.create( 113784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi hostname=mox.IgnoreArg(), status=mox.IgnoreArg(), 114784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi date_created=mox.IgnoreArg(), note=mox.IgnoreArg() 115784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(self.BACKUP_DRONE) 116784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.ServerRole.objects.create( 117784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=mox.IgnoreArg(), role=server_models.ServerRole.ROLE.DRONE 118784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(self.DRONE_ROLE) 119784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 120784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi drone = server_manager.create(hostname=self.BACKUP_DRONE.hostname, 121784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi role=server_models.ServerRole.ROLE.DRONE) 122784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 123784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 124784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testAddRoleToBackupSuccess(self): 125784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager can add a role to a backup server successfully. 126784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 127784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi Confirm that database call is made, and no action is taken, e.g., 128784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi restart scheduler to activate a new devserver. 129784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 130784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(role=server_models.ServerRole.ROLE.DEVSERVER) 13156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.check_server(mox.IgnoreArg(), 13256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi mox.IgnoreArg()).AndReturn(True) 13356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes( 13456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(True) 13556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE, 'get_role_names') 13656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.get_role_names().AndReturn( 13756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 138784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.ServerRole.objects.create( 139784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=mox.IgnoreArg(), 140784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi role=server_models.ServerRole.ROLE.DEVSERVER 141784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(self.DRONE_ROLE) 142784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 143784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._add_role(server=self.BACKUP_DRONE, 14456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DEVSERVER, 14556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 146784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 147784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 148784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testAddRoleToBackupFail_RoleAlreadyExists(self): 149784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager fails to add a role to a backup server if server already 150784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi has the given role. 151784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 152784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(role=server_models.ServerRole.ROLE.DRONE) 15356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE, 'get_role_names') 15456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.get_role_names().AndReturn( 15556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 156784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 15756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.assertRaises(server_manager_utils.ServerActionError, 158784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._add_role, 159784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=self.BACKUP_DRONE, 16056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DRONE, 16156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 162784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 163784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 164784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testDeleteRoleFromBackupSuccess(self): 165784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager can delete a role from a backup server successfully. 166784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 167784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi Confirm that database call is made, and no action is taken, e.g., 168784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi restart scheduler to delete an existing devserver. 169784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 170784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(role=server_models.ServerRole.ROLE.DRONE) 17156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes( 17256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(True) 17356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE, 'get_role_names') 17456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.get_role_names().MultipleTimes().AndReturn( 17556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 17656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE.roles, 'get') 17756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.roles.get( 178784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi role=server_models.ServerRole.ROLE.DRONE 17956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(self.DRONE_ROLE) 180784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 181784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._delete_role(server=self.BACKUP_DRONE, 18256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DRONE, 18356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 184784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 185784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 186784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testDeleteRoleFromBackupFail_RoleNotExist(self): 187784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager fails to delete a role from a backup server if the 188784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server does not have the given role. 189784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 190784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(role=server_models.ServerRole.ROLE.DEVSERVER) 19156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE, 'get_role_names') 19256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.get_role_names().AndReturn( 19356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 194784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 19556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.assertRaises(server_manager_utils.ServerActionError, 196784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._delete_role, server=self.BACKUP_DRONE, 19756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DEVSERVER, 19856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 199784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 200784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 201784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testChangeStatusSuccess_BackupToPrimary(self): 202784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager can change the status of a backup server to primary. 203784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 204784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(status=server_models.Server.STATUS.PRIMARY) 20556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes( 20656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(True) 20756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE, 'get_role_names') 20856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.get_role_names().MultipleTimes().AndReturn( 20956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 210784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE.roles, 'filter') 211784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.BACKUP_DRONE.roles.filter( 212784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE 213784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(None) 21456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.Server.objects.filter( 21556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi roles__role=server_models.ServerRole.ROLE.SCHEDULER, 21656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.PRIMARY 21756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn([self.PRIMARY_SCHEDULER]) 21856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg()) 219784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 220784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._change_status( 221784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=self.BACKUP_DRONE, 22256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.PRIMARY, 22356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 224784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 225784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 226784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testChangeStatusSuccess_PrimaryToBackup(self): 227784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager can change the status of a primary server to backup. 228784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 229784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(status=server_models.Server.STATUS.BACKUP) 230784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(self.PRIMARY_DRONE.roles, 'filter') 23156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.PRIMARY_DRONE, 'get_role_names') 23256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.PRIMARY_DRONE.get_role_names().MultipleTimes().AndReturn( 23356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 234784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.PRIMARY_DRONE.roles.filter( 235784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE 236784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(None) 23756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes().AndReturn(True) 23856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.warn_missing_role( 23956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.ROLE.DRONE, self.PRIMARY_DRONE) 24056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.Server.objects.filter( 24156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi roles__role=server_models.ServerRole.ROLE.SCHEDULER, 24256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.PRIMARY 24356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn([self.PRIMARY_SCHEDULER]) 24456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg()) 245784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 246784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._change_status( 247784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=self.PRIMARY_DRONE, 24856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.BACKUP, 24956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 250784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 251784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 252784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testChangeStatusFail_StatusNoChange(self): 253784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager cannot change the status of a server with the same 254784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi status. 255784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 256784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(status=server_models.Server.STATUS.BACKUP) 257784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 25856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.assertRaises(server_manager_utils.ServerActionError, 259784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._change_status, 260784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=self.BACKUP_DRONE, 26156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.BACKUP, 26256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 263784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 264784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 265784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi def testChangeStatusFail_UniqueInstance(self): 266784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """Test manager cannot change the status of a server from backup to 267784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi primary if there is already a primary exists for role doesn't allow 268784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi multiple instances. 269784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi """ 270784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.validate(status=server_models.Server.STATUS.PRIMARY) 271784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.StubOutWithMock(self.BACKUP_SCHEDULER.roles, 'filter') 272784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.BACKUP_SCHEDULER.roles.filter( 273784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE 274784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(QueriableList([self.SCHEDULER_ROLE])) 275784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_models.Server.objects.filter( 276784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi roles__role=self.SCHEDULER_ROLE.role, 277784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi status=server_models.Server.STATUS.PRIMARY 278784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi ).AndReturn(QueriableList([self.PRIMARY_SCHEDULER])) 279784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi self.mox.ReplayAll() 28056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.assertRaises(server_manager_utils.ServerActionError, 281784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server_manager._change_status, 282784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi server=self.BACKUP_SCHEDULER, 28356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.PRIMARY, 28456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 28556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 28656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 28756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi def testAddRoleToBackupFail_CheckServerFail(self): 28856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """Test manager fails to add a role to a backup server if check_server 28956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi is failed. 29056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """ 29156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.check_server(mox.IgnoreArg(), 29256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi mox.IgnoreArg()).AndReturn(False) 29356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.validate(role=server_models.ServerRole.ROLE.DRONE) 29456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.BACKUP_DRONE, 'get_role_names') 29556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.BACKUP_DRONE.get_role_names().MultipleTimes().AndReturn( 29656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 29756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.ReplayAll() 29856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.assertRaises(server_manager_utils.ServerActionError, 29956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager._add_role, server=self.BACKUP_DRONE, 30056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.SCHEDULER, 30156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 30256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 30356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 30456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi def testAddRoleToPrimarySuccess(self): 30556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """Test manager can add a role to a primary server successfully. 30656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 30756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi Confirm that actions needs to be taken, e.g., restart scheduler for 30856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi new drone to be added. 30956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """ 31056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.validate(role=server_models.ServerRole.ROLE.DRONE) 31156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.check_server(mox.IgnoreArg(), 31256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi mox.IgnoreArg()).AndReturn(True) 31356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes().AndReturn(True) 31456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.PRIMARY_SCHEDULER, 'get_role_names') 31556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.PRIMARY_SCHEDULER.get_role_names().AndReturn( 31656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.SCHEDULER]) 31756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.objects.create( 31856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server=self.PRIMARY_SCHEDULER, 31956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DRONE 32056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(self.DRONE_ROLE) 32156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.Server.objects.filter( 32256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi roles__role=server_models.ServerRole.ROLE.SCHEDULER, 32356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.PRIMARY 32456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn([self.PRIMARY_SCHEDULER]) 32556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg()) 32656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.ReplayAll() 32756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager._add_role(self.PRIMARY_SCHEDULER, 32856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.ROLE.DRONE, 32956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 33056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 33156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 33256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi def testDeleteRoleFromPrimarySuccess(self): 33356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """Test manager can delete a role from a primary server successfully. 33456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 33556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi Confirm that database call is made, and actions are taken, e.g., 33656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi restart scheduler to delete an existing drone. 33756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """ 33856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes().AndReturn(True) 33956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.validate(role=server_models.ServerRole.ROLE.DRONE) 34056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.PRIMARY_DRONE, 'get_role_names') 34156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.PRIMARY_DRONE.get_role_names().MultipleTimes().AndReturn( 34256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 34356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 34456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.PRIMARY_DRONE.roles, 'get') 34556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.PRIMARY_DRONE.roles.get( 34656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DRONE 34756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(self.DRONE_ROLE) 34856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 34956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.Server.objects.filter( 35056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi roles__role=server_models.ServerRole.ROLE.SCHEDULER, 35156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi status=server_models.Server.STATUS.PRIMARY 35256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn([self.PRIMARY_SCHEDULER]) 35356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager.server_manager_utils.warn_missing_role( 35456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.ROLE.DRONE, self.PRIMARY_DRONE) 35556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi infra.execute_command(mox.IgnoreArg(), mox.IgnoreArg()) 35656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.ReplayAll() 35756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager._delete_role(self.PRIMARY_DRONE, 35856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.ROLE.DRONE, 35956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=True) 36056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 36156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 36256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi def testDeleteRoleFromPrimarySuccess_NoAction(self): 36356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """Test manager can delete a role from a primary server successfully. 36456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 36556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi Confirm that database call is made, and no action is taken as action 36656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi is set to False. 36756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi """ 36856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager_utils.use_server_db().MultipleTimes().AndReturn(True) 36956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.validate(role=server_models.ServerRole.ROLE.DRONE) 37056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.PRIMARY_DRONE, 'get_role_names') 37156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.PRIMARY_DRONE.get_role_names().MultipleTimes().AndReturn( 37256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi [server_models.ServerRole.ROLE.DRONE]) 37356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 37456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.StubOutWithMock(self.PRIMARY_DRONE.roles, 'get') 37556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.PRIMARY_DRONE.roles.get( 37656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi role=server_models.ServerRole.ROLE.DRONE 37756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi ).AndReturn(self.DRONE_ROLE) 37856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi 37956f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager.server_manager_utils.warn_missing_role( 38056f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.ROLE.DRONE, self.PRIMARY_DRONE) 38156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi self.mox.ReplayAll() 38256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_manager._delete_role(self.PRIMARY_DRONE, 38356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi server_models.ServerRole.ROLE.DRONE, 38456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi action=False) 385784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 386784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi 387784df0c730f522dc4de83fa81c0f6fe211247673Dan Shiif '__main__': 388784df0c730f522dc4de83fa81c0f6fe211247673Dan Shi unittest.main() 389