1#!/usr/bin/env python 2# 3# Copyright 2018 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""Tests for instance class.""" 17 18import collections 19import datetime 20import subprocess 21 22import unittest 23import mock 24 25# pylint: disable=import-error 26import dateutil.parser 27import dateutil.tz 28 29from acloud.internal import constants 30from acloud.internal.lib import cvd_runtime_config 31from acloud.internal.lib import driver_test_lib 32from acloud.internal.lib.adb_tools import AdbTools 33from acloud.list import instance 34 35 36class InstanceTest(driver_test_lib.BaseDriverTest): 37 """Test instance.""" 38 PS_SSH_TUNNEL = ("/fake_ps_1 --fake arg \n" 39 "/fake_ps_2 --fake arg \n" 40 "/usr/bin/ssh -i ~/.ssh/acloud_rsa " 41 "-o UserKnownHostsFile=/dev/null " 42 "-o StrictHostKeyChecking=no -L 12345:127.0.0.1:6444 " 43 "-L 54321:127.0.0.1:6520 -N -f -l user 1.1.1.1") 44 PS_LAUNCH_CVD = ("Sat Nov 10 21:55:10 2018 /fake_path/bin/run_cvd ") 45 PS_RUNTIME_CF_CONFIG = {"x_res": "1080", "y_res": "1920", "dpi": "480"} 46 GCE_INSTANCE = { 47 constants.INS_KEY_NAME: "fake_ins_name", 48 constants.INS_KEY_CREATETIME: "fake_create_time", 49 constants.INS_KEY_STATUS: "fake_status", 50 constants.INS_KEY_ZONE: "test/zones/fake_zone", 51 "networkInterfaces": [{"accessConfigs": [{"natIP": "1.1.1.1"}]}], 52 "labels": {constants.INS_KEY_AVD_TYPE: "fake_type", 53 constants.INS_KEY_AVD_FLAVOR: "fake_flavor"}, 54 "metadata": { 55 "items":[{"key":constants.INS_KEY_AVD_TYPE, 56 "value":"fake_type"}, 57 {"key":constants.INS_KEY_AVD_FLAVOR, 58 "value":"fake_flavor"}]} 59 } 60 61 # pylint: disable=protected-access 62 def testCreateLocalInstance(self): 63 """"Test get local instance info from launch_cvd process.""" 64 self.Patch(subprocess, "check_output", return_value=self.PS_LAUNCH_CVD) 65 cf_config = mock.MagicMock( 66 instance_id=2, 67 x_res=1080, 68 y_res=1920, 69 dpi=480, 70 instance_dir="fake_instance_dir", 71 adb_port=6521, 72 vnc_port=6445, 73 adb_ip_port="127.0.0.1:6521", 74 ) 75 self.Patch(cvd_runtime_config, "CvdRuntimeConfig", 76 return_value=cf_config) 77 local_instance = instance.LocalInstance(cf_config) 78 79 self.assertEqual(constants.LOCAL_INS_NAME + "-2", local_instance.name) 80 self.assertEqual(True, local_instance.islocal) 81 self.assertEqual("1080x1920 (480)", local_instance.display) 82 expected_full_name = ("device serial: 127.0.0.1:%s (%s) elapsed time: %s" 83 % ("6521", 84 constants.LOCAL_INS_NAME + "-2", 85 "None")) 86 self.assertEqual(expected_full_name, local_instance.fullname) 87 self.assertEqual(6521, local_instance.adb_port) 88 self.assertEqual(6445, local_instance.vnc_port) 89 90 @mock.patch("acloud.list.instance.tempfile") 91 @mock.patch("acloud.list.instance.AdbTools") 92 def testCreateLocalGoldfishInstance(self, mock_adb_tools, mock_tempfile): 93 """"Test the attributes of LocalGoldfishInstance.""" 94 mock_tempfile.gettempdir.return_value = "/unit/test" 95 mock_adb_tools.return_value = mock.Mock(device_information={}) 96 97 inst = instance.LocalGoldfishInstance(1) 98 99 self.assertEqual(inst.name, "local-goldfish-instance-1") 100 self.assertEqual(inst.avd_type, constants.TYPE_GF) 101 self.assertEqual(inst.adb_port, 5555) 102 self.assertTrue(inst.islocal) 103 self.assertEqual(inst.console_port, 5554) 104 self.assertEqual(inst.device_serial, "emulator-5554") 105 self.assertEqual(inst.instance_dir, 106 "/unit/test/acloud_gf_temp/local-goldfish-instance-1") 107 108 @mock.patch("acloud.list.instance.open", 109 mock.mock_open(read_data="test createtime")) 110 @mock.patch("acloud.list.instance.os.path.isfile") 111 @mock.patch("acloud.list.instance.os.listdir") 112 @mock.patch("acloud.list.instance.os.path.isdir") 113 @mock.patch("acloud.list.instance.tempfile") 114 @mock.patch("acloud.list.instance.AdbTools") 115 @mock.patch("acloud.list.instance._GetElapsedTime") 116 def testGetLocalGoldfishInstances(self, mock_get_elapsed_time, 117 mock_adb_tools, mock_tempfile, 118 mock_isdir, mock_listdir, mock_isfile): 119 """Test LocalGoldfishInstance.GetExistingInstances.""" 120 mock_get_elapsed_time.return_value = datetime.timedelta(hours=10) 121 mock_adb_tools.return_value = mock.Mock(device_information={}) 122 mock_tempfile.gettempdir.return_value = "/unit/test" 123 acloud_gf_temp_path = "/unit/test/acloud_gf_temp" 124 subdir_names = ( 125 "local-goldfish-instance-1", 126 "local-goldfish-instance-2", 127 "local-goldfish-instance-3") 128 timestamp_paths = ( 129 "/unit/test/acloud_gf_temp/local-goldfish-instance-1/" 130 "creation_timestamp.txt", 131 "/unit/test/acloud_gf_temp/local-goldfish-instance-2/" 132 "creation_timestamp.txt", 133 "/unit/test/acloud_gf_temp/local-goldfish-instance-3/" 134 "creation_timestamp.txt") 135 mock_isdir.side_effect = lambda path: path == acloud_gf_temp_path 136 mock_listdir.side_effect = lambda path: ( 137 subdir_names if path == acloud_gf_temp_path else []) 138 mock_isfile.side_effect = lambda path: ( 139 path in (timestamp_paths[0], timestamp_paths[2])) 140 141 instances = instance.LocalGoldfishInstance.GetExistingInstances() 142 143 mock_isdir.assert_called_with(acloud_gf_temp_path) 144 mock_listdir.assert_called_with(acloud_gf_temp_path) 145 for timestamp_path in timestamp_paths: 146 mock_isfile.assert_any_call(timestamp_path) 147 self.assertEqual(len(instances), 2) 148 self.assertEqual(instances[0].console_port, 5554) 149 self.assertEqual(instances[0].createtime, "test createtime") 150 self.assertEqual(instances[0].fullname, 151 "device serial: emulator-5554 " 152 "(local-goldfish-instance-1) " 153 "elapsed time: 10:00:00") 154 self.assertEqual(instances[1].console_port, 5558) 155 self.assertEqual(instances[1].createtime, "test createtime") 156 self.assertEqual(instances[1].fullname, 157 "device serial: emulator-5558 " 158 "(local-goldfish-instance-3) " 159 "elapsed time: 10:00:00") 160 161 def testGetElapsedTime(self): 162 """Test _GetElapsedTime""" 163 # Instance time can't parse 164 start_time = "error time" 165 self.assertEqual(instance._MSG_UNABLE_TO_CALCULATE, 166 instance._GetElapsedTime(start_time)) 167 168 # Remote instance elapsed time 169 now = "2019-01-14T13:00:00.000-07:00" 170 start_time = "2019-01-14T03:00:00.000-07:00" 171 self.Patch(instance, "datetime") 172 instance.datetime.datetime.now.return_value = dateutil.parser.parse(now) 173 self.assertEqual( 174 datetime.timedelta(hours=10), instance._GetElapsedTime(start_time)) 175 176 # Local instance elapsed time 177 now = "Mon Jan 14 10:10:10 2019" 178 start_time = "Mon Jan 14 08:10:10 2019" 179 instance.datetime.datetime.now.return_value = dateutil.parser.parse( 180 now).replace(tzinfo=dateutil.tz.tzlocal()) 181 self.assertEqual( 182 datetime.timedelta(hours=2), instance._GetElapsedTime(start_time)) 183 184 # pylint: disable=protected-access 185 def testGetAdbVncPortFromSSHTunnel(self): 186 """"Test Get forwarding adb and vnc port from ssh tunnel.""" 187 self.Patch(subprocess, "check_output", return_value=self.PS_SSH_TUNNEL) 188 self.Patch(instance, "_GetElapsedTime", return_value="fake_time") 189 self.Patch(instance.RemoteInstance, "_GetZoneName", return_value="fake_zone") 190 forwarded_ports = instance.RemoteInstance( 191 mock.MagicMock()).GetAdbVncPortFromSSHTunnel( 192 "1.1.1.1", constants.TYPE_CF) 193 self.assertEqual(54321, forwarded_ports.adb_port) 194 self.assertEqual(12345, forwarded_ports.vnc_port) 195 196 # If avd_type is undefined in utils.AVD_PORT_DICT. 197 forwarded_ports = instance.RemoteInstance( 198 mock.MagicMock()).GetAdbVncPortFromSSHTunnel( 199 "1.1.1.1", "undefined_avd_type") 200 self.assertEqual(None, forwarded_ports.adb_port) 201 self.assertEqual(None, forwarded_ports.vnc_port) 202 203 # pylint: disable=protected-access 204 def testProcessGceInstance(self): 205 """"Test process instance detail.""" 206 fake_adb = 123456 207 fake_vnc = 654321 208 forwarded_ports = collections.namedtuple("ForwardedPorts", 209 [constants.VNC_PORT, 210 constants.ADB_PORT]) 211 self.Patch( 212 instance.RemoteInstance, 213 "GetAdbVncPortFromSSHTunnel", 214 return_value=forwarded_ports(vnc_port=fake_vnc, adb_port=fake_adb)) 215 self.Patch(instance, "_GetElapsedTime", return_value="fake_time") 216 self.Patch(AdbTools, "IsAdbConnected", return_value=True) 217 218 # test ssh_tunnel_is_connected will be true if ssh tunnel connection is found 219 instance_info = instance.RemoteInstance(self.GCE_INSTANCE) 220 self.assertTrue(instance_info.ssh_tunnel_is_connected) 221 self.assertEqual(instance_info.adb_port, fake_adb) 222 self.assertEqual(instance_info.vnc_port, fake_vnc) 223 self.assertEqual("1.1.1.1", instance_info.ip) 224 self.assertEqual("fake_status", instance_info.status) 225 self.assertEqual("fake_type", instance_info.avd_type) 226 self.assertEqual("fake_flavor", instance_info.avd_flavor) 227 expected_full_name = "device serial: 127.0.0.1:%s (%s) elapsed time: %s" % ( 228 fake_adb, self.GCE_INSTANCE[constants.INS_KEY_NAME], "fake_time") 229 self.assertEqual(expected_full_name, instance_info.fullname) 230 231 # test ssh tunnel is connected but adb is disconnected 232 self.Patch(AdbTools, "IsAdbConnected", return_value=False) 233 instance_info = instance.RemoteInstance(self.GCE_INSTANCE) 234 self.assertTrue(instance_info.ssh_tunnel_is_connected) 235 expected_full_name = "device serial: not connected (%s) elapsed time: %s" % ( 236 instance_info.name, "fake_time") 237 self.assertEqual(expected_full_name, instance_info.fullname) 238 239 # test ssh_tunnel_is_connected will be false if ssh tunnel connection is not found 240 self.Patch( 241 instance.RemoteInstance, 242 "GetAdbVncPortFromSSHTunnel", 243 return_value=forwarded_ports(vnc_port=None, adb_port=None)) 244 instance_info = instance.RemoteInstance(self.GCE_INSTANCE) 245 self.assertFalse(instance_info.ssh_tunnel_is_connected) 246 expected_full_name = "device serial: not connected (%s) elapsed time: %s" % ( 247 self.GCE_INSTANCE[constants.INS_KEY_NAME], "fake_time") 248 self.assertEqual(expected_full_name, instance_info.fullname) 249 250 def testInstanceSummary(self): 251 """Test instance summary.""" 252 fake_adb = 123456 253 fake_vnc = 654321 254 forwarded_ports = collections.namedtuple("ForwardedPorts", 255 [constants.VNC_PORT, 256 constants.ADB_PORT]) 257 self.Patch( 258 instance.RemoteInstance, 259 "GetAdbVncPortFromSSHTunnel", 260 return_value=forwarded_ports(vnc_port=fake_vnc, adb_port=fake_adb)) 261 self.Patch(instance, "_GetElapsedTime", return_value="fake_time") 262 self.Patch(AdbTools, "IsAdbConnected", return_value=True) 263 remote_instance = instance.RemoteInstance(self.GCE_INSTANCE) 264 result_summary = (" name: fake_ins_name\n " 265 " IP: 1.1.1.1\n " 266 " create time: fake_create_time\n " 267 " elapse time: fake_time\n " 268 " status: fake_status\n " 269 " avd type: fake_type\n " 270 " display: None\n " 271 " vnc: 127.0.0.1:654321\n " 272 " zone: fake_zone\n " 273 " adb serial: 127.0.0.1:123456\n " 274 " product: None\n " 275 " model: None\n " 276 " device: None\n " 277 " transport_id: None") 278 self.assertEqual(remote_instance.Summary(), result_summary) 279 280 self.Patch( 281 instance.RemoteInstance, 282 "GetAdbVncPortFromSSHTunnel", 283 return_value=forwarded_ports(vnc_port=None, adb_port=None)) 284 self.Patch(instance, "_GetElapsedTime", return_value="fake_time") 285 self.Patch(AdbTools, "IsAdbConnected", return_value=False) 286 remote_instance = instance.RemoteInstance(self.GCE_INSTANCE) 287 result_summary = (" name: fake_ins_name\n " 288 " IP: 1.1.1.1\n " 289 " create time: fake_create_time\n " 290 " elapse time: fake_time\n " 291 " status: fake_status\n " 292 " avd type: fake_type\n " 293 " display: None\n " 294 " vnc: 127.0.0.1:None\n " 295 " zone: fake_zone\n " 296 " adb serial: disconnected") 297 self.assertEqual(remote_instance.Summary(), result_summary) 298 299 def testGetZoneName(self): 300 """Test GetZoneName.""" 301 zone_info = "v1/projects/project/zones/us-central1-c" 302 expected_result = "us-central1-c" 303 self.assertEqual(instance.RemoteInstance._GetZoneName(zone_info), 304 expected_result) 305 # Test can't get zone name from zone info. 306 zone_info = "v1/projects/project/us-central1-c" 307 self.assertEqual(instance.RemoteInstance._GetZoneName(zone_info), None) 308 309 310if __name__ == "__main__": 311 unittest.main() 312