blob: 3e2cbc906fe9accd1d05bd0b1b278537f9a42080 [file] [log] [blame]
# 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)))