1b6d2993fb77f771a886c41ace0850773f5498bedbarfab@chromium.org# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
20499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan# Use of this source code is governed by a BSD-style license that can be
30499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan# found in the LICENSE file.
40499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5b6d2993fb77f771a886c41ace0850773f5498bedbarfab@chromium.orgimport dbus, gobject, logging, os, stat
6b6d2993fb77f771a886c41ace0850773f5498bedbarfab@chromium.orgfrom dbus.mainloop.glib import DBusGMainLoop
70499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
8b6d2993fb77f771a886c41ace0850773f5498bedbarfab@chromium.orgimport common
90499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanfrom autotest_lib.client.bin import utils
100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanfrom autotest_lib.client.common_lib import autotemp, error
11b6d2993fb77f771a886c41ace0850773f5498bedbarfab@chromium.orgfrom mainloop import ExceptionForward
12b6d2993fb77f771a886c41ace0850773f5498bedbarfab@chromium.orgfrom mainloop import GenericTesterMainLoop
130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan"""This module contains several helper classes for writing tests to verify the
160499e53e22a0fe29789e038976e8ede50af51ca3Ben ChanCrosDisks DBus interface. In particular, the CrosDisksTester class can be used
170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanto derive functional tests that interact with the CrosDisks server over DBus.
180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan"""
190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass ExceptionSuppressor(object):
220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """A context manager class for suppressing certain types of exception.
230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    An instance of this class is expected to be used with the with statement
250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    and takes a set of exception classes at instantiation, which are types of
260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    exception to be suppressed (and logged) in the code block under the with
270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    statement.
280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    Example:
300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        with ExceptionSuppressor(OSError, IOError):
320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            # An exception, which is a sub-class of OSError or IOError, is
330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            # suppressed in the block code under the with statement.
340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """
350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, *args):
360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.__suppressed_exc_types = (args)
370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __enter__(self):
390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self
400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __exit__(self, exc_type, exc_value, traceback):
420499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if exc_type and issubclass(exc_type, self.__suppressed_exc_types):
430499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            try:
440499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                logging.exception('Suppressed exception: %s(%s)',
450499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                  exc_type, exc_value)
460499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            except Exception:
470499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                pass
480499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return True
490499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return False
500499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
510499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
520499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass DBusClient(object):
530499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """ A base class of a DBus proxy client to test a DBus server.
540499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
550499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    This class is expected to be used along with a GLib main loop and provides
560499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    some convenient functions for testing the DBus API exposed by a DBus server.
570499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """
580499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, main_loop, bus, bus_name, object_path):
590499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Initializes the instance.
600499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
610499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
620499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            main_loop: The GLib main loop.
630499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            bus: The bus where the DBus server is connected to.
640499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            bus_name: The bus name owned by the DBus server.
650499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            object_path: The object path of the DBus server.
660499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
670499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.__signal_content = {}
680499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.main_loop = main_loop
690499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.signal_timeout_in_seconds = 10
700499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Getting D-Bus proxy object on bus "%s" and path "%s"',
710499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      bus_name, object_path)
720499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.proxy_object = bus.get_object(bus_name, object_path)
730499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
740499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def clear_signal_content(self, signal_name):
750499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Clears the content of the signal.
760499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
770499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
780499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            signal_name: The name of the signal.
790499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
800499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if signal_name in self.__signal_content:
810499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self.__signal_content[signal_name] = None
820499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
830499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def get_signal_content(self, signal_name):
840499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Gets the content of a signal.
850499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
860499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
870499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            signal_name: The name of the signal.
880499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
890499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
900499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The content of a signal or None if the signal is not being handled.
910499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self.__signal_content.get(signal_name)
930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def handle_signal(self, interface, signal_name, argument_names=()):
950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Registers a signal handler to handle a given signal.
960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            interface: The DBus interface of the signal.
990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            signal_name: The name of the signal.
1000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            argument_names: A list of argument names that the signal contains.
1010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
1020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if signal_name in self.__signal_content:
1030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return
1040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.__signal_content[signal_name] = None
1060499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1070499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        def signal_handler(*args):
1080499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self.__signal_content[signal_name] = dict(zip(argument_names, args))
1090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Handling D-Bus signal "%s(%s)" on interface "%s"',
1110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      signal_name, ', '.join(argument_names), interface)
1120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.proxy_object.connect_to_signal(signal_name, signal_handler,
1130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                            interface)
1140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def wait_for_signal(self, signal_name):
11681904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """Waits for the reception of a signal.
11781904f12dbffa5c415db9ad3026eac59a516725fBen Chan
11881904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Args:
11981904f12dbffa5c415db9ad3026eac59a516725fBen Chan            signal_name: The name of the signal to wait for.
1200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
1220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The content of the signal.
1230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
1240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if signal_name not in self.__signal_content:
1250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return None
1260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        def check_signal_content():
1280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            context = self.main_loop.get_context()
1290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            while context.iteration(False):
1300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                pass
1310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return self.__signal_content[signal_name] is not None
1320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Waiting for D-Bus signal "%s"', signal_name)
1340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        utils.poll_for_condition(condition=check_signal_content,
1350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                 desc='%s signal' % signal_name,
1360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                 timeout=self.signal_timeout_in_seconds)
1370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        content = self.__signal_content[signal_name]
1380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Received D-Bus signal "%s(%s)"', signal_name, content)
1390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.__signal_content[signal_name] = None
1400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return content
1410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
14281904f12dbffa5c415db9ad3026eac59a516725fBen Chan    def expect_signal(self, signal_name, expected_content):
14381904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """Waits the the reception of a signal and verifies its content.
14481904f12dbffa5c415db9ad3026eac59a516725fBen Chan
14581904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Args:
14681904f12dbffa5c415db9ad3026eac59a516725fBen Chan            signal_name: The name of the signal to wait for.
14781904f12dbffa5c415db9ad3026eac59a516725fBen Chan            expected_content: The expected content of the signal, which can be
14881904f12dbffa5c415db9ad3026eac59a516725fBen Chan                              partially specified. Only specified fields are
14981904f12dbffa5c415db9ad3026eac59a516725fBen Chan                              compared between the actual and expected content.
15081904f12dbffa5c415db9ad3026eac59a516725fBen Chan
15181904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Returns:
15281904f12dbffa5c415db9ad3026eac59a516725fBen Chan            The actual content of the signal.
15381904f12dbffa5c415db9ad3026eac59a516725fBen Chan
15481904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Raises:
15581904f12dbffa5c415db9ad3026eac59a516725fBen Chan            error.TestFail: A test failure when there is a mismatch between the
15681904f12dbffa5c415db9ad3026eac59a516725fBen Chan                            actual and expected content of the signal.
15781904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """
15881904f12dbffa5c415db9ad3026eac59a516725fBen Chan        actual_content = self.wait_for_signal(signal_name)
15981904f12dbffa5c415db9ad3026eac59a516725fBen Chan        logging.debug("%s signal: expected=%s actual=%s",
16081904f12dbffa5c415db9ad3026eac59a516725fBen Chan                      signal_name, expected_content, actual_content)
16181904f12dbffa5c415db9ad3026eac59a516725fBen Chan        for argument, expected_value in expected_content.iteritems():
16281904f12dbffa5c415db9ad3026eac59a516725fBen Chan            if argument not in actual_content:
16381904f12dbffa5c415db9ad3026eac59a516725fBen Chan                raise error.TestFail(
16481904f12dbffa5c415db9ad3026eac59a516725fBen Chan                    ('%s signal missing "%s": expected=%s, actual=%s') %
16581904f12dbffa5c415db9ad3026eac59a516725fBen Chan                    (signal_name, argument, expected_content, actual_content))
16681904f12dbffa5c415db9ad3026eac59a516725fBen Chan
16781904f12dbffa5c415db9ad3026eac59a516725fBen Chan            if actual_content[argument] != expected_value:
16881904f12dbffa5c415db9ad3026eac59a516725fBen Chan                raise error.TestFail(
16981904f12dbffa5c415db9ad3026eac59a516725fBen Chan                    ('%s signal not matched on "%s": expected=%s, actual=%s') %
17081904f12dbffa5c415db9ad3026eac59a516725fBen Chan                    (signal_name, argument, expected_content, actual_content))
17181904f12dbffa5c415db9ad3026eac59a516725fBen Chan        return actual_content
17281904f12dbffa5c415db9ad3026eac59a516725fBen Chan
1730499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1740499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass CrosDisksClient(DBusClient):
1750499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """A DBus proxy client for testing the CrosDisks DBus server.
1760499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """
1770499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1780499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    CROS_DISKS_BUS_NAME = 'org.chromium.CrosDisks'
1790499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    CROS_DISKS_INTERFACE = 'org.chromium.CrosDisks'
1800499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    CROS_DISKS_OBJECT_PATH = '/org/chromium/CrosDisks'
1810499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
18281904f12dbffa5c415db9ad3026eac59a516725fBen Chan    FORMAT_COMPLETED_SIGNAL = 'FormatCompleted'
18381904f12dbffa5c415db9ad3026eac59a516725fBen Chan    FORMAT_COMPLETED_SIGNAL_ARGUMENTS = (
18481904f12dbffa5c415db9ad3026eac59a516725fBen Chan        'status', 'path'
18581904f12dbffa5c415db9ad3026eac59a516725fBen Chan    )
1860499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    MOUNT_COMPLETED_SIGNAL = 'MountCompleted'
1870499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    MOUNT_COMPLETED_SIGNAL_ARGUMENTS = (
1880499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        'status', 'source_path', 'source_type', 'mount_path'
1890499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    )
1900499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1910499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, main_loop, bus):
1920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Initializes the instance.
1930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
1940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
1950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            main_loop: The GLib main loop.
1960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            bus: The bus where the DBus server is connected to.
1970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
1980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        super(CrosDisksClient, self).__init__(main_loop, bus,
1990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                              self.CROS_DISKS_BUS_NAME,
2000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                              self.CROS_DISKS_OBJECT_PATH)
2010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.interface = dbus.Interface(self.proxy_object,
2020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                        self.CROS_DISKS_INTERFACE)
2030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.properties = dbus.Interface(self.proxy_object,
2040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                         self.DBUS_PROPERTIES_INTERFACE)
2050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.handle_signal(self.CROS_DISKS_INTERFACE,
20681904f12dbffa5c415db9ad3026eac59a516725fBen Chan                           self.FORMAT_COMPLETED_SIGNAL,
20781904f12dbffa5c415db9ad3026eac59a516725fBen Chan                           self.FORMAT_COMPLETED_SIGNAL_ARGUMENTS)
20881904f12dbffa5c415db9ad3026eac59a516725fBen Chan        self.handle_signal(self.CROS_DISKS_INTERFACE,
2090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                           self.MOUNT_COMPLETED_SIGNAL,
2100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                           self.MOUNT_COMPLETED_SIGNAL_ARGUMENTS)
2110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def is_alive(self):
2130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Invokes the CrosDisks IsAlive method.
2140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
2160499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            True if the CrosDisks server is alive or False otherwise.
2170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
2180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self.interface.IsAlive()
2190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def enumerate_auto_mountable_devices(self):
2210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Invokes the CrosDisks EnumerateAutoMountableDevices method.
2220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
2240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            A list of sysfs paths of devices that are auto-mountable by
2250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            CrosDisks.
2260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
2270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self.interface.EnumerateAutoMountableDevices()
2280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def enumerate_devices(self):
2300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Invokes the CrosDisks EnumerateMountableDevices method.
2310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
2330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            A list of sysfs paths of devices that are recognized by
2340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            CrosDisks.
2350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
2360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self.interface.EnumerateDevices()
2370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def get_device_properties(self, path):
2390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Invokes the CrosDisks GetDeviceProperties method.
2400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
2420499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            path: The device path.
2430499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2440499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
2450499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The properties of the device in a dictionary.
2460499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
2470499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self.interface.GetDeviceProperties(path)
2480499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
24981904f12dbffa5c415db9ad3026eac59a516725fBen Chan    def format(self, path, filesystem_type=None, options=None):
25081904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """Invokes the CrosDisks Format method.
25181904f12dbffa5c415db9ad3026eac59a516725fBen Chan
25281904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Args:
25381904f12dbffa5c415db9ad3026eac59a516725fBen Chan            path: The device path to format.
25481904f12dbffa5c415db9ad3026eac59a516725fBen Chan            filesystem_type: The filesystem type used for formatting the device.
25581904f12dbffa5c415db9ad3026eac59a516725fBen Chan            options: A list of options used for formatting the device.
25681904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """
25781904f12dbffa5c415db9ad3026eac59a516725fBen Chan        if filesystem_type is None:
25881904f12dbffa5c415db9ad3026eac59a516725fBen Chan            filesystem_type = ''
25981904f12dbffa5c415db9ad3026eac59a516725fBen Chan        if options is None:
26081904f12dbffa5c415db9ad3026eac59a516725fBen Chan            options = []
26181904f12dbffa5c415db9ad3026eac59a516725fBen Chan        self.clear_signal_content(self.FORMAT_COMPLETED_SIGNAL)
26281904f12dbffa5c415db9ad3026eac59a516725fBen Chan        self.interface.Format(path, filesystem_type, options)
26381904f12dbffa5c415db9ad3026eac59a516725fBen Chan
26481904f12dbffa5c415db9ad3026eac59a516725fBen Chan    def wait_for_format_completion(self):
26581904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """Waits for the CrosDisks FormatCompleted signal.
26681904f12dbffa5c415db9ad3026eac59a516725fBen Chan
26781904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Returns:
26881904f12dbffa5c415db9ad3026eac59a516725fBen Chan            The content of the FormatCompleted signal.
26981904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """
27081904f12dbffa5c415db9ad3026eac59a516725fBen Chan        return self.wait_for_signal(self.FORMAT_COMPLETED_SIGNAL)
27181904f12dbffa5c415db9ad3026eac59a516725fBen Chan
27281904f12dbffa5c415db9ad3026eac59a516725fBen Chan    def expect_format_completion(self, expected_content):
27381904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """Waits and verifies for the CrosDisks FormatCompleted signal.
27481904f12dbffa5c415db9ad3026eac59a516725fBen Chan
27581904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Args:
27681904f12dbffa5c415db9ad3026eac59a516725fBen Chan            expected_content: The expected content of the FormatCompleted
27781904f12dbffa5c415db9ad3026eac59a516725fBen Chan                              signal, which can be partially specified.
27881904f12dbffa5c415db9ad3026eac59a516725fBen Chan                              Only specified fields are compared between the
27981904f12dbffa5c415db9ad3026eac59a516725fBen Chan                              actual and expected content.
28081904f12dbffa5c415db9ad3026eac59a516725fBen Chan
28181904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Returns:
28281904f12dbffa5c415db9ad3026eac59a516725fBen Chan            The actual content of the FormatCompleted signal.
28381904f12dbffa5c415db9ad3026eac59a516725fBen Chan
28481904f12dbffa5c415db9ad3026eac59a516725fBen Chan        Raises:
28581904f12dbffa5c415db9ad3026eac59a516725fBen Chan            error.TestFail: A test failure when there is a mismatch between the
28681904f12dbffa5c415db9ad3026eac59a516725fBen Chan                            actual and expected content of the FormatCompleted
28781904f12dbffa5c415db9ad3026eac59a516725fBen Chan                            signal.
28881904f12dbffa5c415db9ad3026eac59a516725fBen Chan        """
28981904f12dbffa5c415db9ad3026eac59a516725fBen Chan        return self.expect_signal(self.FORMAT_COMPLETED_SIGNAL,
29081904f12dbffa5c415db9ad3026eac59a516725fBen Chan                                  expected_content)
29181904f12dbffa5c415db9ad3026eac59a516725fBen Chan
2920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def mount(self, path, filesystem_type=None, options=None):
2930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Invokes the CrosDisks Mount method.
2940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
2950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
2960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            path: The device path to mount.
2970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            filesystem_type: The filesystem type used for mounting the device.
2980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options: A list of options used for mounting the device.
2990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
3000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if filesystem_type is None:
3010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            filesystem_type = ''
3020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if options is None:
3030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options = []
3040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.clear_signal_content(self.MOUNT_COMPLETED_SIGNAL)
3050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.interface.Mount(path, filesystem_type, options)
3060499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3070499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def unmount(self, path, options=None):
3080499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Invokes the CrosDisks Unmount method.
3090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
3110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            path: The device or mount path to unmount.
3120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options: A list of options used for unmounting the path.
3130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
3140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if options is None:
3150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options = []
3160499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.interface.Unmount(path, options)
3170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def wait_for_mount_completion(self):
3190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Waits for the CrosDisks MountCompleted signal.
3200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
3220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The content of the MountCompleted signal.
3230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
3240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self.wait_for_signal(self.MOUNT_COMPLETED_SIGNAL)
3250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def expect_mount_completion(self, expected_content):
3270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Waits and verifies for the CrosDisks MountCompleted signal.
3280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
3300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            expected_content: The expected content of the MountCompleted
3310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                              signal, which can be partially specified.
3320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                              Only specified fields are compared between the
3330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                              actual and expected content.
3340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
3360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The actual content of the MountCompleted signal.
3370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Raises:
3390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            error.TestFail: A test failure when there is a mismatch between the
3400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                            actual and expected content of the MountCompleted
3410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                            signal.
3420499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
34381904f12dbffa5c415db9ad3026eac59a516725fBen Chan        return self.expect_signal(self.MOUNT_COMPLETED_SIGNAL,
34481904f12dbffa5c415db9ad3026eac59a516725fBen Chan                                  expected_content)
3450499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3460499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3470499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass CrosDisksTester(GenericTesterMainLoop):
3480499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """A base tester class for testing the CrosDisks server.
3490499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3500499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    A derived class should override the get_tests method to return a list of
3510499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    test methods. The perform_one_test method invokes each test method in the
3520499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    list to verify some functionalities of CrosDisks server.
3530499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """
3540499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, test):
3550499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        bus_loop = DBusGMainLoop(set_as_default=True)
3560499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        bus = dbus.SystemBus(mainloop=bus_loop)
3570499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.main_loop = gobject.MainLoop()
3580499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        super(CrosDisksTester, self).__init__(test, self.main_loop)
3590499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.cros_disks = CrosDisksClient(self.main_loop, bus)
3600499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3610499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def get_tests(self):
3620499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Returns a list of test methods to be invoked by perform_one_test.
3630499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3640499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        A derived class should override this method.
3650499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3660499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
3670499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            A list of test methods.
3680499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
3690499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return []
3700499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3710499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    @ExceptionForward
3720499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def perform_one_test(self):
3730499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Exercises each test method in the list returned by get_tests.
3740499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
3750499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        tests = self.get_tests()
3760499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.remaining_requirements = set([test.func_name for test in tests])
3770499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        for test in tests:
3780499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            test()
3790499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self.requirement_completed(test.func_name)
3800499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3810499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3820499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass FilesystemTestObject(object):
3830499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """A base class to represent a filesystem test object.
3840499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3850499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    A filesystem test object can be a file, directory or symbolic link.
3860499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    A derived class should override the _create and _verify method to implement
3870499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    how the test object should be created and verified, respectively, on a
3880499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    filesystem.
3890499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """
3900499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, path, content, mode):
3910499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Initializes the instance.
3920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
3930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
3940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            path: The relative path of the test object.
3950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            content: The content of the test object.
3960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            mode: The file permissions given to the test object.
3970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
3980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._path = path
3990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._content = content
4000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._mode = mode
4010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def create(self, base_dir):
4030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Creates the test object in a base directory.
4040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
4060499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            base_dir: The base directory where the test object is created.
4070499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4080499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
4090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            True if the test object is created successfully or False otherwise.
4100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
4110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not self._create(base_dir):
4120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('Failed to create filesystem test object at "%s"',
4130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          os.path.join(base_dir, self._path))
4140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return False
4150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return True
4160499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def verify(self, base_dir):
4180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Verifies the test object in a base directory.
4190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
4210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            base_dir: The base directory where the test object is expected to be
4220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      found.
4230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
4250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            True if the test object is found in the base directory and matches
4260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            the expected content, or False otherwise.
4270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
4280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not self._verify(base_dir):
4290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('Failed to verify filesystem test object at "%s"',
4300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          os.path.join(base_dir, self._path))
4310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return False
4320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return True
4330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _create(self, base_dir):
4350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return False
4360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _verify(self, base_dir):
4380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return False
4390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass FilesystemTestDirectory(FilesystemTestObject):
4420499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """A filesystem test object that represents a directory."""
4430499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4440499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, path, content, mode=stat.S_IRWXU|stat.S_IRGRP| \
4450499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                 stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH):
4460499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        super(FilesystemTestDirectory, self).__init__(path, content, mode)
4470499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4480499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _create(self, base_dir):
4490499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        path = os.path.join(base_dir, self._path) if self._path else base_dir
4500499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4510499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if self._path:
4520499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            with ExceptionSuppressor(OSError):
4530499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                os.makedirs(path)
4540499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                os.chmod(path, self._mode)
4550499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4560499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not os.path.isdir(path):
4570499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return False
4580499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4590499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        for content in self._content:
4600499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            if not content.create(path):
4610499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                return False
4620499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return True
4630499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4640499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _verify(self, base_dir):
4650499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        path = os.path.join(base_dir, self._path) if self._path else base_dir
4660499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not os.path.isdir(path):
4670499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return False
4680499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4690499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        for content in self._content:
4700499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            if not content.verify(path):
4710499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                return False
4720499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return True
4730499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4740499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4750499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass FilesystemTestFile(FilesystemTestObject):
4760499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    """A filesystem test object that represents a file."""
4770499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4780499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, path, content, mode=stat.S_IRUSR|stat.S_IWUSR| \
4790499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                 stat.S_IRGRP|stat.S_IROTH):
4800499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        super(FilesystemTestFile, self).__init__(path, content, mode)
4810499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4820499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _create(self, base_dir):
4830499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        path = os.path.join(base_dir, self._path)
4840499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        with ExceptionSuppressor(IOError):
4850499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            with open(path, 'wb+') as f:
4860499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                f.write(self._content)
487f6a74c53fc0e802d3a7b9882a2d04de132ebaccaBen Chan            with ExceptionSuppressor(OSError):
488f6a74c53fc0e802d3a7b9882a2d04de132ebaccaBen Chan                os.chmod(path, self._mode)
4890499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return True
4900499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return False
4910499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _verify(self, base_dir):
4930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        path = os.path.join(base_dir, self._path)
4940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        with ExceptionSuppressor(IOError):
4950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            with open(path, 'rb') as f:
4960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                return f.read() == self._content
4970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return False
4980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
4990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass DefaultFilesystemTestContent(FilesystemTestDirectory):
5010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self):
5020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        super(DefaultFilesystemTestContent, self).__init__('', [
5030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            FilesystemTestFile('file1', '0123456789'),
5040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            FilesystemTestDirectory('dir1', [
5050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                FilesystemTestFile('file1', ''),
5060499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                FilesystemTestFile('file2', 'abcdefg'),
5070499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                FilesystemTestDirectory('dir2', [
5080499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                    FilesystemTestFile('file3', 'abcdefg'),
5090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                ]),
5100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            ]),
5110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        ], stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
5120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chanclass VirtualFilesystemImage(object):
5150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __init__(self, block_size, block_count, filesystem_type,
5160499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                 *args, **kwargs):
5170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Initializes the instance.
5180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
5200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            block_size: The number of bytes of each block in the image.
5210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            block_count: The number of blocks in the image.
5220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            filesystem_type: The filesystem type to be given to the mkfs
5230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                             program for formatting the image.
5240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Keyword Args:
5260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            mount_filesystem_type: The filesystem type to be given to the
5270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                                   mount program for mounting the image.
5280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            mkfs_options: A list of options to be given to the mkfs program.
5290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
5300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._block_size = block_size
5310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._block_count = block_count
5320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._filesystem_type = filesystem_type
5330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._mount_filesystem_type = kwargs.get('mount_filesystem_type')
5340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if self._mount_filesystem_type is None:
5350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self._mount_filesystem_type = filesystem_type
5360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._mkfs_options = kwargs.get('mkfs_options')
5370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if self._mkfs_options is None:
5380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self._mkfs_options = []
5390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._image_file = None
5400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._loop_device = None
5410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._mount_dir = None
5420499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5430499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __del__(self):
5440499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        with ExceptionSuppressor(Exception):
5450499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self.clean()
5460499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5470499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __enter__(self):
5480499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.create()
5490499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self
5500499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5510499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def __exit__(self, exc_type, exc_value, traceback):
5520499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.clean()
5530499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return False
5540499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5550499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _remove_temp_path(self, temp_path):
5560499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Removes a temporary file or directory created using autotemp."""
5570499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if temp_path:
5580499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            with ExceptionSuppressor(Exception):
5590499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                path = temp_path.name
5600499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                temp_path.clean()
5610499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                logging.debug('Removed "%s"', path)
5620499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5630499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _remove_image_file(self):
5640499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Removes the image file if one has been created."""
5650499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._remove_temp_path(self._image_file)
5660499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._image_file = None
5670499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5680499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def _remove_mount_dir(self):
5690499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Removes the mount directory if one has been created."""
5700499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._remove_temp_path(self._mount_dir)
5710499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._mount_dir = None
5720499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5730499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    @property
5740499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def image_file(self):
5750499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Gets the path of the image file.
5760499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5770499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
5780499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The path of the image file or None if no image file has been
5790499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            created.
5800499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
5810499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self._image_file.name if self._image_file else None
5820499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5830499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    @property
5840499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def loop_device(self):
5850499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Gets the loop device where the image file is attached to.
5860499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5870499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
5880499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The path of the loop device where the image file is attached to or
5890499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            None if no loop device is attaching the image file.
5900499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
5910499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self._loop_device
5920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    @property
5940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def mount_dir(self):
5950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Gets the directory where the image file is mounted to.
5960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
5970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
5980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The directory where the image file is mounted to or None if no
5990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            mount directory has been created.
6000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
6010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self._mount_dir.name if self._mount_dir else None
6020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def create(self):
6040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Creates a zero-filled image file with the specified size.
6050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6060499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        The created image file is temporary and removed when clean()
6070499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        is called.
6080499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
6090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.clean()
6100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._image_file = autotemp.tempfile(unique_id='fsImage')
6110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        try:
6120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('Creating zero-filled image file at "%s"',
6130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          self._image_file.name)
6140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            utils.run('dd if=/dev/zero of=%s bs=%s count=%s' %
6150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      (self._image_file.name, self._block_size,
6160499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                       self._block_count))
6170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        except error.CmdError as exc:
6180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self._remove_image_file()
6190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            message = 'Failed to create filesystem image: %s' % exc
6200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            raise RuntimeError(message)
6210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def clean(self):
6230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Removes the image file if one has been created.
6240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Before removal, the image file is detached from the loop device that
6260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        it is attached to.
6270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
6280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.detach_from_loop_device()
6290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._remove_image_file()
6300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def attach_to_loop_device(self):
6320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Attaches the created image file to a loop device.
6330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Creates the image file, if one has not been created, by calling
6350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        create().
6360499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6370499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Returns:
6380499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            The path of the loop device where the image file is attached to.
6390499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
6400499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if self._loop_device:
6410499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return self._loop_device
6420499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6430499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not self._image_file:
6440499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self.create()
6450499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6460499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Attaching image file "%s" to loop device',
6470499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      self._image_file.name)
6480499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        utils.run('losetup -f %s' % self._image_file.name)
6490499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        output = utils.system_output('losetup -j %s' % self._image_file.name)
6500499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        # output should look like: "/dev/loop0: [000d]:6329 (/tmp/test.img)"
6510499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._loop_device = output.split(':')[0]
6520499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Attached image file "%s" to loop device "%s"',
6530499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      self._image_file.name, self._loop_device)
6540499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self._loop_device
6550499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6560499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def detach_from_loop_device(self):
6570499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Detaches the image file from the loop device."""
6580499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not self._loop_device:
6590499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return
6600499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6610499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.unmount()
6620499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6630499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Cleaning up remaining mount points of loop device "%s"',
6640499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      self._loop_device)
6650499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        utils.run('umount -f %s' % self._loop_device, ignore_status=True)
6660499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6670499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        logging.debug('Detaching image file "%s" from loop device "%s"',
6680499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      self._image_file.name, self._loop_device)
6690499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        utils.run('losetup -d %s' % self._loop_device)
6700499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._loop_device = None
6710499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6720499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def format(self):
6730499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Formats the image file as the specified filesystem."""
6740499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.attach_to_loop_device()
6750499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        try:
6760499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('Formatting image file at "%s" as "%s" filesystem',
6770499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          self._image_file.name, self._filesystem_type)
6780499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            utils.run('yes | mkfs -t %s %s %s' %
6790499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      (self._filesystem_type, ' '.join(self._mkfs_options),
6800499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                       self._loop_device))
6810499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('blkid: %s', utils.system_output(
6820499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                'blkid -c /dev/null %s' % self._loop_device,
6830499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                ignore_status=True))
6840499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        except error.CmdError as exc:
6850499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            message = 'Failed to format filesystem image: %s' % exc
6860499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            raise RuntimeError(message)
6870499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6880499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def mount(self, options=None):
6890499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Mounts the image file to a directory.
6900499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6910499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        Args:
6920499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options: An optional list of mount options.
6930499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """
6940499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if self._mount_dir:
6950499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return self._mount_dir.name
6960499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
6970499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if options is None:
6980499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options = []
6990499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
7000499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        options_arg = ','.join(options)
7010499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if options_arg:
7020499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            options_arg = '-o ' + options_arg
7030499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
7040499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self.attach_to_loop_device()
7050499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        self._mount_dir = autotemp.tempdir(unique_id='fsImage')
7060499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        try:
7070499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('Mounting image file "%s" (%s) to directory "%s"',
7080499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          self._image_file.name, self._loop_device,
7090499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          self._mount_dir.name)
7100499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            utils.run('mount -t %s %s %s %s' %
7110499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                      (self._mount_filesystem_type, options_arg,
7120499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                       self._loop_device, self._mount_dir.name))
7130499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        except error.CmdError as exc:
7140499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self._remove_mount_dir()
7150499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            message = ('Failed to mount virtual filesystem image "%s": %s' %
7160499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                       (self._image_file.name, exc))
7170499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            raise RuntimeError(message)
7180499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        return self._mount_dir.name
7190499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
7200499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan    def unmount(self):
7210499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        """Unmounts the image file from the mounted directory."""
7220499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        if not self._mount_dir:
7230499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            return
7240499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan
7250499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        try:
7260499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            logging.debug('Unmounting image file "%s" (%s) from directory "%s"',
7270499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          self._image_file.name, self._loop_device,
7280499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                          self._mount_dir.name)
7290499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            utils.run('umount %s' % self._mount_dir.name)
7300499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        except error.CmdError as exc:
7310499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            message = ('Failed to unmount virtual filesystem image "%s": %s' %
7320499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan                       (self._image_file.name, exc))
7330499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            raise RuntimeError(message)
7340499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan        finally:
7350499e53e22a0fe29789e038976e8ede50af51ca3Ben Chan            self._remove_mount_dir()
736