Merge ~sylvain-pineau/checkbox-support:usb_read_write into checkbox-support:master

Proposed by Sylvain Pineau
Status: Merged
Approved by: Sylvain Pineau
Approved revision: 6d8deed3e1f94cb6ab1d6a6ad22149abdedbb396
Merged at revision: 512270690e5a309ca9d8ebf3bde520f889a678ae
Proposed branch: ~sylvain-pineau/checkbox-support:usb_read_write
Merge into: checkbox-support:master
Diff against target: 377 lines (+354/-0)
3 files modified
checkbox_support/scripts/network.py (+0/-0)
checkbox_support/scripts/usb_read_write.py (+352/-0)
setup.py (+2/-0)
Reviewer Review Type Date Requested Status
Jonathan Cave (community) Approve
Maciej Kisielewski Approve
Review via email: mp+325373@code.launchpad.net

Description of the change

New console script entry point for usb_read_write, to use the same version for checkbox-plano and checkbox-snappy.

It's really just a copy of https://git.launchpad.net/plainbox-provider-snappy/tree/bin/usb_read_write

tested using:

sylvain@sylvain-ThinkPad-T430s:~/lp/checkbox-support$ sudo !!
sudo USB_RWTEST_PARTITIONS=sdb1 checkbox-support-usb_read_write
PASS: WRITING TEST: /tmp/tmpd5_03uve/tmpzm6djfsu0
PASS: WRITING TEST: /tmp/tmpd5_03uve/tmpzm6djfsu1
[...]

To post a comment you must log in.
Revision history for this message
Maciej Kisielewski (kissiel) wrote :

Goodies, thanks! +1

review: Approve
Revision history for this message
Jonathan Cave (jocave) wrote :

Looks like a straight import to me +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/checkbox_support/scripts/network.py b/checkbox_support/scripts/network.py
0old mode 1007550old mode 100755
1new mode 1006441new mode 100644
index f66c4ad..f66c4ad
--- a/checkbox_support/scripts/network.py
+++ b/checkbox_support/scripts/network.py
diff --git a/checkbox_support/scripts/usb_read_write.py b/checkbox_support/scripts/usb_read_write.py
2new file mode 1006442new file mode 100644
index 0000000..c589def
--- /dev/null
+++ b/checkbox_support/scripts/usb_read_write.py
@@ -0,0 +1,352 @@
1#!/usr/bin/env python3
2# Copyright 2015 - 2016 Canonical Ltd.
3# All rights reserved.
4#
5# Written by:
6# Taihsiang Ho <taihsiang.ho@canonical.com>
7
8"""
9Script to test the performance of USB read/write.
10
11This script is intended be used as part of a plainbox provider.
12It is expected to find the partition of the usb storage
13inserted in the previous insertion test
14and use the info to mount, read and write the USB.
15
16The test is performed by the following steps:
17 1. create a random file, say a "source file"
18 2. mount the USB storage with the folder FOLDER_TO_MOUNT
19 3. copy the source file into FOLDER_TO_MOUNT by dd REPETITION_NUM times.
20 4. access the md5sum numbers of the files copied into FOLDER_TO_MOUNT
21 5. compare the md5sum numbers with the md5sum of the source file.
22 6. report the result and return associated values back to plainbox.
23"""
24
25import sys
26import subprocess
27import os
28import collections
29import tempfile
30import logging
31import errno
32import contextlib
33
34
35PLAINBOX_SESSION_SHARE = os.environ.get('PLAINBOX_SESSION_SHARE', '')
36FOLDER_TO_MOUNT = tempfile.mkdtemp()
37REPETITION_NUM = 5 # number to repeat the read/write test units.
38# Prepare a random file which size is RANDOM_FILE_SIZE.
39# Default 1048576 = 1MB to perform writing test
40RANDOM_FILE_SIZE = 104857600
41USB_INSERT_INFO = "usb_insert_info"
42
43log_path = os.path.join(PLAINBOX_SESSION_SHARE, 'usb-rw.log')
44logging.basicConfig(level=logging.DEBUG, filename=log_path)
45
46
47class RandomData():
48 """
49 Class to create data files.
50
51 This class is ported from the original checkbox script.
52 """
53
54 def __init__(self, size):
55 """
56 init method of class RandomData.
57
58 :param size:
59 an integer to decide the size of the generated random file in byte.
60 """
61 self.tfile = tempfile.NamedTemporaryFile(delete=False)
62 self.path = ''
63 self.name = ''
64 self.path, self.name = os.path.split(self.tfile.name)
65 self._write_test_data_file(size)
66
67 def _generate_test_data(self):
68 seed = "104872948765827105728492766217823438120"
69 phrase = '''
70 Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam
71 nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat
72 volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation
73 ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
74 Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse
75 molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero
76 eros et accumsan et iusto odio dignissim qui blandit praesent luptatum
77 zzril delenit augue duis dolore te feugait nulla facilisi.
78 '''
79 words = phrase.replace('\n', '').split()
80 word_deque = collections.deque(words)
81 seed_deque = collections.deque(seed)
82 while True:
83 yield ' '.join(list(word_deque))
84 word_deque.rotate(int(seed_deque[0]))
85 seed_deque.rotate(1)
86
87 def _write_test_data_file(self, size):
88 data = self._generate_test_data()
89 while os.path.getsize(self.tfile.name) < size:
90 self.tfile.write(next(data).encode('UTF-8'))
91 return self
92
93
94def get_partition_info():
95 """
96 get partition info.
97
98 use a cache file provided by usb insertion test
99 to get the partition to mount later
100
101 return: a string which is a partition name. e.g. sdb1
102 """
103 if not PLAINBOX_SESSION_SHARE:
104 logging.error("no PLAINBOX_SESSION_SHARE is defined.")
105 sys.exit(1)
106 file_lines = ""
107 info_path = os.path.join(PLAINBOX_SESSION_SHARE, USB_INSERT_INFO)
108 try:
109 with open(info_path, "r") as file_usb_insert_info:
110 file_lines = file_usb_insert_info.readlines()
111 except OSError as e:
112 if e.errno == errno.ENOENT:
113 logging.error("%s info file was not found. \
114 Did the insertion test was run successfully?"
115 % USB_INSERT_INFO)
116 sys.exit(1)
117 # TODO: need to be smarter
118 if len(file_lines) == 1:
119 partition = file_lines[0].strip()
120 else:
121 logging.error("has no idea which partition to mount or not found")
122 sys.exit(1)
123
124 return partition
125
126
127def run_read_write_test():
128 """try to mount the partition candidates."""
129 # random file as a benchmark, a "source" file
130 with gen_random_file() as random_file:
131 # initialize the necessary tasks before performing read/write test
132 partitions = os.environ.get('USB_RWTEST_PARTITIONS', '').split()
133 if not partitions:
134 partitions = [get_partition_info()]
135 for partition in partitions:
136 with mount_usb_storage(partition):
137 # write test
138 write_test(random_file)
139 # already write some data into the target
140 # so let's read it to perform the read test
141 # and validate the writing correctness
142 read_test(random_file)
143
144
145@contextlib.contextmanager
146def mount_usb_storage(partition):
147 """
148 initialize the configuration so we could get ready to test jobs.
149
150 get everything ready to have the read/write test, including
151 1. create a temporary folder used for mounting devices
152 2. un-mount the target device at the very beginning if it was mounted
153
154 TODO: this function should be smarter and not completed enough
155 """
156 logging.debug("try to mount usb storage for testing")
157
158 try:
159 device_to_mount = os.path.join("/dev", partition)
160 # use pipe so I could hide message like
161 # "umount: /tmp/tmpjzwb6lys: not mounted"
162 subprocess.call(['umount', FOLDER_TO_MOUNT], stderr=subprocess.PIPE)
163
164 # mount the target device/partition
165 # if the return code of the shell command is non-zero,
166 # means something wrong.
167 # quit this script and return a non-zero value to plainbox
168 if subprocess.call(['mount', device_to_mount, FOLDER_TO_MOUNT]):
169 logging.error("mount %s on %s failed."
170 % (device_to_mount, FOLDER_TO_MOUNT))
171 sys.exit(1)
172 else:
173 logging.debug("mount %s on %s successfully."
174 % (device_to_mount, FOLDER_TO_MOUNT))
175 yield
176
177 finally:
178 logging.info("context manager exit: unmount USB storage")
179 if subprocess.call(['umount', FOLDER_TO_MOUNT]):
180 logging.warning("umount %s failed." % FOLDER_TO_MOUNT)
181 else:
182 logging.info("umount %s successfully." % FOLDER_TO_MOUNT)
183
184
185def read_test(random_file):
186 """perform the read test."""
187 logging.debug("===================")
188 logging.debug("reading test begins")
189 logging.debug("===================")
190
191 read_test_list = []
192 for idx in range(REPETITION_NUM):
193 read_test_list.append(read_test_unit(random_file, str(idx)))
194 print('PASS: all reading tests passed.')
195
196
197def read_test_unit(random_source_file, idx=""):
198 """
199 perform the read test.
200
201 :param random_source_file: a RandomData object
202 :param idx: a idx to label the files to be compared with the source file.
203 It is an int string, "1", "2", "3", ......etc.
204 """
205 # access the temporary file
206 path_random_file = os.path.join(
207 FOLDER_TO_MOUNT, os.path.basename(random_source_file.tfile.name)) + idx
208 # get the md5sum of the temp random files to compare
209 process = subprocess.Popen(['md5sum', path_random_file],
210 stdout=subprocess.PIPE)
211 tfile_md5sum = process.communicate()[0].decode().split(" ")[0]
212 # get the md5sum of the source random file
213 process = subprocess.Popen(['md5sum', random_source_file.tfile.name],
214 stdout=subprocess.PIPE)
215 source_md5sum = process.communicate()[0].decode().split(" ")[0]
216
217 logging.debug("%s %s (verified)" % (tfile_md5sum, path_random_file))
218 logging.debug("%s %s (source)"
219 % (source_md5sum, random_source_file.tfile.name))
220
221 # Clean the target file
222 os.remove(path_random_file)
223
224 # verify the md5sum
225 if tfile_md5sum == source_md5sum:
226 print("PASS: READING TEST: %s passes md5sum comparison."
227 % path_random_file)
228 else:
229 # failed in the reading test
230 # tell plainbox the failure code
231 logging.warning("FAIL: READING TEST: %s failed in md5sum comparison."
232 % path_random_file)
233 sys.exit(1)
234
235
236def write_test(random_file):
237 """perform a writing test."""
238 logging.debug("===================")
239 logging.debug("writing test begins")
240 logging.debug("===================")
241
242 write_speed_list = []
243 for idx in range(REPETITION_NUM):
244 write_speed_list.append(write_test_unit(random_file, str(idx)))
245 average_speed = sum(write_speed_list)/REPETITION_NUM
246 file_size_in_mb = RANDOM_FILE_SIZE / (1024*1024)
247 print("Average writing speed is: {:.3f} MB/s "
248 "({}x{} MB files were written)".format(
249 average_speed, REPETITION_NUM, file_size_in_mb))
250
251
252def write_test_unit(random_file, idx=""):
253 """
254 perform the writing test.
255
256 :param random_file: a RndomData object created to be written
257 :return: a float in MB/s to denote writing speed
258 """
259 target_file = os.path.join(
260 FOLDER_TO_MOUNT, os.path.basename(random_file.tfile.name)) + idx
261 process = subprocess.Popen([
262 'dd', 'if=' + random_file.tfile.name, 'of=' + target_file],
263 stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
264 logging.debug("Apply command: %s" % process.args)
265 # will get something like
266 # ['2048+1 records in', '2048+1 records out',
267 # '1049076 bytes (1.0 MB) copied, 0.00473357 s, 222 MB/s', '']
268 list_dd_message = process.communicate()[0].decode().split("\n")
269 logging.debug(list_dd_message)
270 logging.debug(get_md5sum(target_file))
271
272 try:
273 dd_speed = float(list_dd_message[2].split(" ")[-2])
274 print("PASS: WRITING TEST: %s" % target_file)
275 except:
276 # Example:
277 # ['dd: writing to ‘/tmp/tmp08osy45j/tmpnek46on30’: Input/output error'
278 # , '38913+0 records in', '38912+0 records out', '19922944 bytes
279 # (20 MB) copied, 99.647 s, 200 kB/s', '']
280 print("ERROR: {}".format(list_dd_message))
281 sys.exit(1)
282
283 return dd_speed
284
285
286@contextlib.contextmanager
287def gen_random_file():
288 """
289 generate a random file which size is RANDOM_FILE_SIZE.
290
291 :return: a RandomData object
292 """
293 logging.debug("generating a random file")
294
295 try:
296 # 1048576 = 1024 * 1024
297 # we are going to generate a 1M file
298 random_file = RandomData(RANDOM_FILE_SIZE)
299 # flush the remaining data in the memory buffer
300 # otherwise the md5sum will be different if you
301 # check it manually from your shell command md5sum
302 random_file.tfile.file.flush()
303
304 yield random_file
305
306 finally:
307 logging.info("Remove temporary folders and files.")
308 # delete the mount folder
309 try:
310 os.rmdir(FOLDER_TO_MOUNT)
311 except OSError:
312 logging.warning("Failed to remove %s (mount folder not empty)."
313 % FOLDER_TO_MOUNT)
314 # delete the random file (source file of a writing test)
315 os.unlink(random_file.tfile.name)
316
317
318def get_md5sum(file_to_check):
319 """
320 get md5sum of file_to_check.
321
322 :param file_to_check: the absolute path of a file to access its md5sum
323 :return: md5sum as a string
324 """
325 try:
326 # return the md5sum of the temp file
327 process = subprocess.Popen(['md5sum', file_to_check],
328 stdout=subprocess.PIPE)
329 # something like
330 # (b'07bc8f96b7c7dba2c1f3eb2f7dd50541 /tmp/tmp9jnuv329\n', None)
331 # will be returned by communicate() in this case
332 md5sum = process.communicate()[0].decode().split(" ")[0]
333
334 if md5sum:
335 logging.debug("MD5SUM: of %s \t\t\t\t\t%s"
336 % (file_to_check, md5sum))
337 return md5sum
338 else:
339 logging.error("Could not found file to check its MD5SUM. \
340 Check the folder permission?")
341 sys.exit(1)
342
343 except OSError as e:
344 if e.errno == errno.ENOENT:
345 logging.error("%s info file was not found. \
346 Did the insertion test was run successfully?"
347 % USB_INSERT_INFO)
348 sys.exit(1)
349
350
351if __name__ == "__main__":
352 run_read_write_test()
diff --git a/setup.py b/setup.py
index c95ea76..dc1aa37 100755
--- a/setup.py
+++ b/setup.py
@@ -82,6 +82,8 @@ setup(
82 "checkbox_support.scripts.fwts_test:main"),82 "checkbox_support.scripts.fwts_test:main"),
83 ("checkbox-support-network="83 ("checkbox-support-network="
84 "checkbox_support.scripts.network:main"),84 "checkbox_support.scripts.network:main"),
85 ("checkbox-support-usb_read_write="
86 "checkbox_support.scripts.usb_read_write:run_read_write_test"),
85 ],87 ],
86 },88 },
87)89)

Subscribers

People subscribed via source and target branches