blob: abb6a946d4293ff6a43ead020e3d9714a13f1e44 [file] [log] [blame]
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for lab_cli."""
import atexit
import copy
import datetime
import json
import os
import shutil
import tempfile
from unittest import mock
from absl.testing import absltest
from absl.testing import parameterized
from tradefed_cluster.configs import lab_config
from multitest_transport.cli import lab_cli
from multitest_transport.cli import unittest_util
_PROJECT = 'aproject'
_SERVICE_ACCOUNT_EMAIL = 'as@gsa.com'
_KEY_CREATE_TIME = '2021-04-15T23:13:34Z'
_SERVICE_ACCOUNT_KEY_ID = 'akey_id'
_SERVICE_ACCOUNT_KEY = {
'type': 'service_account',
'project_id': _PROJECT,
'private_key_id': _SERVICE_ACCOUNT_KEY_ID,
'private_key': 'aprivate_key',
'client_email': _SERVICE_ACCOUNT_EMAIL,
'client_id': '1234',
'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
'token_uri': 'https://oauth2.googleapis.com/token',
'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
'client_x509_cert_url': 'url'
}
_SERVICE_ACCOUNT_KEY_INFO = {
'name': (f'projects/{_PROJECT}/serviceAccounts/'
f'{_SERVICE_ACCOUNT_EMAIL}/keys/{_SERVICE_ACCOUNT_KEY_ID}'),
'validAfterTime': _KEY_CREATE_TIME,
'validBeforeTime': '2021-07-14T23:13:34Z',
'keyAlgorithm': 'KEY_ALG_RSA_2048',
'keyOrigin': 'GOOGLE_PROVIDED',
'keyType': 'USER_MANAGED'
}
class LabCliTest(parameterized.TestCase):
def setUp(self):
super(LabCliTest, self).setUp()
self.arg_parser = lab_cli.CreateParser()
self.tmp_root = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.tmp_root)
super(LabCliTest, self).tearDown()
def testCreateParser(self):
args = self.arg_parser.parse_args(['version'])
self.assertEqual('version', args.action)
def testCreateParser_withLoggingArgs(self):
args = self.arg_parser.parse_args([
'-v', '--log_file', 'file.log', 'version'])
self.assertEqual('version', args.action)
self.assertTrue(args.verbose)
self.assertEqual('file.log', args.log_file)
@mock.patch.object(tempfile, 'mkdtemp')
def testSetupMTTBinary(self, mock_create_temp):
tmp_folder = os.path.join(self.tmp_root, 'mtt_lab_extracted')
os.mkdir(tmp_folder)
mock_create_temp.return_value = tmp_folder
mtt_lab_path = os.path.join(self.tmp_root, 'mtt_lab')
unittest_util.CreateZipFile(
mtt_lab_path,
[unittest_util.File(
filename='mtt_binary', content='this is a mtt binary')])
args = mock.MagicMock(cli_path=mtt_lab_path)
host = mock.MagicMock(
context=mock.MagicMock(user='testuser'))
lab_cli._SetupMTTBinary(args, host)
host.context.assert_has_calls([
mock.call.CopyFile(
os.path.join(tmp_folder, 'mtt_binary'),
'/tmp/testuser/mtt'),
mock.call.Run(['chmod', '+x', '/tmp/testuser/mtt'])])
self.assertEqual('Setting up MTT Binary', host.execution_state)
@mock.patch.object(tempfile, 'NamedTemporaryFile')
def testSetupHostConfig(self, mock_make_tmpfile):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/config.yaml'
mock_make_tmpfile.return_value = mock_tmpfile
host = mock.MagicMock(context=mock.MagicMock(user='testuser'))
lab_cli._SetupHostConfig(host)
host.config.Save.assert_called_once_with('/local/config.yaml')
host.context.CopyFile.assert_called_once_with(
'/local/config.yaml', '/tmp/testuser/mtt_host_config.yaml')
mock_tmpfile.close.assert_called_once_with()
self.assertEqual('Setting up host config', host.execution_state)
@parameterized.named_parameters(
('$ mtt_lab start --ask_sudo_password --sudo_user user',
True, 'user', True),
('$ mtt_lab start --sudo_user user', False, 'user', True),
('$ mtt_lab start --ask_sudo_password', True, None, True),
('$ mtt_lab start', False, None, False))
@mock.patch.object(lab_cli, '_SetupMTTBinary')
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(lab_config.HostConfig, 'Save')
def testStart(
self, ask_sudo_password, sudo_user, use_sudo, mock_save,
mock_make_tmpfile, mock_setup):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/config.yaml'
mock_make_tmpfile.return_value = mock_tmpfile
args = mock.MagicMock(
service_account_json_key_path='/path/to/keyfile.json',
ask_sudo_password=ask_sudo_password,
sudo_user=sudo_user,
verbose=False,
very_verbose=False)
host = mock.MagicMock(
context=mock.MagicMock(user='testuser'),
config=lab_config.CreateHostConfig(
service_account_json_key_path='/path/to/keyfile.json'))
lab_cli.Start(args, host)
host.context.assert_has_calls([
mock.call.CopyFile('/path/to/keyfile.json',
'/tmp/testuser/keyfile/key.json'),
mock.call.CopyFile('/local/config.yaml',
'/tmp/testuser/mtt_host_config.yaml'),
mock.call.Run(['/tmp/testuser/mtt', '--no_check_update', 'start',
'/tmp/testuser/mtt_host_config.yaml'],
sudo=use_sudo)])
self.assertEqual('/tmp/testuser/keyfile/key.json',
host.config.service_account_json_key_path)
mock_save.assert_called_once_with('/local/config.yaml')
mock_setup.assert_called_once_with(args, host)
mock_tmpfile.close.assert_called_once_with()
self.assertEqual('Running start', host.execution_state)
@mock.patch.object(lab_cli, '_SetupMTTBinary')
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(lab_config.HostConfig, 'Save')
def testStart_noServiceAccountKey(
self, mock_save, mock_make_tmpfile, mock_setup):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/config.yaml'
mock_make_tmpfile.return_value = mock_tmpfile
args = mock.MagicMock(
service_account_json_key_path=None,
sudo_user=None,
ask_sudo_password=False,
verbose=False,
very_verbose=False)
host = mock.MagicMock(
context=mock.MagicMock(user='testuser'),
config=lab_config.CreateHostConfig(
service_account_json_key_path=None))
lab_cli.Start(args, host)
host.context.assert_has_calls([
mock.call.CopyFile(
'/local/config.yaml', '/tmp/testuser/mtt_host_config.yaml'),
mock.call.Run(['/tmp/testuser/mtt', '--no_check_update', 'start',
'/tmp/testuser/mtt_host_config.yaml'], sudo=False)])
mock_save.assert_called_once_with('/local/config.yaml')
mock_setup.assert_called_once_with(args, host)
mock_tmpfile.close.assert_called_once_with()
@parameterized.named_parameters(
('$ mtt_lab update --ask_sudo_password --sudo_user user',
True, 'user', True),
('$ mtt_lab update --sudo_user user', False, 'user', True),
('$ mtt_lab update --ask_sudo_password', True, None, True),
('$ mtt_lab update', False, None, False))
@mock.patch.object(lab_cli, '_SetupMTTBinary')
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(lab_config.HostConfig, 'Save')
def testUpdate(
self, ask_sudo_password, sudo_user, use_sudo, mock_save,
mock_make_tmpfile, mock_setup):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/config.yaml'
mock_make_tmpfile.return_value = mock_tmpfile
args = mock.MagicMock(
service_account_json_key_path='/path/to/keyfile.json',
ask_sudo_password=ask_sudo_password,
sudo_user=sudo_user,
verbose=False,
very_verbose=False)
host = mock.MagicMock(
context=mock.MagicMock(user='testuser'),
config=lab_config.CreateHostConfig(
service_account_json_key_path='/path/to/keyfile.json'))
lab_cli.Update(args, host)
host.context.assert_has_calls([
mock.call.CopyFile('/path/to/keyfile.json',
'/tmp/testuser/keyfile/key.json'),
mock.call.CopyFile('/local/config.yaml',
'/tmp/testuser/mtt_host_config.yaml'),
mock.call.Run(['/tmp/testuser/mtt', '--no_check_update', 'update',
'/tmp/testuser/mtt_host_config.yaml'],
sudo=use_sudo)])
self.assertEqual('/tmp/testuser/keyfile/key.json',
host.config.service_account_json_key_path)
mock_save.assert_called_once_with('/local/config.yaml')
mock_setup.assert_called_once_with(args, host)
mock_tmpfile.close.assert_called_once_with()
self.assertEqual('Running update', host.execution_state)
@parameterized.named_parameters(
('$ mtt_lab restart --ask_sudo_password --sudo_user user',
True, 'user', True),
('$ mtt_lab restart --sudo_user user', False, 'user', True),
('$ mtt_lab restart --ask_sudo_password', True, None, True),
('$ mtt_lab restart', False, None, False))
@mock.patch.object(lab_cli, '_SetupMTTBinary')
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(lab_config.HostConfig, 'Save')
def testRestart(
self, ask_sudo_password, sudo_user, use_sudo, mock_save,
mock_make_tmpfile, mock_setup):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/config.yaml'
mock_make_tmpfile.return_value = mock_tmpfile
args = mock.MagicMock(
service_account_json_key_path='/path/to/keyfile.json',
ask_sudo_password=ask_sudo_password,
sudo_user=sudo_user,
verbose=False,
very_verbose=False)
host = mock.MagicMock(
context=mock.MagicMock(user='testuser'),
config=lab_config.CreateHostConfig(
service_account_json_key_path='/path/to/keyfile.json'))
lab_cli.Restart(args, host)
host.context.assert_has_calls([
mock.call.CopyFile('/path/to/keyfile.json',
'/tmp/testuser/keyfile/key.json'),
mock.call.CopyFile('/local/config.yaml',
'/tmp/testuser/mtt_host_config.yaml'),
mock.call.Run(['/tmp/testuser/mtt', '--no_check_update', 'restart',
'/tmp/testuser/mtt_host_config.yaml'],
sudo=use_sudo)])
self.assertEqual('/tmp/testuser/keyfile/key.json',
host.config.service_account_json_key_path)
mock_save.assert_called_once_with('/local/config.yaml')
mock_setup.assert_called_once_with(args, host)
mock_tmpfile.close.assert_called_once_with()
self.assertEqual('Running restart', host.execution_state)
@parameterized.named_parameters(
('$ mtt_lab stop --ask_sudo_password --sudo_user user',
True, 'user', True),
('$ mtt_lab stop --sudo_user user', False, 'user', True),
('$ mtt_lab stop --ask_sudo_password', True, None, True),
('$ mtt_lab stop', False, None, False))
@mock.patch.object(lab_cli, '_SetupMTTBinary')
def testStop(self, ask_sudo_password, sudo_user, use_sudo, mock_setup):
args = mock.MagicMock(
ask_sudo_password=ask_sudo_password,
sudo_user=sudo_user,
verbose=False,
very_verbose=False)
host = mock.MagicMock(context=mock.MagicMock(user='testuser'))
lab_cli.Stop(args, host)
host.context.Run.assert_called_once_with(
['/tmp/testuser/mtt', '--no_check_update', 'stop'],
sudo=use_sudo)
mock_setup.assert_called_once_with(args, host)
self.assertEqual('Running stop', host.execution_state)
@parameterized.named_parameters(
('run_cmd_with_sudo_pwd_and_user', True, 'user', True),
('run_cmd_with_sudo_user', False, 'user', True),
('run_cmd_with_sudo_pwd', True, 'user', True),
('run_cmd_without_sudo', False, None, False))
def testRunCmd(self, ask_sudo_password, sudo_user, use_sudo):
args = mock.MagicMock(
ask_sudo_password=ask_sudo_password,
sudo_user=sudo_user,
cmd='run a command line')
host = mock.MagicMock()
lab_cli.RunCmd(args, host)
host.context.Run.assert_called_once_with(
['run', 'a', 'command', 'line'], sudo=use_sudo)
self.assertEqual('Running cmd', host.execution_state)
def testCreateLabCommandArgParser(self):
parser = lab_cli._CreateLabCommandArgParser()
args = parser.parse_args(['lab_config.yaml'])
self.assertEqual('lab_config.yaml', args.lab_config_path)
self.assertEmpty(args.hosts_or_clusters)
def testCreateLabCommandArgParser_withHostOrCluster(self):
parser = lab_cli._CreateLabCommandArgParser()
args = parser.parse_args(['lab_config.yaml', 'host1', 'cluster1'])
self.assertEqual('lab_config.yaml', args.lab_config_path)
self.assertEqual(['host1', 'cluster1'], args.hosts_or_clusters)
@parameterized.named_parameters(
('no_verbose', [], ['--no_check_update']),
('verbose', ['-v'], ['-v', '--no_check_update']),
('very_verbose', ['-vv'], ['-vv', '--no_check_update']),
('both', ['-v', '-vv'], ['-vv', '--no_check_update']))
def testBuildBaseMTTCmd(self, verbose_flag, res_flag):
parser = lab_cli.cli_util.CreateLoggingArgParser()
args = parser.parse_args(verbose_flag)
host = mock.MagicMock(context=mock.MagicMock(user='testuser'))
cmd = lab_cli._BuildBaseMTTCmd(args, host)
self.assertEqual(
['/tmp/testuser/mtt'] + res_flag,
cmd)
def testSetupServiceAccountKey(self):
args = mock.MagicMock(service_account_json_key_path=None)
host = mock.MagicMock(
context=mock.MagicMock(user='testuser'),
config=lab_config.CreateHostConfig(
service_account_json_key_path='/path/to/keyfile.json'))
lab_cli._SetupServiceAccountKey(args, host)
host.context.CopyFile.assert_called_once_with(
'/path/to/keyfile.json', '/tmp/testuser/keyfile/key.json')
self.assertEqual('/tmp/testuser/keyfile/key.json',
host.config.service_account_json_key_path)
self.assertEqual('Setting up service account key', host.execution_state)
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(lab_cli.google_auth_util, 'CreateKey')
@mock.patch.object(lab_cli.google_auth_util, 'UpdateSecret')
@mock.patch.object(lab_cli.google_auth_util, 'GetServiceAccountKeyInfo')
@mock.patch.object(lab_cli.google_auth_util, 'GetSecret')
@mock.patch.object(lab_cli.google_auth_util, 'CanCreateKey')
@mock.patch.object(lab_cli.google_auth_util, 'CanUpdateSecret')
@mock.patch.object(lab_cli.google_auth_util, 'DisableSecretVersions')
@mock.patch.object(atexit, 'register')
def testGetServiceAccountKeyFilePath(
self, mock_register, mock_disable_versions, mock_can_update_secret,
mock_can_create_key, mock_get_secret, mock_get_key_info,
mock_update_secret, mock_create_key, mock_make_tmpfile):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/sa_key.json'
mock_make_tmpfile.return_value = mock_tmpfile
mock_get_secret.return_value = json.dumps(_SERVICE_ACCOUNT_KEY).encode()
key_info = copy.deepcopy(_SERVICE_ACCOUNT_KEY_INFO)
key_info['validAfterTime'] = (
datetime.datetime.now(tz=datetime.timezone.utc)
- datetime.timedelta(days=3)).isoformat()
mock_get_key_info.return_value = key_info
lab_cli._GetServiceAccountKeyFilePath('secret_project', 'sa_key')
mock_get_key_info.assert_called_once_with(
_SERVICE_ACCOUNT_EMAIL, _SERVICE_ACCOUNT_KEY_ID)
self.assertTrue(mock_can_create_key.called)
self.assertTrue(mock_can_update_secret.called)
self.assertFalse(mock_create_key.called)
self.assertFalse(mock_update_secret.called)
self.assertFalse(mock_disable_versions.called)
mock_tmpfile.write.assert_called_once_with(
json.dumps(_SERVICE_ACCOUNT_KEY).encode())
mock_tmpfile.flush.assert_called_once_with()
mock_get_secret.assert_called_once_with('secret_project', 'sa_key')
mock_register.assert_called_once_with(mock_tmpfile.close)
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(lab_cli.google_auth_util, 'CreateKey')
@mock.patch.object(lab_cli.google_auth_util, 'UpdateSecret')
@mock.patch.object(lab_cli.google_auth_util, 'GetServiceAccountKeyInfo')
@mock.patch.object(lab_cli.google_auth_util, 'GetSecret')
@mock.patch.object(lab_cli.google_auth_util, 'CanCreateKey')
@mock.patch.object(lab_cli.google_auth_util, 'CanUpdateSecret')
@mock.patch.object(lab_cli.google_auth_util, 'DisableSecretVersions')
@mock.patch.object(atexit, 'register')
def testGetServiceAccountKeyFilePath_renew(
self, mock_register, mock_disable_versions, mock_can_update_secret,
mock_can_create_key, mock_get_secret, mock_get_key_info,
mock_update_secret, mock_create_key, mock_make_tmpfile):
mock_tmpfile = mock.MagicMock()
mock_tmpfile.name = '/local/sa_key.json'
mock_make_tmpfile.return_value = mock_tmpfile
mock_get_secret.return_value = json.dumps(_SERVICE_ACCOUNT_KEY).encode()
key_info = copy.deepcopy(_SERVICE_ACCOUNT_KEY_INFO)
key_info['validAfterTime'] = (
datetime.datetime.now(tz=datetime.timezone.utc)
- datetime.timedelta(days=20)).isoformat()
mock_get_key_info.return_value = key_info
mock_can_create_key.return_value = True
mock_can_update_secret.return_value = True
new_key = copy.deepcopy(_SERVICE_ACCOUNT_KEY)
new_key['private_key_id'] = 'new_key_id'
mock_create_key.return_value = new_key
mock_update_secret.return_value = 'new_version'
lab_cli._GetServiceAccountKeyFilePath('secret_project', 'sa_key')
mock_get_key_info.assert_called_once_with(
_SERVICE_ACCOUNT_EMAIL, _SERVICE_ACCOUNT_KEY_ID)
mock_can_update_secret.assert_called_once_with('secret_project', 'sa_key')
mock_can_create_key.assert_called_once_with(_SERVICE_ACCOUNT_EMAIL)
mock_create_key.assert_called_once_with(_SERVICE_ACCOUNT_EMAIL)
mock_update_secret.assert_called_once_with(
'secret_project', 'sa_key', json.dumps(new_key).encode())
mock_disable_versions.assert_called_once_with(
'secret_project', 'sa_key', exclude_versions=['new_version'],
days_before=28)
mock_tmpfile.write.assert_called_once_with(json.dumps(new_key).encode())
mock_tmpfile.flush.assert_called_once_with()
mock_get_secret.assert_called_once_with('secret_project', 'sa_key')
mock_register.assert_called_once_with(mock_tmpfile.close)
@mock.patch.object(lab_cli.google_auth_util, 'GetServiceAccountKeyInfo')
def testShouldRenewServiceAccountKey_noNeedToRenew(self, mock_get_key_info):
key_info = copy.deepcopy(_SERVICE_ACCOUNT_KEY_INFO)
key_info['validAfterTime'] = (
datetime.datetime.now(tz=datetime.timezone.utc)
- datetime.timedelta(days=3)).isoformat()
mock_get_key_info.return_value = key_info
shoud_renew = lab_cli._ShouldRenewServiceAccountKey(_SERVICE_ACCOUNT_KEY)
self.assertFalse(shoud_renew)
mock_get_key_info.assert_called_once_with(
_SERVICE_ACCOUNT_EMAIL, _SERVICE_ACCOUNT_KEY_ID)
@mock.patch.object(lab_cli.google_auth_util, 'GetServiceAccountKeyInfo')
def testShouldRenewServiceAccountKey_shouldRenew(self, mock_get_key_info):
key_info = copy.deepcopy(_SERVICE_ACCOUNT_KEY_INFO)
key_info['validAfterTime'] = (
datetime.datetime.now(tz=datetime.timezone.utc)
- datetime.timedelta(days=20)).isoformat()
mock_get_key_info.return_value = key_info
shoud_renew = lab_cli._ShouldRenewServiceAccountKey(_SERVICE_ACCOUNT_KEY)
self.assertTrue(shoud_renew)
mock_get_key_info.assert_called_once_with(
_SERVICE_ACCOUNT_EMAIL, _SERVICE_ACCOUNT_KEY_ID)
if __name__ == '__main__':
absltest.main()