diff options
author | Antonio Terceiro <antonio.terceiro@linaro.org> | 2013-11-18 18:47:24 -0300 |
---|---|---|
committer | Antonio Terceiro <antonio.terceiro@linaro.org> | 2013-11-18 18:47:24 -0300 |
commit | 99ea8ab8541c3144c5e62cec8cdd327dc13c4388 (patch) | |
tree | 67c033d678e6ea7b0223d9608e5bf7dcd4752f20 /lava/device | |
parent | 49c36d653c19c018e473d7a1a27bdf5cb607c4be (diff) |
Imported Upstream version 0.8upstream/0.8
Diffstat (limited to 'lava/device')
-rw-r--r-- | lava/device/__init__.py | 97 | ||||
-rw-r--r-- | lava/device/commands.py | 122 | ||||
-rw-r--r-- | lava/device/templates.py | 82 | ||||
-rw-r--r-- | lava/device/tests/__init__.py | 0 | ||||
-rw-r--r-- | lava/device/tests/test_commands.py | 182 | ||||
-rw-r--r-- | lava/device/tests/test_device.py | 119 |
6 files changed, 602 insertions, 0 deletions
diff --git a/lava/device/__init__.py b/lava/device/__init__.py new file mode 100644 index 0000000..35fe181 --- /dev/null +++ b/lava/device/__init__.py @@ -0,0 +1,97 @@ +# Copyright (C) 2013 Linaro Limited +# +# Author: Milo Casagrande <milo.casagrande@linaro.org> +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>. + +"""Device class.""" + +import re + +from copy import deepcopy + +from lava.device.templates import ( + DEFAULT_TEMPLATE, + HOSTNAME_PARAMETER, + KNOWN_TEMPLATES, +) +from lava.helper.template import expand_template + + +def __re_compile(name): + """Creates a generic regex for the specified device name. + + :param name: The name of the device. + :return A Pattern object. + """ + return re.compile('^.*{0}.*'.format(name), re.I) + + +# Dictionary of know devices. +# Keys are the general device name taken from lava.device.templates, values +# are tuples of: a regex matcher to match the device, and the device associated +# template. +KNOWN_DEVICES = dict([(device, (__re_compile(device), template)) + for device, template in KNOWN_TEMPLATES.iteritems()]) + + +class Device(object): + + """A generic device.""" + + def __init__(self, data, hostname=None): + self.data = deepcopy(data) + self.hostname = hostname + + def write(self, conf_file): + """Writes the object to file. + + :param conf_file: The full path of the file where to write.""" + with open(conf_file, 'w') as write_file: + write_file.write(str(self)) + + def update(self, config): + """Updates the Device object values based on the provided config. + + :param config: A Config instance. + """ + # We should always have a hostname, since it defaults to the name + # given on the command line for the config file. + if self.hostname is not None: + # We do not ask the user again this parameter. + self.data[HOSTNAME_PARAMETER.id].value = self.hostname + self.data[HOSTNAME_PARAMETER.id].asked = True + + expand_template(self.data, config) + + def __str__(self): + string_list = [] + for key, value in self.data.iteritems(): + string_list.append("{0} = {1}\n".format(str(key), str(value))) + return "".join(string_list) + + +def get_known_device(name): + """Tries to match a device name with a known device type. + + :param name: The name of the device we want matched to a real device. + :return A Device instance. + """ + instance = Device(DEFAULT_TEMPLATE, hostname=name) + for _, (matcher, dev_template) in KNOWN_DEVICES.iteritems(): + if matcher.match(name): + instance = Device(dev_template, hostname=name) + break + return instance diff --git a/lava/device/commands.py b/lava/device/commands.py new file mode 100644 index 0000000..a8ce66d --- /dev/null +++ b/lava/device/commands.py @@ -0,0 +1,122 @@ +# Copyright (C) 2013 Linaro Limited +# +# Author: Milo Casagrande <milo.casagrande@linaro.org> +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>. + +""" +Device specific commands class. +""" + +import os +import sys + +from lava.device import get_known_device +from lava.helper.command import ( + BaseCommand, +) +from lava.helper.dispatcher import ( + get_device_file, + get_devices_path, +) +from lava.tool.command import CommandGroup +from lava.tool.errors import CommandError +from lava_tool.utils import ( + can_edit_file, + edit_file, +) + +DEVICE_FILE_SUFFIX = "conf" + + +class device(CommandGroup): + """LAVA devices handling.""" + + namespace = "lava.device.commands" + + +class add(BaseCommand): + """Adds a new device.""" + + @classmethod + def register_arguments(cls, parser): + super(add, cls).register_arguments(parser) + parser.add_argument("DEVICE", help="The name of the device to add.") + + def invoke(self): + real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX]) + + if get_device_file(real_file_name) is not None: + print >> sys.stdout, ("A device configuration file named '{0}' " + "already exists.".format(real_file_name)) + print >> sys.stdout, ("Use 'lava device config {0}' to edit " + "it.".format(self.args.DEVICE)) + sys.exit(-1) + + devices_path = get_devices_path() + device_conf_file = os.path.abspath(os.path.join(devices_path, + real_file_name)) + + device = get_known_device(self.args.DEVICE) + device.update(self.config) + device.write(device_conf_file) + + print >> sys.stdout, ("Created device file '{0}' in: {1}".format( + real_file_name, devices_path)) + edit_file(device_conf_file) + + +class remove(BaseCommand): + """Removes the specified device.""" + + @classmethod + def register_arguments(cls, parser): + super(remove, cls).register_arguments(parser) + parser.add_argument("DEVICE", + help="The name of the device to remove.") + + def invoke(self): + real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX]) + device_conf = get_device_file(real_file_name) + + if device_conf: + try: + os.remove(device_conf) + print >> sys.stdout, ("Device configuration file '{0}' " + "removed.".format(real_file_name)) + except OSError: + raise CommandError("Cannot remove file '{0}' at: {1}.".format( + real_file_name, os.path.dirname(device_conf))) + else: + print >> sys.stdout, ("No device configuration file '{0}' " + "found.".format(real_file_name)) + + +class config(BaseCommand): + """Opens the specified device config file.""" + @classmethod + def register_arguments(cls, parser): + super(config, cls).register_arguments(parser) + parser.add_argument("DEVICE", + help="The name of the device to edit.") + + def invoke(self): + real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX]) + device_conf = get_device_file(real_file_name) + + if device_conf and can_edit_file(device_conf): + edit_file(device_conf) + else: + raise CommandError("Cannot edit file '{0}'".format(real_file_name)) diff --git a/lava/device/templates.py b/lava/device/templates.py new file mode 100644 index 0000000..e260117 --- /dev/null +++ b/lava/device/templates.py @@ -0,0 +1,82 @@ +# Copyright (C) 2013 Linaro Limited +# +# Author: Milo Casagrande <milo.casagrande@linaro.org> +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>. + +""" +This is just a place where to store a template like dictionary that +will be used to serialize a Device object. +""" + +from copy import copy + +from lava.parameter import Parameter + +# The hostname parameter is always in the DEFAULT config section. +HOSTNAME_PARAMETER = Parameter("hostname") +DEVICE_TYPE_PARAMETER = Parameter("device_type", depends=HOSTNAME_PARAMETER) +CONNECTION_COMMAND_PARMAETER = Parameter("connection_command", + depends=DEVICE_TYPE_PARAMETER) + +DEFAULT_TEMPLATE = { + 'hostname': HOSTNAME_PARAMETER, + 'device_type': DEVICE_TYPE_PARAMETER, + 'connection_command': CONNECTION_COMMAND_PARMAETER, +} + +# Specialized copies of the parameters. +# We need this or we might end up asking the user twice the same parameter due +# to different object references when one Parameter depends on a "specialized" +# one, different from the defaults. +PANDA_DEVICE_TYPE = copy(DEVICE_TYPE_PARAMETER) +PANDA_DEVICE_TYPE.value = "panda" +PANDA_DEVICE_TYPE.asked = True + +PANDA_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER) +PANDA_CONNECTION_COMMAND.depends = PANDA_DEVICE_TYPE + +VEXPRESS_DEVICE_TYPE = copy(DEVICE_TYPE_PARAMETER) +VEXPRESS_DEVICE_TYPE.value = "vexpress" +VEXPRESS_DEVICE_TYPE.asked = True + +VEXPRESS_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER) +VEXPRESS_CONNECTION_COMMAND.depends = VEXPRESS_DEVICE_TYPE + +QEMU_DEVICE_TYPE = copy(DEVICE_TYPE_PARAMETER) +QEMU_DEVICE_TYPE.value = "qemu" +QEMU_DEVICE_TYPE.asked = True + +QEMU_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER) +QEMU_CONNECTION_COMMAND.depends = QEMU_DEVICE_TYPE + +# Dictionary with templates of known devices. +KNOWN_TEMPLATES = { + 'panda': { + 'hostname': HOSTNAME_PARAMETER, + 'device_type': PANDA_DEVICE_TYPE, + 'connection_command': PANDA_CONNECTION_COMMAND, + }, + 'vexpress': { + 'hostname': HOSTNAME_PARAMETER, + 'device_type': VEXPRESS_DEVICE_TYPE, + 'connection_command': VEXPRESS_CONNECTION_COMMAND, + }, + 'qemu': { + 'hostname': HOSTNAME_PARAMETER, + 'device_type': QEMU_DEVICE_TYPE, + 'connection_command': QEMU_CONNECTION_COMMAND, + } +} diff --git a/lava/device/tests/__init__.py b/lava/device/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lava/device/tests/__init__.py diff --git a/lava/device/tests/test_commands.py b/lava/device/tests/test_commands.py new file mode 100644 index 0000000..91b204f --- /dev/null +++ b/lava/device/tests/test_commands.py @@ -0,0 +1,182 @@ +# Copyright (C) 2013 Linaro Limited +# +# Author: Milo Casagrande <milo.casagrande@linaro.org> +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>. + +""" +lava.device.commands unit tests. +""" + +import os + +from mock import ( + MagicMock, + call, + patch, +) + +from lava.device.commands import ( + add, + config, + remove, +) +from lava.helper.tests.helper_test import HelperTest +from lava.tool.errors import CommandError + + +class AddCommandTest(HelperTest): + + def test_register_argument(self): + # Make sure that the parser add_argument is called and we have the + # correct argument. + add_command = add(self.parser, self.args) + add_command.register_arguments(self.parser) + name, args, kwargs = self.parser.method_calls[0] + self.assertIn("--non-interactive", args) + + name, args, kwargs = self.parser.method_calls[1] + self.assertIn("DEVICE", args) + + @patch("lava.device.commands.edit_file", create=True) + @patch("lava.device.Device.__str__") + @patch("lava.device.Device.update") + @patch("lava.device.commands.get_device_file") + @patch("lava.device.commands.get_devices_path") + def test_add_invoke_0(self, mocked_get_devices_path, + mocked_get_device_file, mocked_update, mocked_str, + mocked_edit_file): + # Tests invocation of the add command. Verifies that the conf file is + # written to disk. + mocked_get_devices_path.return_value = self.temp_dir + mocked_get_device_file.return_value = None + mocked_str.return_value = "" + + add_command = add(self.parser, self.args) + add_command.invoke() + + expected_path = os.path.join(self.temp_dir, + ".".join([self.device, "conf"])) + self.assertTrue(os.path.isfile(expected_path)) + + @patch("lava.device.commands.edit_file", create=True) + @patch("lava.device.commands.get_known_device") + @patch("lava.device.commands.get_devices_path") + @patch("lava.device.commands.sys.exit") + @patch("lava.device.commands.get_device_file") + def test_add_invoke_1(self, mocked_get_device_file, mocked_sys_exit, + mocked_get_devices_path, mocked_get_known_device, + mocked_edit_file): + mocked_get_devices_path.return_value = self.temp_dir + mocked_get_device_file.return_value = self.temp_file.name + + add_command = add(self.parser, self.args) + add_command.invoke() + + self.assertTrue(mocked_sys_exit.called) + + +class RemoveCommandTests(HelperTest): + + def test_register_argument(self): + # Make sure that the parser add_argument is called and we have the + # correct argument. + command = remove(self.parser, self.args) + command.register_arguments(self.parser) + name, args, kwargs = self.parser.method_calls[0] + self.assertIn("--non-interactive", args) + + name, args, kwargs = self.parser.method_calls[1] + self.assertIn("DEVICE", args) + + @patch("lava.device.commands.edit_file", create=True) + @patch("lava.device.Device.__str__", return_value="") + @patch("lava.device.Device.update") + @patch("lava.device.commands.get_device_file") + @patch("lava.device.commands.get_devices_path") + def test_remove_invoke(self, get_devices_path_mock, get_device_file_mock, + mocked_update, mocked_str, mocked_edit_file): + # Tests invocation of the remove command. Verifies that the conf file + # has been correctly removed. + # First we add a new conf file, then we remove it. + get_device_file_mock.return_value = None + get_devices_path_mock.return_value = self.temp_dir + + add_command = add(self.parser, self.args) + add_command.invoke() + + expected_path = os.path.join(self.temp_dir, + ".".join([self.device, "conf"])) + + # Set new values for the mocked function. + get_device_file_mock.return_value = expected_path + + remove_command = remove(self.parser, self.args) + remove_command.invoke() + + self.assertFalse(os.path.isfile(expected_path)) + + @patch("lava.device.commands.get_device_file", + new=MagicMock(return_value="/root")) + def test_remove_invoke_raises(self): + # Tests invocation of the remove command, with a non existent device + # configuration file. + remove_command = remove(self.parser, self.args) + self.assertRaises(CommandError, remove_command.invoke) + + +class ConfigCommanTests(HelperTest): + + def test_register_argument(self): + # Make sure that the parser add_argument is called and we have the + # correct argument. + command = config(self.parser, self.args) + command.register_arguments(self.parser) + name, args, kwargs = self.parser.method_calls[0] + self.assertIn("--non-interactive", args) + + name, args, kwargs = self.parser.method_calls[1] + self.assertIn("DEVICE", args) + + @patch("lava.device.commands.can_edit_file", create=True) + @patch("lava.device.commands.edit_file", create=True) + @patch("lava.device.commands.get_device_file") + def test_config_invoke_0(self, mocked_get_device_file, mocked_edit_file, + mocked_can_edit_file): + command = config(self.parser, self.args) + + mocked_can_edit_file.return_value = True + mocked_get_device_file.return_value = self.temp_file.name + command.invoke() + + self.assertTrue(mocked_edit_file.called) + self.assertEqual([call(self.temp_file.name)], + mocked_edit_file.call_args_list) + + @patch("lava.device.commands.get_device_file", + new=MagicMock(return_value=None)) + def test_config_invoke_raises_0(self): + # Tests invocation of the config command, with a non existent device + # configuration file. + config_command = config(self.parser, self.args) + self.assertRaises(CommandError, config_command.invoke) + + @patch("lava.device.commands.get_device_file", + new=MagicMock(return_value="/etc/password")) + def test_config_invoke_raises_1(self): + # Tests invocation of the config command, with a non writable file. + # Hopefully tests are not run as root. + config_command = config(self.parser, self.args) + self.assertRaises(CommandError, config_command.invoke) diff --git a/lava/device/tests/test_device.py b/lava/device/tests/test_device.py new file mode 100644 index 0000000..c8185f4 --- /dev/null +++ b/lava/device/tests/test_device.py @@ -0,0 +1,119 @@ +# Copyright (C) 2013 Linaro Limited +# +# Author: Milo Casagrande <milo.casagrande@linaro.org> +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>. + +""" +Device class unit tests. +""" + +from mock import patch + +from lava.config import Config +from lava.device import ( + Device, + get_known_device, +) +from lava.device.templates import ( + HOSTNAME_PARAMETER, + PANDA_DEVICE_TYPE, + PANDA_CONNECTION_COMMAND, +) +from lava.helper.tests.helper_test import HelperTest +from lava.parameter import Parameter + + +class DeviceTest(HelperTest): + + def test_get_known_device_panda_0(self): + # User creates a new device with a guessable name for a device. + instance = get_known_device('panda_new_01') + self.assertIsInstance(instance, Device) + self.assertEqual(instance.data['device_type'].value, 'panda') + + def test_get_known_device_panda_1(self): + # User creates a new device with a guessable name for a device. + # Name passed has capital letters. + instance = get_known_device('new_PanDa_02') + self.assertIsInstance(instance, Device) + self.assertEqual(instance.data['device_type'].value, 'panda') + + def test_get_known_device_vexpress_0(self): + # User creates a new device with a guessable name for a device. + # Name passed has capital letters. + instance = get_known_device('a_VexPress_Device') + self.assertIsInstance(instance, Device) + self.assertEqual(instance.data['device_type'].value, 'vexpress') + + def test_get_known_device_vexpress_1(self): + # User creates a new device with a guessable name for a device. + instance = get_known_device('another-vexpress') + self.assertIsInstance(instance, Device) + self.assertIsInstance(instance.data['device_type'], Parameter) + self.assertEqual(instance.data['device_type'].value, 'vexpress') + + @patch("lava.config.Config.save") + def test_device_update_1(self, patched_save): + # Tests that when calling update() on a Device, the template gets + # updated with the correct values from a Config instance. + hostname = "panda_device" + + config = Config() + config._config_file = self.temp_file.name + config.put_parameter(HOSTNAME_PARAMETER, hostname) + config.put_parameter(PANDA_DEVICE_TYPE, "panda") + config.put_parameter(PANDA_CONNECTION_COMMAND, "test") + + expected = { + "hostname": hostname, + "device_type": "panda", + "connection_command": "test" + } + + instance = get_known_device(hostname) + instance.update(config) + + self.assertEqual(expected, instance.data) + + @patch("lava.config.Config.save") + def test_device_write(self, mocked_save): + # User tries to create a new panda device. The conf file is written + # and contains the expected results. + hostname = "panda_device" + + config = Config() + config._config_file = self.temp_file.name + config.put_parameter(HOSTNAME_PARAMETER, hostname) + config.put_parameter(PANDA_DEVICE_TYPE, "panda") + config.put_parameter(PANDA_CONNECTION_COMMAND, "test") + + expected = { + "hostname": hostname, + "device_type": "panda", + "connection_command": "test" + } + + instance = get_known_device(hostname) + instance.update(config) + instance.write(self.temp_file.name) + + expected = ("hostname = panda_device\nconnection_command = test\n" + "device_type = panda\n") + + obtained = "" + with open(self.temp_file.name) as f: + obtained = f.read() + self.assertEqual(expected, obtained) |