| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| import sys |
| |
| from dbus.mainloop.glib import DBusGMainLoop |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import utils |
| from autotest_lib.client.common_lib.cros import smbprovider |
| |
| class enterprise_SmbProviderDaemon(test.test): |
| """ |
| Test for SmbProvider Daemon. |
| |
| """ |
| |
| version = 1 |
| |
| WORKGROUP = '' |
| USERNAME = '' |
| PASSWORD = '' |
| |
| def setup(self): |
| """ |
| Compiles protobufs for error type and input/output parameters. |
| |
| """ |
| |
| os.chdir(self.srcdir) |
| utils.make('OUT_DIR=.') |
| |
| def initialize(self): |
| """ |
| Initializes the D-Bus loop and creates Python wrapper. |
| |
| """ |
| |
| bus_loop = DBusGMainLoop(set_as_default=True) |
| self._smbprovider = smbprovider.SmbProvider(bus_loop, self.srcdir) |
| |
| # Append path for directory_entry_pb2 imports. |
| sys.path.append(self.srcdir) |
| |
| def run_once(self, mount_path): |
| """ |
| Runs smbproviderd D-Bus commands. |
| |
| @param mount_path: Address of the SMB share. |
| """ |
| |
| self.sanity_test(mount_path) |
| |
| def _generate_random_id(self, size): |
| """ |
| Generates a random string of size N. |
| |
| @param size: Size of the generated string. |
| |
| @return: Returns a random alphanumeric string of size N. |
| |
| """ |
| |
| import string |
| import random |
| |
| return ''.join(random.choice(string.ascii_uppercase + |
| string.digits) for i in range(size)) |
| |
| def sanity_test(self, mount_path): |
| """ |
| Sanity test that runs through all filesystem operations |
| on the SmbProvider Daemon. |
| |
| @param mount_path: Address of the SMB share. |
| |
| """ |
| |
| from directory_entry_pb2 import ERROR_EXISTS |
| |
| # Mount the SMB share. |
| mount_id = self._check_mount(mount_path) |
| |
| # Generate random directory. |
| rand_dir_id = self._generate_random_id(10) |
| test_dir = '/autotest_' + rand_dir_id + '/' |
| self._check_create_directory(mount_id, test_dir, False) |
| |
| # Get metadata of a directory. |
| metadata = self._check_get_metadata(mount_id, test_dir) |
| |
| # Check that GetMetadata has correct values of a directory. |
| self._check_metadata(test_dir[1:-1], 0, True, metadata) |
| |
| # Create file inside directory. |
| test_file = test_dir + '1.txt' |
| self._check_create_file(mount_id, test_file) |
| |
| # Open file with Read-Only privileges. |
| file_id = self._check_open_file(mount_id, test_file, False) |
| self._check_close_file(mount_id, file_id) |
| |
| # Open + Close file with Read-Only privileges. |
| file_id = self._check_open_file(mount_id, test_file, False) |
| self._check_close_file(mount_id, file_id) |
| |
| # Open file for writing. |
| file_id = self._check_open_file(mount_id, test_file, True) |
| |
| # Write data to file. |
| data = 'Hello World!' |
| self._check_write_file(mount_id, file_id, 0, data) |
| |
| # Read data from file. |
| read_data = self._check_read_file(mount_id, file_id, 0, len(data)) |
| |
| # Close file. |
| self._check_close_file(mount_id, file_id) |
| |
| # Verify data is written to file correctly. |
| self._check_contents(data, read_data) |
| |
| # Get the metadata of the file. |
| metadata = self._check_get_metadata(mount_id, test_file) |
| |
| # Check that GetMetadeta has correct values of a file. |
| # TODO(jimmyxgong): len() only works properly for UTF-8. Find way to |
| # get size universally. |
| self._check_metadata('1.txt', len(data), False, metadata) |
| |
| # Delete file. |
| self._check_delete_entry(mount_id, test_file, False) |
| |
| # Create recursive directories. |
| recursive_dir = test_dir + 'test1/test2/' |
| self._check_create_directory(mount_id, recursive_dir, True) |
| |
| # Create file within the new directory. |
| test_file2 = recursive_dir + '2.txt' |
| self._check_create_file(mount_id, test_file2) |
| |
| # Check moving to existing entry is handled. |
| self._check_move_entry(mount_id, test_file2, test_dir, ERROR_EXISTS) |
| |
| # Move file up to root test directory. |
| self._check_move_entry(mount_id, test_file2, test_dir + 'moved.txt') |
| |
| # Move back down to original location. |
| self._check_move_entry(mount_id, test_dir + 'moved.txt', test_file2) |
| |
| # TODO(jimmyxgong): Delete contents of autotest directory recursively. |
| self._check_delete_entry(mount_id, test_file2, False) |
| self._check_delete_entry(mount_id, test_dir + 'test1/test2/', False) |
| self._check_delete_entry(mount_id, test_dir + 'test1/', False) |
| |
| # Delete autotest directory. |
| self._check_delete_entry(mount_id, test_dir, False) |
| |
| # Unmount the SMB share. |
| self._check_unmount(mount_id) |
| |
| def _check_mount(self, mount_path): |
| """ |
| Checks that mount is working. |
| |
| @param mount_path: Address of the SMB share. |
| |
| @return mount_id: Unique identifier of the mount. |
| |
| """ |
| |
| from directory_entry_pb2 import ERROR_OK |
| |
| error, mount_id = self._smbprovider.mount(mount_path, |
| self.WORKGROUP, |
| self.USERNAME, |
| self.PASSWORD) |
| |
| if mount_id < 0 : |
| raise error.TestFail('Unexpected failure with mount id.') |
| |
| self._check_result('Mount', error) |
| return mount_id |
| |
| def _check_unmount(self, mount_id): |
| """ |
| Checks that unmount is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| |
| """ |
| |
| error = self._smbprovider.unmount(mount_id) |
| |
| self._check_result('Unmount', error) |
| |
| def _check_get_metadata(self, mount_id, entry_path): |
| """ |
| Checks that get metadata is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param entry_path: Path of the entry. |
| |
| @return: GetMetaDataEntryOptionsProto blob string returned by the D-Bus |
| call. |
| |
| """ |
| |
| error, metadata_blob = self._smbprovider.get_metadata(mount_id, |
| entry_path) |
| |
| self._check_result('Get Metadata', error) |
| |
| return metadata_blob |
| |
| def _check_metadata(self, entry_path, size, is_dir, metadata_blob): |
| """ |
| Checks that metadata_blob has the correct values. |
| |
| @param entry_path: File path of the entry we are checking. |
| @param size: Size of the entry in bytes. |
| @param is_dir: Boolean that indicates whether the entry is a directory. |
| @param metadata_blob: Blob that contains metadata of the entry. |
| |
| """ |
| |
| if entry_path != metadata_blob.name or \ |
| size != metadata_blob.size or \ |
| is_dir != metadata_blob.is_directory: |
| logging.error('Failed: Metadata is incorrect') |
| raise error.TestFail('Unexpected error with metadata') |
| |
| def _check_create_file(self, mount_id, file_path): |
| """ |
| Checks that create file is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param file_path: Path of where the new file will be created. |
| |
| """ |
| |
| error = self._smbprovider.create_file(mount_id, file_path) |
| |
| self._check_result('Create File', error) |
| |
| def _check_open_file(self, mount_id, file_path, writeable): |
| """ |
| Checks that open file is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param file_path: Path of where the file is located. |
| @param writeable: Boolean to indicated whether the file should |
| be opened with write access. |
| |
| """ |
| |
| error, file_id = self._smbprovider.open_file(mount_id, |
| file_path, |
| writeable) |
| if file_id < 0: |
| raise error.TestFail('Unexpected file id failure.') |
| |
| self._check_result('Open File', error) |
| |
| return file_id |
| |
| def _check_close_file(self, mount_id, file_id): |
| """ |
| Checks that close file is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param file_id: Unique identifier of the file. |
| |
| """ |
| |
| error = self._smbprovider.close_file(mount_id, file_id) |
| |
| self._check_result('Close File', error) |
| |
| def _check_write_file(self, mount_id, file_id, offset, data): |
| """ |
| Checks that write file is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param file_id: Unique identifier of the file. |
| @param offset: Offset of the file to start writing to. |
| @param data: Data to be written. |
| |
| """ |
| |
| error = self._smbprovider.write_file(mount_id, file_id, offset, data) |
| |
| self._check_result('Write File', error) |
| |
| def _check_read_file(self, mount_id, file_id, offset, length): |
| """ |
| Checks that read file is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param file_id: Unique identifier of the file. |
| @param offset: Offset of the file to start reading from. |
| @param length: Length of data to read in bytes. |
| |
| @return A buffer containing the data read. |
| |
| """ |
| |
| error, fd = self._smbprovider.read_file(mount_id, file_id, offset, |
| length) |
| |
| self._check_result('Read File', error) |
| |
| return fd |
| |
| def _check_contents(self, data, read_data): |
| """ |
| Checks that read_data is equal to data. |
| |
| @param data: Original data to be compared to. |
| @param read_data: Data to be compared to the original data. |
| |
| """ |
| |
| if data != read_data: |
| logging.error('Failed: Written data does not match Read data') |
| raise error.TestFail( |
| 'Unexpected mismatch of written data and read data.\ |
| Expected: %s , but got: %s' % (data, read_data)) |
| |
| def _check_create_directory(self, mount_id, |
| directory_path, |
| recursive): |
| """ |
| Checks that create directory is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param directory_path: Path for the test directory. |
| @param recursive: Boolean to indicate whether directories should be |
| created recursively. |
| |
| """ |
| |
| error = self._smbprovider.create_directory(mount_id, |
| directory_path, |
| recursive) |
| |
| self._check_result('Create Directory', error) |
| |
| def _check_delete_entry(self, mount_id, entry_path, recursive): |
| """ |
| Checks that delete an entry works. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param entry_path: Path to the file/directory to delete. |
| @param recursive: Boolean to indicate recursive deletes. |
| |
| """ |
| |
| error = self._smbprovider.delete_entry(mount_id, |
| entry_path, |
| recursive) |
| |
| self._check_result('Delete Entry', error) |
| |
| def _check_move_entry(self, mount_id, source_path, target_path, |
| expected=None): |
| """ |
| Checks that move entry is working. |
| |
| @param mount_id: Unique identifier of the mount. |
| @param source_path: Path of the entry to be moved. |
| @param target_path: Path of the destination for the entry. |
| @param expected: Expected ErrorType. Default: None (ERROR_OK) |
| |
| """ |
| |
| error = self._smbprovider.move_entry(mount_id, |
| source_path, |
| target_path) |
| |
| self._check_result('Move Entry', error, expected) |
| |
| def _check_result(self, method_name, result, expected=None): |
| """ |
| Helper to check error codes and throw on mismatch. |
| |
| Checks whether the returned ErrorType from a D-Bus call to smbproviderd |
| matches the expected ErrorType. In case of a mismatch, throws a |
| TestError. |
| |
| @param method_name: Name of the D-Bus method that was called. |
| @param result: ErrorType returned from the D-Bus call. |
| @param expected: Expected ErrorType. Default: ErrorType.ERROR_OK. |
| |
| """ |
| |
| from directory_entry_pb2 import ErrorType |
| from directory_entry_pb2 import ERROR_OK |
| |
| if not expected: |
| expected = ERROR_OK |
| |
| if result != expected: |
| logging.error('Failed to run %s', method_name) |
| raise error.TestFail( |
| '%s failed with error %s (%s), expected %s (%s)' % ( |
| method_name, result, ErrorType.Name(result), expected, |
| ErrorType.Name(expected))) |