1# Copyright 2018 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Tests for delete.""" 15 16import subprocess 17import unittest 18 19from unittest import mock 20 21from acloud import errors 22from acloud.delete import delete 23from acloud.internal.lib import cvd_compute_client_multi_stage 24from acloud.internal.lib import driver_test_lib 25from acloud.internal.lib import oxygen_client 26from acloud.internal.lib import utils 27from acloud.list import list as list_instances 28from acloud.public import config 29from acloud.public import device_driver 30from acloud.public import report 31 32 33# pylint: disable=invalid-name,protected-access,unused-argument,no-member 34class DeleteTest(driver_test_lib.BaseDriverTest): 35 """Test delete functions.""" 36 37 def testDeleteLocalCuttlefishInstanceSuccess(self): 38 """Test DeleteLocalCuttlefishInstance.""" 39 instance_object = mock.MagicMock() 40 instance_object.name = "local-instance" 41 mock_lock = mock.Mock() 42 mock_lock.Lock.return_value = True 43 instance_object.GetLock.return_value = mock_lock 44 45 delete_report = report.Report(command="delete") 46 delete.DeleteLocalCuttlefishInstance(instance_object, delete_report) 47 self.assertEqual(delete_report.data, { 48 "deleted": [ 49 { 50 "type": "instance", 51 "name": "local-instance", 52 }, 53 ], 54 }) 55 self.assertEqual(delete_report.status, "SUCCESS") 56 mock_lock.SetInUse.assert_called_once_with(False) 57 mock_lock.Unlock.assert_called_once() 58 59 mock_lock.Lock.return_value = False 60 delete.DeleteLocalCuttlefishInstance(instance_object, delete_report) 61 self.assertEqual(delete_report.status, "FAIL") 62 63 def testDeleteLocalCuttlefishInstanceFailure(self): 64 """Test DeleteLocalCuttlefishInstance with command failure.""" 65 instance_object = mock.MagicMock() 66 instance_object.name = "local-instance" 67 instance_object.Delete.side_effect = subprocess.CalledProcessError( 68 1, "cmd") 69 mock_lock = mock.Mock() 70 mock_lock.Lock.return_value = True 71 instance_object.GetLock.return_value = mock_lock 72 73 delete_report = report.Report(command="delete") 74 delete.DeleteLocalCuttlefishInstance(instance_object, delete_report) 75 76 self.assertEqual(delete_report.status, "FAIL") 77 mock_lock.SetInUse.assert_called_once_with(False) 78 mock_lock.Unlock.assert_called_once() 79 80 def testDeleteLocalGoldfishInstanceSuccess(self): 81 """Test DeleteLocalGoldfishInstance.""" 82 mock_adb_tools = mock.Mock() 83 mock_adb_tools.EmuCommand.return_value = 0 84 mock_instance = mock.Mock(adb=mock_adb_tools, 85 adb_port=5555, 86 device_serial="serial", 87 instance_dir="/unit/test") 88 # name is a positional argument of Mock(). 89 mock_instance.name = "unittest" 90 mock_lock = mock.Mock() 91 mock_lock.Lock.return_value = True 92 mock_instance.GetLock.return_value = mock_lock 93 94 delete_report = report.Report(command="delete") 95 delete.DeleteLocalGoldfishInstance(mock_instance, delete_report) 96 97 mock_adb_tools.EmuCommand.assert_called_with("kill") 98 self.assertEqual(delete_report.data, { 99 "deleted": [ 100 { 101 "type": "instance", 102 "name": "unittest", 103 }, 104 ], 105 }) 106 self.assertEqual(delete_report.status, "SUCCESS") 107 mock_lock.SetInUse.assert_called_once_with(False) 108 mock_lock.Unlock.assert_called_once() 109 110 mock_lock.Lock.return_value = False 111 delete.DeleteLocalGoldfishInstance(mock_instance, delete_report) 112 self.assertEqual(delete_report.status, "FAIL") 113 114 def testDeleteLocalGoldfishInstanceFailure(self): 115 """Test DeleteLocalGoldfishInstance with adb command failure.""" 116 mock_adb_tools = mock.Mock() 117 mock_adb_tools.EmuCommand.return_value = 1 118 mock_instance = mock.Mock(adb=mock_adb_tools, 119 adb_port=5555, 120 device_serial="serial", 121 instance_dir="/unit/test") 122 # name is a positional argument of Mock(). 123 mock_instance.name = "unittest" 124 mock_lock = mock.Mock() 125 mock_lock.Lock.return_value = True 126 mock_instance.GetLock.return_value = mock_lock 127 128 delete_report = report.Report(command="delete") 129 delete.DeleteLocalGoldfishInstance(mock_instance, delete_report) 130 131 mock_adb_tools.EmuCommand.assert_called_with("kill") 132 self.assertTrue(len(delete_report.errors) > 0) 133 self.assertEqual(delete_report.status, "FAIL") 134 mock_lock.SetInUse.assert_called_once_with(False) 135 mock_lock.Unlock.assert_called_once() 136 137 def testResetLocalInstanceLockByName(self): 138 """test ResetLocalInstanceLockByName.""" 139 mock_lock = mock.Mock() 140 mock_lock.Lock.return_value = True 141 self.Patch(list_instances, "GetLocalInstanceLockByName", 142 return_value=mock_lock) 143 delete_report = report.Report(command="delete") 144 delete.ResetLocalInstanceLockByName("unittest", delete_report) 145 146 self.assertEqual(delete_report.data, { 147 "deleted": [ 148 { 149 "type": "instance", 150 "name": "unittest", 151 }, 152 ], 153 }) 154 mock_lock.Lock.assert_called_once() 155 mock_lock.SetInUse.assert_called_once_with(False) 156 mock_lock.Unlock.assert_called_once() 157 158 mock_lock.Lock.return_value = False 159 delete.ResetLocalInstanceLockByName("unittest", delete_report) 160 self.assertEqual(delete_report.status, "FAIL") 161 162 def testResetLocalInstanceLockByNameFailure(self): 163 """test ResetLocalInstanceLockByName with an invalid name.""" 164 self.Patch(list_instances, "GetLocalInstanceLockByName", 165 return_value=None) 166 delete_report = report.Report(command="delete") 167 delete.ResetLocalInstanceLockByName("unittest", delete_report) 168 169 self.assertTrue(len(delete_report.errors) > 0) 170 self.assertEqual(delete_report.status, "FAIL") 171 172 @mock.patch("acloud.delete.delete.emulator_console.RemoteEmulatorConsole") 173 def testDeleteHostGoldfishInstance(self, mock_console): 174 """test DeleteHostGoldfishInstance.""" 175 mock_console_obj = mock.MagicMock() 176 mock_console.return_value = mock_console_obj 177 mock_console_obj.__enter__.return_value = mock_console_obj 178 179 cfg_attrs = {"ssh_private_key_path": "cfg_key_path", 180 "extra_args_ssh_tunnel": "extra args"} 181 mock_cfg = mock.Mock(spec_set=list(cfg_attrs.keys()), **cfg_attrs) 182 instance_name = "host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk" 183 delete_report = report.Report(command="delete") 184 185 delete.DeleteHostGoldfishInstance(mock_cfg, instance_name, 186 None, None, delete_report) 187 mock_console.assert_called_with("192.0.2.1", 5554, "vsoc-01", 188 "cfg_key_path", "extra args") 189 mock_console_obj.Kill.assert_called() 190 self.assertEqual(delete_report.status, "SUCCESS") 191 self.assertEqual(delete_report.data, { 192 "deleted": [ 193 { 194 "type": "instance", 195 "name": instance_name, 196 }, 197 ], 198 }) 199 200 mock_console_obj.reset_mock() 201 mock_console_obj.Kill.side_effect = errors.DeviceConnectionError 202 delete_report = report.Report(command="delete") 203 204 delete.DeleteHostGoldfishInstance(mock_cfg, instance_name, 205 "user", "key_path", delete_report) 206 mock_console.assert_called_with("192.0.2.1", 5554, "user", 207 "key_path", "extra args") 208 self.assertEqual(delete_report.status, "FAIL") 209 self.assertEqual(len(delete_report.errors), 1) 210 211 @mock.patch.object(delete, "ssh") 212 @mock.patch.object(delete, "cvd_utils") 213 def testCleanUpRemoteHost(self, mock_cvd_utils, mock_ssh): 214 """Test CleanUpRemoteHost.""" 215 mock_ssh_ip = mock.Mock() 216 mock_ssh.IP.return_value = mock_ssh_ip 217 mock_ssh_obj = mock.Mock() 218 mock_ssh.Ssh.return_value = mock_ssh_obj 219 cfg_attrs = {"ssh_private_key_path": "cfg_key_path"} 220 mock_cfg = mock.Mock(spec_set=list(cfg_attrs.keys()), **cfg_attrs) 221 delete_report = report.Report(command="delete") 222 delete.CleanUpRemoteHost(mock_cfg, "192.0.2.1", "vsoc-01", 223 None, delete_report) 224 225 mock_ssh.IP.assert_called_with(ip="192.0.2.1") 226 mock_ssh.Ssh.assert_called_with( 227 ip=mock_ssh_ip, 228 user="vsoc-01", 229 ssh_private_key_path="cfg_key_path") 230 mock_cvd_utils.CleanUpRemoteCvd.assert_called_with(mock_ssh_obj, 231 raise_error=True) 232 self.assertEqual(delete_report.status, "SUCCESS") 233 self.assertEqual(delete_report.data, { 234 "deleted": [ 235 { 236 "type": "remote host", 237 "name": "192.0.2.1", 238 }, 239 ], 240 }) 241 242 mock_ssh_ip.reset_mock() 243 mock_ssh_obj.reset_mock() 244 mock_cvd_utils.reset_mock() 245 mock_cvd_utils.CleanUpRemoteCvd.side_effect = ( 246 subprocess.CalledProcessError(cmd="test", returncode=1)) 247 delete_report = report.Report(command="delete") 248 249 delete.CleanUpRemoteHost(mock_cfg, "192.0.2.2", "user", 250 "key_path", delete_report) 251 mock_ssh.IP.assert_called_with(ip="192.0.2.2") 252 mock_ssh.Ssh.assert_called_with( 253 ip=mock_ssh_ip, 254 user="user", 255 ssh_private_key_path="key_path") 256 mock_cvd_utils.CleanUpRemoteCvd.assert_called_with(mock_ssh_obj, 257 raise_error=True) 258 self.assertEqual(delete_report.status, "FAIL") 259 self.assertEqual(len(delete_report.errors), 1) 260 261 @mock.patch.object(delete, "DeleteInstances", return_value="") 262 @mock.patch.object(delete, "ResetLocalInstanceLockByName") 263 @mock.patch.object(delete, "CleanUpRemoteHost") 264 @mock.patch.object(delete, "DeleteHostGoldfishInstance") 265 @mock.patch.object(delete, "DeleteRemoteInstances", return_value="") 266 def testDeleteInstanceByNames(self, mock_delete_remote_ins, 267 mock_delete_host_gf_ins, 268 mock_clean_up_remote_host, mock_reset_lock, 269 mock_delete_local_ins): 270 """test DeleteInstanceByNames.""" 271 cfg = mock.Mock() 272 # Test delete local instances. 273 instances = ["local-instance-1", "local-instance-2"] 274 mock_local_ins = mock.Mock() 275 mock_local_ins.name = "local-instance-1" 276 self.Patch(list_instances, "GetLocalInstancesByNames", 277 return_value=[mock_local_ins]) 278 delete.DeleteInstanceByNames(cfg, instances, None, None) 279 mock_delete_local_ins.assert_called_with(cfg, [mock_local_ins]) 280 mock_reset_lock.assert_called_with("local-instance-2", mock.ANY) 281 282 # Test delete remote host instances. 283 instances = ["host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk", 284 "host-192.0.2.2-123456-aosp_cf_x86_64_phone"] 285 delete.DeleteInstanceByNames(cfg, instances, "user", "key") 286 mock_delete_host_gf_ins.assert_called_with( 287 cfg, instances[0], "user", "key", mock.ANY) 288 mock_clean_up_remote_host.assert_called_with( 289 cfg, "192.0.2.2", "user", "key", mock.ANY) 290 291 # Test delete remote instances. 292 instances = ["ins-id1-cf-x86-phone-userdebug", 293 "ins-id2-cf-x86-phone-userdebug"] 294 delete.DeleteInstanceByNames(cfg, instances, None, None) 295 mock_delete_remote_ins.assert_called() 296 297 @mock.patch.object(oxygen_client.OxygenClient, "ReleaseDevice") 298 def testReleaseOxygenDevice(self, mock_release): 299 """test ReleaseOxygenDevice""" 300 cfg = mock.Mock() 301 cfg.oxygen_client = "oxygen_client" 302 ip = "0.0.0.0" 303 # Raise exception for multiple instances 304 instances = ["local-instance-1", "local-instance-2"] 305 self.assertRaises(errors.CommandArgError, delete._ReleaseOxygenDevice, cfg, instances, ip) 306 307 # Test release device with oxygen client 308 instances = ["local-instance-1"] 309 delete._ReleaseOxygenDevice(cfg, instances, ip) 310 mock_release.assert_called_once() 311 312 mock_release.side_effect = subprocess.CalledProcessError( 313 0, "fake_cmd", 314 "Error received while trying to release device: error_msg") 315 delete_report = delete._ReleaseOxygenDevice(cfg, instances, ip) 316 self.assertEqual(delete_report.errors, ["error_msg"]) 317 318 mock_release.side_effect = subprocess.CalledProcessError( 319 0, "fake_cmd", 320 "error") 321 delete_report = delete._ReleaseOxygenDevice(cfg, instances, ip) 322 self.assertEqual(delete_report.status, "FAIL") 323 324 def testDeleteInstances(self): 325 """test DeleteInstances.""" 326 fake_ins = mock.MagicMock() 327 fake_ins.islocal = False 328 fake_ins.avd_type = "cuttlefish" 329 fake_ins.vnc_port = None 330 331 fake_ins2 = mock.MagicMock() 332 fake_ins2.islocal = True 333 fake_ins2.avd_type = "cuttlefish" 334 fake_ins2.vnc_port = None 335 336 fake_ins3 = mock.MagicMock() 337 fake_ins3.islocal = True 338 fake_ins3.avd_type = "goldfish" 339 fake_ins3.vnc_port = None 340 341 fake_ins4 = mock.MagicMock() 342 fake_ins4.islocal = True 343 fake_ins4.avd_type = "unknown" 344 fake_ins4.vnc_port = 12345 345 346 self.Patch(delete, "DeleteLocalGoldfishInstance") 347 self.Patch(delete, "DeleteLocalCuttlefishInstance") 348 self.Patch(delete, "DeleteRemoteInstances") 349 self.Patch(utils, "CleanupSSVncviewer") 350 351 fake_instances_to_delete = [] 352 delete.DeleteInstances(None, fake_instances_to_delete) 353 delete.DeleteRemoteInstances.assert_not_called() 354 355 fake_instances_to_delete = [ 356 fake_ins, fake_ins2, fake_ins3, fake_ins4] 357 delete.DeleteInstances(None, fake_instances_to_delete) 358 delete.DeleteRemoteInstances.assert_called_once() 359 delete.DeleteLocalGoldfishInstance.assert_called_once() 360 delete.DeleteLocalCuttlefishInstance.assert_called_once() 361 utils.CleanupSSVncviewer.assert_called_once() 362 363 def testDeleteRemoteInstances(self): 364 """test DeleteRemoteInstances.""" 365 fake_cfg = mock.MagicMock() 366 fake_cfg.SupportRemoteInstance = mock.MagicMock() 367 fake_cfg.SupportRemoteInstance.return_value = True 368 fake_instances_to_delete = ["fake_ins"] 369 delete_report = report.Report(command="delete") 370 self.Patch(device_driver, "DeleteAndroidVirtualDevices", 371 return_value=delete_report) 372 delete.DeleteRemoteInstances(fake_cfg, fake_instances_to_delete) 373 device_driver.DeleteAndroidVirtualDevices.assert_called_once() 374 375 fake_cfg.SupportRemoteInstance.return_value = False 376 self.assertRaises(errors.ConfigError, 377 delete.DeleteRemoteInstances, 378 fake_cfg, fake_instances_to_delete) 379 380 def testRun(self): 381 """test Run.""" 382 args = mock.MagicMock() 383 args.oxygen = False 384 args.instance_names = None 385 args.remote_host = None 386 args.local_only = True 387 args.adb_port = None 388 args.all = True 389 390 self.Patch(delete, "_ReleaseOxygenDevice") 391 self.Patch(delete, "DeleteInstanceByNames") 392 self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, 393 "ParseRemoteHostAddress") 394 self.Patch(delete, "CleanUpRemoteHost") 395 fake_cfg = mock.MagicMock() 396 fake_cfg.SupportRemoteInstance = mock.MagicMock() 397 self.Patch(config, "GetAcloudConfig", return_value=fake_cfg) 398 self.Patch(list_instances, "GetLocalInstances", 399 return_value=[]) 400 self.Patch(list_instances, "GetRemoteInstances", 401 return_value=["remote_instances"]) 402 self.Patch(list_instances, "FilterInstancesByAdbPort", 403 return_value=["filter_by_port_instance"]) 404 self.Patch(list_instances, "ChooseInstancesFromList", 405 return_value=["choice_instance"]) 406 self.Patch(delete, "DeleteInstances") 407 408 delete.Run(args) 409 delete.DeleteInstances.assert_called_with(fake_cfg, []) 410 411 list_instances.GetLocalInstances.return_value = ["local_instances"] 412 delete.Run(args) 413 delete.DeleteInstances.assert_called_with(fake_cfg, ["local_instances"]) 414 415 args.all = False 416 delete.Run(args) 417 delete.DeleteInstances.assert_called_with(fake_cfg, ["choice_instance"]) 418 419 args.adb_port = "12345" 420 delete.Run(args) 421 delete.DeleteInstances.assert_called_with(fake_cfg, ["filter_by_port_instance"]) 422 423 args.local_only = False 424 args.all = True 425 args.adb_port = None 426 delete.Run(args) 427 delete.DeleteInstances.assert_called_with( 428 fake_cfg, ["local_instances", "remote_instances"]) 429 430 args.remote_host = True 431 delete.Run(args) 432 delete.CleanUpRemoteHost.assert_called_once() 433 434 args.instance_names = ["fake_ins_name"] 435 delete.Run(args) 436 delete.DeleteInstanceByNames.assert_called_once() 437 438 args.oxygen = True 439 delete.Run(args) 440 delete._ReleaseOxygenDevice.assert_called_once() 441 442 443if __name__ == "__main__": 444 unittest.main() 445