1import datetime, logging, os, time
2from autotest_lib.client.bin import test, utils
3from autotest_lib.client.common_lib import error
4
5class wb_kupdate(test.test):
6    version = 1
7
8
9    def _check_parameters(self, mount_point, write_size, file_count,
10                          old_cleanup=False):
11        """
12        Check all test parameters.
13
14        @param mount_point: the path to the desired mount_point.
15        @param write_size: the size of data in MB to write.
16        @param file_count: the number of files to write.
17        @param old_cleanup: removes previous mount_point if it exists and is
18                not mounted. Default is False.
19        """
20        # Check mount_point.
21        if not os.path.exists(mount_point):
22            logging.info('%s does not exist. Creating directory.', mount_point)
23        elif not os.path.ismount(mount_point) and old_cleanup:
24            logging.info('Removing previous mount_point directory')
25            os.rmdir(mount_point)
26            logging.info('Creating new mount_point.')
27        else:
28            raise error.TestError('Mount point: %s already exists.' %
29                                  mount_point)
30
31        os.makedirs(mount_point)
32        # Check write_size > 0.
33        if not (write_size > 0):
34            raise error.TestError('Write size should be a positive integer.')
35
36        # Check file_count > 0.
37        if not (file_count > 0) :
38            raise error.TestError('File count shoulde be a positive integer.')
39
40
41    def _reset_device(self):
42        """
43        Reset the test. Reinitialize sparse file.
44        """
45        # Umount device.
46        logging.debug('Cleanup - unmounting loopback device.')
47        utils.system('umount %s' % self.mount_point, ignore_status=True)
48
49        # Remove sparse_file.
50        logging.debug('Cleanup - removing sparse file.')
51        os.remove(self.sparse_file)
52
53        # Remove mount_point directory.
54        logging.debug('Cleanup - removing the mount_point.')
55        os.rmdir(self.mount_point)
56
57
58    def _create_partition(self):
59        """
60        Create and initialize the sparse file.
61        """
62        # Recreate sparse_file.
63        utils.system('dd if=/dev/zero of=%s bs=1M seek=1024 count=1' %
64                      self.sparse_file)
65
66        # Format sparse_file.
67        utils.system('echo "y" |  mkfs -t %s %s' %
68                     (self.file_system, self.sparse_file))
69
70        # Mount sparse_file.
71        utils.system('mount -o loop -t %s %s %s' %
72                     (self.file_system, self.sparse_file, self.mount_point))
73
74
75    def _needs_more_time(self, start_time, duration, _now=None):
76        """
77        Checks to see if the test has run its course.
78
79        @param start_time: a datetime object specifying the start time of the
80                test.
81        @param duration: test duration in minutes.
82        @param _now: used mostly for testing - ensures that the function returns
83                pass/fail depnding on the value of _now.
84
85        @return: True if the test still needs to run longer.
86                 False if the test has run for 'duration' minutes.
87        """
88        if not _now:
89            time_diff = datetime.datetime.now() - start_time
90        else:
91            time_diff = _now - start_time
92        return time_diff <= datetime.timedelta(seconds=duration*60)
93
94
95    def _write_data(self, destination, counter, write_size):
96        """
97        Writes data to the cache/memory.
98
99        @param destination: the absolute path to where the data needs to be
100        written.
101        @param counter: the file counter.
102        @param write_size: the size of data to be written.
103
104        @return: the time when the write completed as a datetime object.
105        """
106        # Write data to disk.
107        file_name = os.path.join(destination, 'test_file_%s' % counter)
108        write_cmd = ('dd if=/dev/zero of=%s bs=1M count=%s' %
109                     (file_name, write_size))
110        utils.system(write_cmd)
111
112        # Time the write operation.
113        write_completion_time = datetime.datetime.now()
114
115        # Return write completion time.
116        return write_completion_time
117
118
119    def _get_disk_usage(self, file_name):
120        """
121        Returns the disk usage of given file.
122
123        @param file_name: the name of the file.
124
125        @return: the disk usage as an integer.
126        """
127        # Check du stats.
128        cmd = '%s %s' % (self._DU_CMD, file_name)
129
130        # Expected value for  output = '1028\tfoo'
131        output = utils.system_output(cmd)
132
133        # Desired ouput = (1028, foo)
134        output = output.split('\t')
135
136        return int(output[0])
137
138
139    def _wait_until_data_flushed(self, start_time, max_wait_time):
140        """
141        Check to see if the sparse file size increases.
142
143        @param start_time: the time when data was actually written into the
144                cache.
145        @param max_wait_time: the max amount of time to wait.
146
147        @return: time waited as a datetime.timedelta object.
148        """
149        current_size = self._get_disk_usage(self.sparse_file)
150        flushed_size = current_size
151
152        logging.debug('current_size: %s' % current_size)
153        logging.debug('flushed_size: %s' % flushed_size)
154
155        # Keep checking until du value changes.
156        while current_size == flushed_size:
157            # Get flushed_size.
158            flushed_size = self._get_disk_usage(self.sparse_file)
159            logging.debug('flushed_size: %s' % flushed_size)
160            time.sleep(1)
161
162            # Check if data has been synced to disk.
163            if not self._needs_more_time(start_time, max_wait_time):
164                raise error.TestError('Data not flushed. Waited for %s minutes '
165                                      'for data to flush out.' % max_wait_time)
166
167        # Return time waited.
168        return datetime.datetime.now() - start_time
169
170
171    def initialize(self):
172        """
173        Initialize all private and global member variables.
174        """
175        self._DU_CMD = 'du'
176        self.partition = None
177        self.mount_point = ''
178        self.sparse_file = ''
179        self.result_map = {}
180        self.file_system = None
181
182
183    def run_once(self, mount_point, file_count, write_size,
184                 max_flush_time=1, file_system=None, remove_previous=False,
185                 sparse_file=os.path.join(os.getcwd(),'sparse_file'),
186                 old_cleanup=False):
187        """
188        Control execution of the test.
189
190        @param mount_point: the absolute path to the mount point.
191        @param file_count: the number of files to write.
192        @param write_size: the size of each file in MB.
193        @param max_flush_time: the maximum time to wait for the writeback to
194                flush dirty data to disk. Default = 1 minute.
195        @param file_system: the new file system to be mounted, if any.
196                Default = None.
197        @param remove_previous: boolean that allows the removal of previous
198                files before creating a new one. Default = False.
199        @param sparse_file: the absolute path to the sparse file.
200        @param old_cleanup: removes previous mount_point if it exists and is
201                not mounted. Default is False.
202        """
203        # Check validity of parameters.
204        self._check_parameters(mount_point, write_size, file_count,
205                               old_cleanup)
206
207        # Initialize class variables.
208        self.mount_point = mount_point
209        self.sparse_file = sparse_file
210        self.file_system = file_system
211
212        # Initialize partition values.
213        self._create_partition()
214
215        # Flush read and write cache.
216        utils.drop_caches()
217
218        # Start iterations.
219        logging.info('Starting test operations.')
220        test_start_time = datetime.datetime.now()
221        counter = 1
222
223        # Run test until file_count files are successfully written to disk.
224        while counter < file_count:
225            logging.info('Iteration %s.', counter)
226
227            # Write data to disk.
228            write_completion_time = self._write_data(self.mount_point, counter,
229                                                     write_size)
230            logging.debug('Write time:%s',
231                          write_completion_time.strftime("%H:%M:%S"))
232
233            # Wait until data get synced to disk.
234            time_taken = self._wait_until_data_flushed(write_completion_time,
235                                                       max_flush_time)
236
237            # Log time statistics.
238            logging.info('Time taken to flush data: %s seconds.',
239                         time_taken.seconds)
240
241            # Check if there is a need to remove the previously written file.
242            if remove_previous:
243                logging.debug('Removing previous file instance.')
244                os.remove(sparse_file)
245            else:
246                logging.debug('Not removing previous file instance.')
247
248            # Flush cache.
249            logging.debug('Flush cache between iterations.')
250            utils.drop_caches()
251
252           # Update the result map.
253            self.result_map[counter] = time_taken.seconds
254
255            # Increment the counter.
256            counter += 1
257
258
259    def postprocess(self):
260        """
261        Cleanup routine.
262        """
263        # Write out keyval map.
264        self.write_perf_keyval(self.result_map)
265
266        # Cleanup device.
267        self._reset_device()
268
269        logging.info('Test operations completed.')
270