1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li"""Storage device utilities to be used in storage device based tests 7*9c5db199SXin Li""" 8*9c5db199SXin Li 9*9c5db199SXin Liimport logging, re, os, time, hashlib 10*9c5db199SXin Li 11*9c5db199SXin Lifrom autotest_lib.client.bin import test, utils 12*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 13*9c5db199SXin Lifrom autotest_lib.client.cros import liststorage 14*9c5db199SXin Li 15*9c5db199SXin Li 16*9c5db199SXin Liclass StorageException(error.TestError): 17*9c5db199SXin Li """Indicates that a storage/volume operation failed. 18*9c5db199SXin Li It is fatal to the test unless caught. 19*9c5db199SXin Li """ 20*9c5db199SXin Li pass 21*9c5db199SXin Li 22*9c5db199SXin Li 23*9c5db199SXin Liclass StorageScanner(object): 24*9c5db199SXin Li """Scan device for storage points. 25*9c5db199SXin Li 26*9c5db199SXin Li It also performs basic operations on found storage devices as mount/umount, 27*9c5db199SXin Li creating file with randomized content or checksum file content. 28*9c5db199SXin Li 29*9c5db199SXin Li Each storage device is defined by a dictionary containing the following 30*9c5db199SXin Li keys: 31*9c5db199SXin Li 32*9c5db199SXin Li device: the device path (e.g. /dev/sdb1) 33*9c5db199SXin Li bus: the bus name (e.g. usb, ata, etc) 34*9c5db199SXin Li model: the kind of device (e.g. Multi-Card, USB_DISK_2.0, SanDisk) 35*9c5db199SXin Li size: the size of the volume/partition ib bytes (int) 36*9c5db199SXin Li fs_uuid: the UUID for the filesystem (str) 37*9c5db199SXin Li fstype: filesystem type 38*9c5db199SXin Li is_mounted: wether the FS is mounted (0=False,1=True) 39*9c5db199SXin Li mountpoint: where the FS is mounted (if mounted=1) or a suggestion where to 40*9c5db199SXin Li mount it (if mounted=0) 41*9c5db199SXin Li 42*9c5db199SXin Li Also |filter()| and |scan()| will use the same dictionary keys associated 43*9c5db199SXin Li with regular expression in order to filter a result set. 44*9c5db199SXin Li Multiple keys act in an AND-fashion way. The absence of a key in the filter 45*9c5db199SXin Li make the filter matching all the values for said key in the storage 46*9c5db199SXin Li dictionary. 47*9c5db199SXin Li 48*9c5db199SXin Li Example: {'device':'/dev/sd[ab]1', 'is_mounted':'0'} will match all the 49*9c5db199SXin Li found devices which block device file is either /dev/sda1 or /dev/sdb1, AND 50*9c5db199SXin Li are not mounted, excluding all other devices from the matched result. 51*9c5db199SXin Li """ 52*9c5db199SXin Li storages = None 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Li def __init__(self): 56*9c5db199SXin Li self.__mounted = {} 57*9c5db199SXin Li 58*9c5db199SXin Li 59*9c5db199SXin Li def filter(self, storage_filter={}): 60*9c5db199SXin Li """Filters a stored result returning a list of matching devices. 61*9c5db199SXin Li 62*9c5db199SXin Li The passed dictionary represent the filter and its values are regular 63*9c5db199SXin Li expressions (str). If an element of self.storage matches the regex 64*9c5db199SXin Li defined in all the keys for a filter, the item will be part of the 65*9c5db199SXin Li returning value. 66*9c5db199SXin Li 67*9c5db199SXin Li Calling this method does not change self.storages, thus can be called 68*9c5db199SXin Li several times against the same result set. 69*9c5db199SXin Li 70*9c5db199SXin Li @param storage_filter: a dictionary representing the filter. 71*9c5db199SXin Li 72*9c5db199SXin Li @return a list of dictionaries representing the found devices after the 73*9c5db199SXin Li application of the filter. The list can be empty if no device 74*9c5db199SXin Li has been found. 75*9c5db199SXin Li """ 76*9c5db199SXin Li ret = [] 77*9c5db199SXin Li 78*9c5db199SXin Li for storage in self.storages: 79*9c5db199SXin Li matches = True 80*9c5db199SXin Li for key in storage_filter: 81*9c5db199SXin Li if not re.match(storage_filter[key], storage[key]): 82*9c5db199SXin Li matches = False 83*9c5db199SXin Li break 84*9c5db199SXin Li if matches: 85*9c5db199SXin Li ret.append(storage.copy()) 86*9c5db199SXin Li 87*9c5db199SXin Li return ret 88*9c5db199SXin Li 89*9c5db199SXin Li 90*9c5db199SXin Li def scan(self, storage_filter={}): 91*9c5db199SXin Li """Scan the current storage devices. 92*9c5db199SXin Li 93*9c5db199SXin Li If no parameter is given, it will return all the storage devices found. 94*9c5db199SXin Li Otherwise it will internally call self.filter() with the passed 95*9c5db199SXin Li filter. 96*9c5db199SXin Li The result (being it filtered or not) will be saved in self.storages. 97*9c5db199SXin Li 98*9c5db199SXin Li Such list can be (re)-filtered using self.filter(). 99*9c5db199SXin Li 100*9c5db199SXin Li @param storage_filter: a dict representing the filter, default is 101*9c5db199SXin Li matching anything. 102*9c5db199SXin Li 103*9c5db199SXin Li @return a list of found dictionaries representing the found devices. 104*9c5db199SXin Li The list can be empty if no device has been found. 105*9c5db199SXin Li """ 106*9c5db199SXin Li self.storages = liststorage.get_all() 107*9c5db199SXin Li 108*9c5db199SXin Li if storage_filter: 109*9c5db199SXin Li self.storages = self.filter(storage_filter) 110*9c5db199SXin Li 111*9c5db199SXin Li return self.storages 112*9c5db199SXin Li 113*9c5db199SXin Li 114*9c5db199SXin Li def mount_volume(self, index=None, storage_dict=None, args=''): 115*9c5db199SXin Li """Mount the passed volume. 116*9c5db199SXin Li 117*9c5db199SXin Li Either index or storage_dict can be set, but not both at the same time. 118*9c5db199SXin Li If neither is passed, it will mount the first volume found in 119*9c5db199SXin Li self.storage. 120*9c5db199SXin Li 121*9c5db199SXin Li @param index: (int) the index in self.storages for the storage 122*9c5db199SXin Li device/volume to be mounted. 123*9c5db199SXin Li @param storage_dict: (dict) the storage dictionary representing the 124*9c5db199SXin Li storage device, the dictionary should be obtained from 125*9c5db199SXin Li self.storage or using self.scan() or self.filter(). 126*9c5db199SXin Li @param args: (str) args to be passed to the mount command, if needed. 127*9c5db199SXin Li e.g., "-o foo,bar -t ext3". 128*9c5db199SXin Li """ 129*9c5db199SXin Li if index is None and storage_dict is None: 130*9c5db199SXin Li storage_dict = self.storages[0] 131*9c5db199SXin Li elif isinstance(index, int): 132*9c5db199SXin Li storage_dict = self.storages[index] 133*9c5db199SXin Li elif not isinstance(storage_dict, dict): 134*9c5db199SXin Li raise TypeError('Either index or storage_dict passed ' 135*9c5db199SXin Li 'with the wrong type') 136*9c5db199SXin Li 137*9c5db199SXin Li if storage_dict['is_mounted']: 138*9c5db199SXin Li logging.debug('Volume "%s" is already mounted, skipping ' 139*9c5db199SXin Li 'mount_volume().') 140*9c5db199SXin Li return 141*9c5db199SXin Li 142*9c5db199SXin Li logging.info('Mounting %(device)s in %(mountpoint)s.', storage_dict) 143*9c5db199SXin Li 144*9c5db199SXin Li try: 145*9c5db199SXin Li # Create the dir in case it does not exist. 146*9c5db199SXin Li os.mkdir(storage_dict['mountpoint']) 147*9c5db199SXin Li except OSError as e: 148*9c5db199SXin Li # If it's not "file exists", report the exception. 149*9c5db199SXin Li if e.errno != 17: 150*9c5db199SXin Li raise e 151*9c5db199SXin Li cmd = 'mount %s' % args 152*9c5db199SXin Li cmd += ' %(device)s %(mountpoint)s' % storage_dict 153*9c5db199SXin Li utils.system(cmd) 154*9c5db199SXin Li storage_dict['is_mounted'] = True 155*9c5db199SXin Li self.__mounted[storage_dict['mountpoint']] = storage_dict 156*9c5db199SXin Li 157*9c5db199SXin Li 158*9c5db199SXin Li def umount_volume(self, index=None, storage_dict=None, args=''): 159*9c5db199SXin Li """Un-mount the passed volume, by index or storage dictionary. 160*9c5db199SXin Li 161*9c5db199SXin Li Either index or storage_dict can be set, but not both at the same time. 162*9c5db199SXin Li If neither is passed, it will mount the first volume found in 163*9c5db199SXin Li self.storage. 164*9c5db199SXin Li 165*9c5db199SXin Li @param index: (int) the index in self.storages for the storage 166*9c5db199SXin Li device/volume to be mounted. 167*9c5db199SXin Li @param storage_dict: (dict) the storage dictionary representing the 168*9c5db199SXin Li storage device, the dictionary should be obtained from 169*9c5db199SXin Li self.storage or using self.scan() or self.filter(). 170*9c5db199SXin Li @param args: (str) args to be passed to the umount command, if needed. 171*9c5db199SXin Li e.g., '-f -t' for force+lazy umount. 172*9c5db199SXin Li """ 173*9c5db199SXin Li if index is None and storage_dict is None: 174*9c5db199SXin Li storage_dict = self.storages[0] 175*9c5db199SXin Li elif isinstance(index, int): 176*9c5db199SXin Li storage_dict = self.storages[index] 177*9c5db199SXin Li elif not isinstance(storage_dict, dict): 178*9c5db199SXin Li raise TypeError('Either index or storage_dict passed ' 179*9c5db199SXin Li 'with the wrong type') 180*9c5db199SXin Li 181*9c5db199SXin Li 182*9c5db199SXin Li if not storage_dict['is_mounted']: 183*9c5db199SXin Li logging.debug('Volume "%s" is already unmounted: skipping ' 184*9c5db199SXin Li 'umount_volume().') 185*9c5db199SXin Li return 186*9c5db199SXin Li 187*9c5db199SXin Li logging.info('Unmounting %(device)s from %(mountpoint)s.', 188*9c5db199SXin Li storage_dict) 189*9c5db199SXin Li cmd = 'umount %s' % args 190*9c5db199SXin Li cmd += ' %(device)s' % storage_dict 191*9c5db199SXin Li utils.system(cmd) 192*9c5db199SXin Li # We don't care if it fails, it might be busy for a /proc/mounts issue. 193*9c5db199SXin Li # See BUG=chromium-os:32105 194*9c5db199SXin Li try: 195*9c5db199SXin Li os.rmdir(storage_dict['mountpoint']) 196*9c5db199SXin Li except OSError as e: 197*9c5db199SXin Li logging.debug('Removing %s failed: %s: ignoring.', 198*9c5db199SXin Li storage_dict['mountpoint'], e) 199*9c5db199SXin Li storage_dict['is_mounted'] = False 200*9c5db199SXin Li # If we previously mounted it, remove it from our internal list. 201*9c5db199SXin Li if storage_dict['mountpoint'] in self.__mounted: 202*9c5db199SXin Li del self.__mounted[storage_dict['mountpoint']] 203*9c5db199SXin Li 204*9c5db199SXin Li 205*9c5db199SXin Li def unmount_all(self): 206*9c5db199SXin Li """Unmount all volumes mounted by self.mount_volume(). 207*9c5db199SXin Li """ 208*9c5db199SXin Li # We need to copy it since we are iterating over a dict which will 209*9c5db199SXin Li # change size. 210*9c5db199SXin Li for volume in self.__mounted.copy(): 211*9c5db199SXin Li self.umount_volume(storage_dict=self.__mounted[volume]) 212*9c5db199SXin Li 213*9c5db199SXin Li 214*9c5db199SXin Liclass StorageTester(test.test): 215*9c5db199SXin Li """This is a class all tests about Storage can use. 216*9c5db199SXin Li 217*9c5db199SXin Li It has methods to 218*9c5db199SXin Li - create random files 219*9c5db199SXin Li - compute a file's md5 checksum 220*9c5db199SXin Li - look/wait for a specific device (specified using StorageScanner 221*9c5db199SXin Li dictionary format) 222*9c5db199SXin Li 223*9c5db199SXin Li Subclasses can override the _prepare_volume() method in order to disable 224*9c5db199SXin Li them or change their behaviours. 225*9c5db199SXin Li 226*9c5db199SXin Li Subclasses should take care of unmount all the mounted filesystems when 227*9c5db199SXin Li needed (e.g. on cleanup phase), calling self.umount_volume() or 228*9c5db199SXin Li self.unmount_all(). 229*9c5db199SXin Li """ 230*9c5db199SXin Li scanner = None 231*9c5db199SXin Li 232*9c5db199SXin Li 233*9c5db199SXin Li def initialize(self, filter_dict={'bus':'usb'}, filesystem='ext2'): 234*9c5db199SXin Li """Initialize the test. 235*9c5db199SXin Li 236*9c5db199SXin Li Instantiate a StorageScanner instance to be used by tests and prepare 237*9c5db199SXin Li any volume matched by |filter_dict|. 238*9c5db199SXin Li Volume preparation is done by the _prepare_volume() method, which can be 239*9c5db199SXin Li overriden by subclasses. 240*9c5db199SXin Li 241*9c5db199SXin Li @param filter_dict: a dictionary to filter attached USB devices to be 242*9c5db199SXin Li initialized. 243*9c5db199SXin Li @param filesystem: the filesystem name to format the attached device. 244*9c5db199SXin Li """ 245*9c5db199SXin Li super(StorageTester, self).initialize() 246*9c5db199SXin Li 247*9c5db199SXin Li self.scanner = StorageScanner() 248*9c5db199SXin Li 249*9c5db199SXin Li self._prepare_volume(filter_dict, filesystem=filesystem) 250*9c5db199SXin Li 251*9c5db199SXin Li # Be sure that if any operation above uses self.scanner related 252*9c5db199SXin Li # methods, its result is cleaned after use. 253*9c5db199SXin Li self.storages = None 254*9c5db199SXin Li 255*9c5db199SXin Li 256*9c5db199SXin Li def _prepare_volume(self, filter_dict, filesystem='ext2'): 257*9c5db199SXin Li """Prepare matching volumes for test. 258*9c5db199SXin Li 259*9c5db199SXin Li Prepare all the volumes matching |filter_dict| for test by formatting 260*9c5db199SXin Li the matching storages with |filesystem|. 261*9c5db199SXin Li 262*9c5db199SXin Li This method is called by StorageTester.initialize(), a subclass can 263*9c5db199SXin Li override this method to change its behaviour. 264*9c5db199SXin Li Setting it to None (or a not callable) will disable it. 265*9c5db199SXin Li 266*9c5db199SXin Li @param filter_dict: a filter for the storages to be prepared. 267*9c5db199SXin Li @param filesystem: filesystem with which volumes will be formatted. 268*9c5db199SXin Li """ 269*9c5db199SXin Li if not os.path.isfile('/sbin/mkfs.%s' % filesystem): 270*9c5db199SXin Li raise error.TestError('filesystem not supported by mkfs installed ' 271*9c5db199SXin Li 'on this device') 272*9c5db199SXin Li 273*9c5db199SXin Li try: 274*9c5db199SXin Li storages = self.wait_for_devices(filter_dict, cycles=1, 275*9c5db199SXin Li mount_volume=False)[0] 276*9c5db199SXin Li 277*9c5db199SXin Li for storage in storages: 278*9c5db199SXin Li logging.debug('Preparing volume on %s.', storage['device']) 279*9c5db199SXin Li cmd = 'mkfs.%s %s' % (filesystem, storage['device']) 280*9c5db199SXin Li utils.system(cmd) 281*9c5db199SXin Li except StorageException as e: 282*9c5db199SXin Li logging.warning("%s._prepare_volume() didn't find any device " 283*9c5db199SXin Li "attached: skipping volume preparation: %s", 284*9c5db199SXin Li self.__class__.__name__, e) 285*9c5db199SXin Li except error.CmdError as e: 286*9c5db199SXin Li logging.warning("%s._prepare_volume() couldn't format volume: %s", 287*9c5db199SXin Li self.__class__.__name__, e) 288*9c5db199SXin Li 289*9c5db199SXin Li logging.debug('Volume preparation finished.') 290*9c5db199SXin Li 291*9c5db199SXin Li 292*9c5db199SXin Li def wait_for_devices(self, storage_filter, time_to_sleep=1, cycles=10, 293*9c5db199SXin Li mount_volume=True): 294*9c5db199SXin Li """Cycles |cycles| times waiting |time_to_sleep| seconds each cycle, 295*9c5db199SXin Li looking for a device matching |storage_filter| 296*9c5db199SXin Li 297*9c5db199SXin Li @param storage_filter: a dictionary holding a set of storage device's 298*9c5db199SXin Li keys which are used as filter, to look for devices. 299*9c5db199SXin Li @see StorageDevice class documentation. 300*9c5db199SXin Li @param time_to_sleep: time (int) to wait after each |cycles|. 301*9c5db199SXin Li @param cycles: number of tentatives. Use -1 for infinite. 302*9c5db199SXin Li 303*9c5db199SXin Li @raises StorageException if no device can be found. 304*9c5db199SXin Li 305*9c5db199SXin Li @return (storage_dict, waited_time) tuple. storage_dict is the found 306*9c5db199SXin Li device list and waited_time is the time spent waiting for the 307*9c5db199SXin Li device to be found. 308*9c5db199SXin Li """ 309*9c5db199SXin Li msg = ('Scanning for %s for %d times, waiting each time ' 310*9c5db199SXin Li '%d secs' % (storage_filter, cycles, time_to_sleep)) 311*9c5db199SXin Li if mount_volume: 312*9c5db199SXin Li logging.debug('%s and mounting each matched volume.', msg) 313*9c5db199SXin Li else: 314*9c5db199SXin Li logging.debug('%s, but not mounting each matched volume.', msg) 315*9c5db199SXin Li 316*9c5db199SXin Li if cycles == -1: 317*9c5db199SXin Li logging.info('Waiting until device is inserted, ' 318*9c5db199SXin Li 'no timeout has been set.') 319*9c5db199SXin Li 320*9c5db199SXin Li cycle = 0 321*9c5db199SXin Li while cycles == -1 or cycle < cycles: 322*9c5db199SXin Li ret = self.scanner.scan(storage_filter) 323*9c5db199SXin Li if ret: 324*9c5db199SXin Li logging.debug('Found %s (mount_volume=%d).', ret, mount_volume) 325*9c5db199SXin Li if mount_volume: 326*9c5db199SXin Li for storage in ret: 327*9c5db199SXin Li self.scanner.mount_volume(storage_dict=storage) 328*9c5db199SXin Li 329*9c5db199SXin Li return (ret, cycle*time_to_sleep) 330*9c5db199SXin Li else: 331*9c5db199SXin Li logging.debug('Storage %s not found, wait and rescan ' 332*9c5db199SXin Li '(cycle %d).', storage_filter, cycle) 333*9c5db199SXin Li # Wait a bit and rescan storage list. 334*9c5db199SXin Li time.sleep(time_to_sleep) 335*9c5db199SXin Li cycle += 1 336*9c5db199SXin Li 337*9c5db199SXin Li # Device still not found. 338*9c5db199SXin Li msg = ('Could not find anything matching "%s" after %d seconds' % 339*9c5db199SXin Li (storage_filter, time_to_sleep*cycles)) 340*9c5db199SXin Li raise StorageException(msg) 341*9c5db199SXin Li 342*9c5db199SXin Li 343*9c5db199SXin Li def wait_for_device(self, storage_filter, time_to_sleep=1, cycles=10, 344*9c5db199SXin Li mount_volume=True): 345*9c5db199SXin Li """Cycles |cycles| times waiting |time_to_sleep| seconds each cycle, 346*9c5db199SXin Li looking for a device matching |storage_filter|. 347*9c5db199SXin Li 348*9c5db199SXin Li This method needs to match one and only one device. 349*9c5db199SXin Li @raises StorageException if no device can be found or more than one is 350*9c5db199SXin Li found. 351*9c5db199SXin Li 352*9c5db199SXin Li @param storage_filter: a dictionary holding a set of storage device's 353*9c5db199SXin Li keys which are used as filter, to look for devices 354*9c5db199SXin Li The filter has to be match a single device, a multiple matching 355*9c5db199SXin Li filter will lead to StorageException to e risen. Use 356*9c5db199SXin Li self.wait_for_devices() if more than one device is allowed to 357*9c5db199SXin Li be found. 358*9c5db199SXin Li @see StorageDevice class documentation. 359*9c5db199SXin Li @param time_to_sleep: time (int) to wait after each |cycles|. 360*9c5db199SXin Li @param cycles: number of tentatives. Use -1 for infinite. 361*9c5db199SXin Li 362*9c5db199SXin Li @return (storage_dict, waited_time) tuple. storage_dict is the found 363*9c5db199SXin Li device list and waited_time is the time spent waiting for the 364*9c5db199SXin Li device to be found. 365*9c5db199SXin Li """ 366*9c5db199SXin Li storages, waited_time = self.wait_for_devices(storage_filter, 367*9c5db199SXin Li time_to_sleep=time_to_sleep, 368*9c5db199SXin Li cycles=cycles, 369*9c5db199SXin Li mount_volume=mount_volume) 370*9c5db199SXin Li if len(storages) > 1: 371*9c5db199SXin Li msg = ('filter matched more than one storage volume, use ' 372*9c5db199SXin Li '%s.wait_for_devices() if you need more than one match' % 373*9c5db199SXin Li self.__class__) 374*9c5db199SXin Li raise StorageException(msg) 375*9c5db199SXin Li 376*9c5db199SXin Li # Return the first element if only this one has been matched. 377*9c5db199SXin Li return (storages[0], waited_time) 378*9c5db199SXin Li 379*9c5db199SXin Li 380*9c5db199SXin Li# Some helpers not present in utils.py to abstract normal file operations. 381*9c5db199SXin Li 382*9c5db199SXin Lidef create_file(path, size): 383*9c5db199SXin Li """Create a file using /dev/urandom. 384*9c5db199SXin Li 385*9c5db199SXin Li @param path: the path of the file. 386*9c5db199SXin Li @param size: the file size in bytes. 387*9c5db199SXin Li """ 388*9c5db199SXin Li logging.debug('Creating %s (size %d) from /dev/urandom.', path, size) 389*9c5db199SXin Li with open('/dev/urandom', 'rb') as urandom: 390*9c5db199SXin Li utils.open_write_close(path, urandom.read(size)) 391*9c5db199SXin Li 392*9c5db199SXin Li 393*9c5db199SXin Lidef checksum_file(path): 394*9c5db199SXin Li """Compute the MD5 Checksum for a file. 395*9c5db199SXin Li 396*9c5db199SXin Li @param path: the path of the file. 397*9c5db199SXin Li 398*9c5db199SXin Li @return a string with the checksum. 399*9c5db199SXin Li """ 400*9c5db199SXin Li chunk_size = 1024 401*9c5db199SXin Li 402*9c5db199SXin Li m = hashlib.md5() 403*9c5db199SXin Li with open(path, 'rb') as f: 404*9c5db199SXin Li for chunk in f.read(chunk_size): 405*9c5db199SXin Li m.update(chunk) 406*9c5db199SXin Li 407*9c5db199SXin Li logging.debug("MD5 checksum for %s is %s.", path, m.hexdigest()) 408*9c5db199SXin Li 409*9c5db199SXin Li return m.hexdigest() 410*9c5db199SXin Li 411*9c5db199SXin Li 412*9c5db199SXin Lidef args_to_storage_dict(args): 413*9c5db199SXin Li """Map args into storage dictionaries. 414*9c5db199SXin Li 415*9c5db199SXin Li This function is to be used (likely) in control files to obtain a storage 416*9c5db199SXin Li dictionary from command line arguments. 417*9c5db199SXin Li 418*9c5db199SXin Li @param args: a list of arguments as passed to control file. 419*9c5db199SXin Li 420*9c5db199SXin Li @return a tuple (storage_dict, rest_of_args) where storage_dict is a 421*9c5db199SXin Li dictionary for storage filtering and rest_of_args is a dictionary 422*9c5db199SXin Li of keys which do not match storage dict keys. 423*9c5db199SXin Li """ 424*9c5db199SXin Li args_dict = utils.args_to_dict(args) 425*9c5db199SXin Li storage_dict = {} 426*9c5db199SXin Li 427*9c5db199SXin Li # A list of all allowed keys and their type. 428*9c5db199SXin Li key_list = ('device', 'bus', 'model', 'size', 'fs_uuid', 'fstype', 429*9c5db199SXin Li 'is_mounted', 'mountpoint') 430*9c5db199SXin Li 431*9c5db199SXin Li def set_if_exists(src, dst, key): 432*9c5db199SXin Li """If |src| has |key| copies its value to |dst|. 433*9c5db199SXin Li 434*9c5db199SXin Li @return True if |key| exists in |src|, False otherwise. 435*9c5db199SXin Li """ 436*9c5db199SXin Li if key in src: 437*9c5db199SXin Li dst[key] = src[key] 438*9c5db199SXin Li return True 439*9c5db199SXin Li else: 440*9c5db199SXin Li return False 441*9c5db199SXin Li 442*9c5db199SXin Li for key in key_list: 443*9c5db199SXin Li if set_if_exists(args_dict, storage_dict, key): 444*9c5db199SXin Li del args_dict[key] 445*9c5db199SXin Li 446*9c5db199SXin Li # Return the storage dict and the leftovers of the args to be evaluated 447*9c5db199SXin Li # later. 448*9c5db199SXin Li return storage_dict, args_dict 449