blob: 579d9e44c724bc470a4f6a0fdcfe98a20b6de731 [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test the remote_access module."""
from __future__ import print_function
import os
from chromite.lib import cros_build_lib
from chromite.lib import cros_build_lib_unittest
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.lib import partial_mock
from chromite.lib import remote_access
# pylint: disable=protected-access
class TestNormalizePort(cros_test_lib.TestCase):
"""Verifies we normalize port."""
def testNormalizePortStrOK(self):
"""Tests that string will be converted to integer."""
self.assertEqual(remote_access.NormalizePort('123'), 123)
def testNormalizePortStrNotOK(self):
"""Tests that error is raised if port is string and str_ok=False."""
self.assertRaises(
ValueError, remote_access.NormalizePort, '123', str_ok=False)
def testNormalizePortOutOfRange(self):
"""Tests that error is rasied when port is out of range."""
self.assertRaises(ValueError, remote_access.NormalizePort, '-1')
self.assertRaises(ValueError, remote_access.NormalizePort, 99999)
class TestRemoveKnownHost(cros_test_lib.MockTempDirTestCase):
"""Verifies RemoveKnownHost() functionality."""
# ssh-keygen doesn't check for a valid hostname so use something that won't
# be in the user's known_hosts to avoid changing their file contents.
_HOST = '0.0.0.0.0.0'
_HOST_KEY = (
_HOST + ' ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjysPTaDAtRaxRaW1JjqzCHp2'
'88gvlUgtJxd2Jt/v63fkqZ5zzLLoeoAMwv0oYSRU82qhLimXpHxXRkrMC5nrpz5zJch+ktql'
'0rSRgo+dqc1GzmyOOAq5NkQsgBb3hefxMxCZRV8Dv0n7qaindZRxE8MnRJmVUoj8Wq8wryab'
'p+fUBkesBwaJhPXa4WBJeI5d+rO5tEBSNkvIp0USU6Ku3Ct0q2sZbOkY5g1VFAUYm4wyshCf'
'oWvU8ivMFp0pCezMISGstKpkIQApq2dLUb6EmeIgnhHzZXOn7doxIGD33JUfFmwNi0qfk3vV'
'6vKRVDEZD68+ix6gjKpicY5upA/9P\n')
def testRemoveKnownHostDefaultFile(self):
"""Tests RemoveKnownHost() on the default known_hosts file.
`ssh-keygen -R` on its own fails when run from within the chroot
since the default known_hosts is bind mounted.
"""
# It doesn't matter if known_hosts actually has this host in it or not,
# this test just makes sure the command doesn't fail. The default
# known_hosts file always exists in the chroot due to the bind mount.
remote_access.RemoveKnownHost(self._HOST)
def testRemoveKnownHostCustomFile(self):
"""Tests RemoveKnownHost() on a custom known_hosts file."""
path = os.path.join(self.tempdir, 'known_hosts')
osutils.WriteFile(path, self._HOST_KEY)
remote_access.RemoveKnownHost(self._HOST, known_hosts_path=path)
self.assertEqual(osutils.ReadFile(path), '')
def testRemoveKnownHostNonexistentFile(self):
"""Tests RemoveKnownHost() on a nonexistent known_hosts file."""
path = os.path.join(self.tempdir, 'known_hosts')
remote_access.RemoveKnownHost(self._HOST, known_hosts_path=path)
class TestCompileSSHConnectSettings(cros_test_lib.TestCase):
"""Verifies CompileSSHConnectSettings()."""
def testCustomSettingIncluded(self):
"""Tests that a custom setting will be included in the output."""
self.assertIn(
'-oNumberOfPasswordPrompts=100',
remote_access.CompileSSHConnectSettings(NumberOfPasswordPrompts=100))
def testNoneSettingOmitted(self):
"""Tests that a None value will omit a default setting from the output."""
self.assertIn('-oProtocol=2', remote_access.CompileSSHConnectSettings())
self.assertNotIn(
'-oProtocol=2',
remote_access.CompileSSHConnectSettings(Protocol=None))
class RemoteShMock(partial_mock.PartialCmdMock):
"""Mocks the RemoteSh function."""
TARGET = 'chromite.lib.remote_access.RemoteAccess'
ATTRS = ('RemoteSh',)
DEFAULT_ATTR = 'RemoteSh'
def RemoteSh(self, inst, cmd, *args, **kwargs):
"""Simulates a RemoteSh invocation.
Returns:
A CommandResult object with an additional member |rc_mock| to
enable examination of the underlying RunCommand() function call.
"""
result = self._results['RemoteSh'].LookupResult(
(cmd,), hook_args=(inst, cmd,) + args, hook_kwargs=kwargs)
# Run the real RemoteSh with RunCommand mocked out.
rc_mock = cros_build_lib_unittest.RunCommandMock()
rc_mock.AddCmdResult(
partial_mock.Ignore(), result.returncode, result.output, result.error)
with rc_mock:
result = self.backup['RemoteSh'](inst, cmd, *args, **kwargs)
result.rc_mock = rc_mock
return result
class RemoteDeviceMock(partial_mock.PartialMock):
"""Mocks the RemoteDevice function."""
TARGET = 'chromite.lib.remote_access.RemoteDevice'
ATTRS = ('Pingable',)
def Pingable(self, _):
return True
class RemoteAccessTest(cros_test_lib.MockTempDirTestCase):
"""Base class with RemoteSh mocked out for testing RemoteAccess."""
def setUp(self):
self.rsh_mock = self.StartPatcher(RemoteShMock())
self.host = remote_access.RemoteAccess('foon', self.tempdir)
class RemoteShTest(RemoteAccessTest):
"""Tests of basic RemoteSh functions"""
TEST_CMD = 'ls'
RETURN_CODE = 0
OUTPUT = 'witty'
ERROR = 'error'
def assertRemoteShRaises(self, **kwargs):
"""Asserts that RunCommandError is raised when running TEST_CMD."""
self.assertRaises(cros_build_lib.RunCommandError, self.host.RemoteSh,
self.TEST_CMD, **kwargs)
def assertRemoteShRaisesSSHConnectionError(self, **kwargs):
"""Asserts that SSHConnectionError is raised when running TEST_CMD."""
self.assertRaises(remote_access.SSHConnectionError, self.host.RemoteSh,
self.TEST_CMD, **kwargs)
def SetRemoteShResult(self, returncode=RETURN_CODE, output=OUTPUT,
error=ERROR):
"""Sets the RemoteSh command results."""
self.rsh_mock.AddCmdResult(self.TEST_CMD, returncode=returncode,
output=output, error=error)
def testNormal(self):
"""Test normal functionality."""
self.SetRemoteShResult()
result = self.host.RemoteSh(self.TEST_CMD)
self.assertEquals(result.returncode, self.RETURN_CODE)
self.assertEquals(result.output.strip(), self.OUTPUT)
self.assertEquals(result.error.strip(), self.ERROR)
def testRemoteCmdFailure(self):
"""Test failure in remote cmd."""
self.SetRemoteShResult(returncode=1)
self.assertRemoteShRaises()
self.assertRemoteShRaises(ssh_error_ok=True)
self.host.RemoteSh(self.TEST_CMD, error_code_ok=True)
self.host.RemoteSh(self.TEST_CMD, ssh_error_ok=True, error_code_ok=True)
def testSshFailure(self):
"""Test failure in ssh command."""
self.SetRemoteShResult(returncode=remote_access.SSH_ERROR_CODE)
self.assertRemoteShRaisesSSHConnectionError()
self.assertRemoteShRaisesSSHConnectionError(error_code_ok=True)
self.host.RemoteSh(self.TEST_CMD, ssh_error_ok=True)
self.host.RemoteSh(self.TEST_CMD, ssh_error_ok=True, error_code_ok=True)
def testEnvLcMessagesSet(self):
"""Test that LC_MESSAGES is set to 'C' for an SSH command."""
self.SetRemoteShResult()
result = self.host.RemoteSh(self.TEST_CMD)
rc_kwargs = result.rc_mock.call_args_list[-1][1]
self.assertEqual(rc_kwargs['extra_env']['LC_MESSAGES'], 'C')
def testEnvLcMessagesOverride(self):
"""Test that LC_MESSAGES is overridden to 'C' for an SSH command."""
self.SetRemoteShResult()
result = self.host.RemoteSh(self.TEST_CMD, extra_env={'LC_MESSAGES': 'fr'})
rc_kwargs = result.rc_mock.call_args_list[-1][1]
self.assertEqual(rc_kwargs['extra_env']['LC_MESSAGES'], 'C')
class CheckIfRebootedTest(RemoteAccessTest):
"""Tests of the _CheckIfRebooted function."""
_OLD_BOOT_ID = '1234'
_NEW_BOOT_ID = '5678'
def _SetCheckRebootResult(self, returncode=0, output='', error=''):
"""Sets the result object fields to mock a specific ssh command.
The command is the one used to fetch the boot ID (cat /proc/sys/...)
"""
self.rsh_mock.AddCmdResult(partial_mock.ListRegex('/proc/sys/.*'),
returncode=returncode,
output=output, error=error)
def testSuccess(self):
"""Test the case of successful reboot."""
self._SetCheckRebootResult(returncode=0, output=self._NEW_BOOT_ID)
self.assertTrue(self.host._CheckIfRebooted(self._OLD_BOOT_ID))
def testFailure(self):
"""Test case of failed reboot (boot ID did not change)."""
self._SetCheckRebootResult(0, output=self._OLD_BOOT_ID)
self.assertFalse(self.host._CheckIfRebooted(self._OLD_BOOT_ID))
def testSshFailure(self):
"""Test case of reboot pending (ssh failed)."""
self._SetCheckRebootResult(returncode=remote_access.SSH_ERROR_CODE)
self.assertFalse(self.host._CheckIfRebooted(self._OLD_BOOT_ID))
def testInvalidErrorCode(self):
"""Test case of bad error code returned."""
self._SetCheckRebootResult(returncode=2)
self.assertRaises(Exception,
lambda: self.host._CheckIfRebooted(self._OLD_BOOT_ID))
class RemoteDeviceTest(cros_test_lib.MockTestCase):
"""Tests for RemoteDevice class."""
def setUp(self):
self.rsh_mock = self.StartPatcher(RemoteShMock())
self.pingable_mock = self.PatchObject(
remote_access.RemoteDevice, 'Pingable', return_value=True)
def _SetupRemoteTempDir(self):
"""Mock out the calls needed for a remote tempdir."""
self.rsh_mock.AddCmdResult(partial_mock.In('mktemp'))
self.rsh_mock.AddCmdResult(partial_mock.In('rm'))
def testCommands(self):
"""Tests simple RunCommand() and BaseRunCommand() usage."""
command = ['echo', 'foo']
expected_output = 'foo'
self.rsh_mock.AddCmdResult(command, output=expected_output)
self._SetupRemoteTempDir()
with remote_access.RemoteDeviceHandler('1.1.1.1') as device:
self.assertEqual(expected_output,
device.RunCommand(['echo', 'foo']).output)
self.assertEqual(expected_output,
device.BaseRunCommand(['echo', 'foo']).output)
def testRunCommandShortCmdline(self):
"""Verify short command lines execute env settings directly."""
with remote_access.RemoteDeviceHandler('1.1.1.1') as device:
self.PatchObject(remote_access.RemoteDevice, 'CopyToWorkDir',
side_effect=Exception('should not be copying files'))
self.rsh_mock.AddCmdResult(partial_mock.In('runit'))
device.RunCommand(['runit'], extra_env={'VAR': 'val'})
def testRunCommandLongCmdline(self):
"""Verify long command lines execute env settings via script."""
with remote_access.RemoteDeviceHandler('1.1.1.1') as device:
self._SetupRemoteTempDir()
m = self.PatchObject(remote_access.RemoteDevice, 'CopyToWorkDir')
self.rsh_mock.AddCmdResult(partial_mock.In('runit'))
device.RunCommand(['runit'], extra_env={'VAR': 'v' * 1024 * 1024})
# We'll assume that the test passed when it tries to copy a file to the
# remote side (the shell script to run indirectly).
self.assertEqual(m.call_count, 1)
def testNoDeviceBaseDir(self):
"""Tests base_dir=None."""
command = ['echo', 'foo']
expected_output = 'foo'
self.rsh_mock.AddCmdResult(command, output=expected_output)
with remote_access.RemoteDeviceHandler('1.1.1.1', base_dir=None) as device:
self.assertEqual(expected_output,
device.BaseRunCommand(['echo', 'foo']).output)
def testDelayedRemoteDirs(self):
"""Tests the delayed creation of base_dir/work_dir."""
with remote_access.RemoteDeviceHandler('1.1.1.1', base_dir='/f') as device:
# Make sure we didn't talk to the remote yet.
self.assertEqual(self.rsh_mock.call_count, 0)
# The work dir will get automatically created when we use it.
self.rsh_mock.AddCmdResult(partial_mock.In('mktemp'))
_ = device.work_dir
self.assertEqual(self.rsh_mock.call_count, 1)
# Add a mock for the clean up logic.
self.rsh_mock.AddCmdResult(partial_mock.In('rm'))
self.assertEqual(self.rsh_mock.call_count, 2)