Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(263)

Side by Side Diff: client/tests/kvm/test_setup.py

Issue 6883035: Merge remote branch 'autotest-upstream/master' into autotest-merge (Closed) Base URL: ssh://gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 """ 1 """
2 Library to perform pre/post test setup for KVM autotest. 2 Library to perform pre/post test setup for KVM autotest.
3 """ 3 """
4 import os, shutil, tempfile, re, ConfigParser, glob, inspect 4 import os, logging
5 import logging, time
6 from autotest_lib.client.common_lib import error 5 from autotest_lib.client.common_lib import error
7 from autotest_lib.client.bin import utils 6 from autotest_lib.client.bin import utils
8 7
9 8
10 @error.context_aware
11 def cleanup(dir):
12 """
13 If dir is a mountpoint, do what is possible to unmount it. Afterwards,
14 try to remove it.
15
16 @param dir: Directory to be cleaned up.
17 """
18 error.context("cleaning up unattended install directory %s" % dir)
19 if os.path.ismount(dir):
20 utils.run('fuser -k %s' % dir, ignore_status=True)
21 utils.run('umount %s' % dir)
22 if os.path.isdir(dir):
23 shutil.rmtree(dir)
24
25
26 @error.context_aware
27 def clean_old_image(image):
28 """
29 Clean a leftover image file from previous processes. If it contains a
30 mounted file system, do the proper cleanup procedures.
31
32 @param image: Path to image to be cleaned up.
33 """
34 error.context("cleaning up old leftover image %s" % image)
35 if os.path.exists(image):
36 mtab = open('/etc/mtab', 'r')
37 mtab_contents = mtab.read()
38 mtab.close()
39 if image in mtab_contents:
40 utils.run('fuser -k %s' % image, ignore_status=True)
41 utils.run('umount %s' % image)
42 os.remove(image)
43
44
45 def display_attributes(instance):
46 """
47 Inspects a given class instance attributes and displays them, convenient
48 for debugging.
49 """
50 logging.debug("Attributes set:")
51 for member in inspect.getmembers(instance):
52 name, value = member
53 attribute = getattr(instance, name)
54 if not (name.startswith("__") or callable(attribute) or not value):
55 logging.debug(" %s: %s", name, value)
56
57
58 class Disk(object):
59 """
60 Abstract class for Disk objects, with the common methods implemented.
61 """
62 def __init__(self):
63 self.path = None
64
65
66 def setup_answer_file(self, filename, contents):
67 utils.open_write_close(os.path.join(self.mount, filename), contents)
68
69
70 def copy_to(self, src):
71 logging.debug("Copying %s to disk image mount", src)
72 dst = os.path.join(self.mount, os.path.basename(src))
73 if os.path.isdir(src):
74 shutil.copytree(src, dst)
75 elif os.path.isfile(src):
76 shutil.copyfile(src, dst)
77
78
79 def close(self):
80 os.chmod(self.path, 0755)
81 cleanup(self.mount)
82 logging.debug("Disk %s successfuly set", self.path)
83
84
85 class FloppyDisk(Disk):
86 """
87 Represents a 1.44 MB floppy disk. We can copy files to it, and setup it in
88 convenient ways.
89 """
90 @error.context_aware
91 def __init__(self, path, qemu_img_binary, tmpdir):
92 error.context("Creating unattended install floppy image %s" % path)
93 self.tmpdir = tmpdir
94 self.mount = tempfile.mkdtemp(prefix='floppy_', dir=self.tmpdir)
95 self.virtio_mount = None
96 self.path = path
97 clean_old_image(path)
98 if not os.path.isdir(os.path.dirname(path)):
99 os.makedirs(os.path.dirname(path))
100
101 try:
102 c_cmd = '%s create -f raw %s 1440k' % (qemu_img_binary, path)
103 utils.run(c_cmd)
104 f_cmd = 'mkfs.msdos -s 1 %s' % path
105 utils.run(f_cmd)
106 m_cmd = 'mount -o loop,rw %s %s' % (path, self.mount)
107 utils.run(m_cmd)
108 except error.CmdError, e:
109 cleanup(self.mount)
110 raise
111
112
113 def _copy_virtio_drivers(self, virtio_floppy):
114 """
115 Copy the virtio drivers on the virtio floppy to the install floppy.
116
117 1) Mount the floppy containing the viostor drivers
118 2) Copy its contents to the root of the install floppy
119 """
120 virtio_mount = tempfile.mkdtemp(prefix='virtio_floppy_',
121 dir=self.tmpdir)
122
123 pwd = os.getcwd()
124 try:
125 m_cmd = 'mount -o loop %s %s' % (virtio_floppy, virtio_mount)
126 utils.run(m_cmd)
127 os.chdir(virtio_mount)
128 path_list = glob.glob('*')
129 for path in path_list:
130 self.copy_to(path)
131 finally:
132 os.chdir(pwd)
133 cleanup(virtio_mount)
134
135
136 def setup_virtio_win2003(self, virtio_floppy, virtio_oemsetup_id):
137 """
138 Setup the install floppy with the virtio storage drivers, win2003 style.
139
140 Win2003 and WinXP depend on the file txtsetup.oem file to install
141 the virtio drivers from the floppy, which is a .ini file.
142 Process:
143
144 1) Copy the virtio drivers on the virtio floppy to the install floppy
145 2) Parse the ini file with config parser
146 3) Modify the identifier of the default session that is going to be
147 executed on the config parser object
148 4) Re-write the config file to the disk
149 """
150 self._copy_virtio_drivers(virtio_floppy)
151 txtsetup_oem = os.path.join(self.mount, 'txtsetup.oem')
152 if not os.path.isfile(txtsetup_oem):
153 raise IOError('File txtsetup.oem not found on the install '
154 'floppy. Please verify if your floppy virtio '
155 'driver image has this file')
156 parser = ConfigParser.ConfigParser()
157 parser.read(txtsetup_oem)
158 if not parser.has_section('Defaults'):
159 raise ValueError('File txtsetup.oem does not have the session '
160 '"Defaults". Please check txtsetup.oem')
161 default_driver = parser.get('Defaults', 'SCSI')
162 if default_driver != virtio_oemsetup_id:
163 parser.set('Defaults', 'SCSI', virtio_oemsetup_id)
164 fp = open(txtsetup_oem, 'w')
165 parser.write(fp)
166 fp.close()
167
168
169 def setup_virtio_win2008(self, virtio_floppy):
170 """
171 Setup the install floppy with the virtio storage drivers, win2008 style.
172
173 Win2008, Vista and 7 require people to point out the path to the drivers
174 on the unattended file, so we just need to copy the drivers to the
175 driver floppy disk.
176 Process:
177
178 1) Copy the virtio drivers on the virtio floppy to the install floppy
179 """
180 self._copy_virtio_drivers(virtio_floppy)
181
182
183 class CdromDisk(Disk):
184 """
185 Represents a CDROM disk that we can master according to our needs.
186 """
187 def __init__(self, path, tmpdir):
188 self.mount = tempfile.mkdtemp(prefix='cdrom_unattended_', dir=tmpdir)
189 self.path = path
190 clean_old_image(path)
191 if not os.path.isdir(os.path.dirname(path)):
192 os.makedirs(os.path.dirname(path))
193
194
195 @error.context_aware
196 def close(self):
197 error.context("Creating unattended install CD image %s" % self.path)
198 g_cmd = ('mkisofs -o %s -max-iso9660-filenames '
199 '-relaxed-filenames -D --input-charset iso8859-1 '
200 '%s' % (self.path, self.mount))
201 utils.run(g_cmd)
202
203 os.chmod(self.path, 0755)
204 cleanup(self.mount)
205 logging.debug("unattended install CD image %s successfuly created",
206 self.path)
207
208
209 class UnattendedInstallConfig(object):
210 """
211 Creates a floppy disk image that will contain a config file for unattended
212 OS install. The parameters to the script are retrieved from environment
213 variables.
214 """
215 def __init__(self, test, params):
216 """
217 Sets class atributes from test parameters.
218
219 @param test: KVM test object.
220 @param params: Dictionary with test parameters.
221 """
222 root_dir = test.bindir
223 images_dir = os.path.join(root_dir, 'images')
224 self.deps_dir = os.path.join(root_dir, 'deps')
225 self.unattended_dir = os.path.join(root_dir, 'unattended')
226
227 attributes = ['kernel_args', 'finish_program', 'cdrom_cd1',
228 'unattended_file', 'medium', 'url', 'kernel', 'initrd',
229 'nfs_server', 'nfs_dir', 'install_virtio', 'floppy',
230 'cdrom_unattended', 'boot_path', 'extra_params',
231 'qemu_img_binary', 'cdkey', 'finish_program']
232
233 for a in attributes:
234 setattr(self, a, params.get(a, ''))
235
236 if self.install_virtio == 'yes':
237 v_attributes = ['virtio_floppy', 'virtio_storage_path',
238 'virtio_network_path', 'virtio_oemsetup_id',
239 'virtio_network_installer']
240 for va in v_attributes:
241 setattr(self, va, params.get(va, ''))
242
243 self.tmpdir = test.tmpdir
244
245 if getattr(self, 'unattended_file'):
246 self.unattended_file = os.path.join(root_dir, self.unattended_file)
247
248 if getattr(self, 'finish_program'):
249 self.finish_program = os.path.join(root_dir, self.finish_program)
250
251 if getattr(self, 'qemu_img_binary'):
252 if not os.path.isfile(getattr(self, 'qemu_img_binary')):
253 self.qemu_img_binary = os.path.join(root_dir,
254 self.qemu_img_binary)
255
256 if getattr(self, 'cdrom_cd1'):
257 self.cdrom_cd1 = os.path.join(root_dir, self.cdrom_cd1)
258 self.cdrom_cd1_mount = tempfile.mkdtemp(prefix='cdrom_cd1_',
259 dir=self.tmpdir)
260 if self.medium == 'nfs':
261 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_',
262 dir=self.tmpdir)
263
264 if getattr(self, 'floppy'):
265 self.floppy = os.path.join(root_dir, self.floppy)
266 if not os.path.isdir(os.path.dirname(self.floppy)):
267 os.makedirs(os.path.dirname(self.floppy))
268
269 self.image_path = os.path.dirname(self.kernel)
270
271
272 @error.context_aware
273 def render_answer_file(self):
274 """
275 Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey
276 provided for this test and replace the KVM_TEST_MEDIUM with
277 the tree url or nfs address provided for this test.
278
279 @return: Answer file contents
280 """
281 error.base_context('Rendering final answer file')
282 error.context('Reading answer file %s' % self.unattended_file)
283 unattended_contents = open(self.unattended_file).read()
284 dummy_cdkey_re = r'\bKVM_TEST_CDKEY\b'
285 if re.search(dummy_cdkey_re, unattended_contents):
286 if self.cdkey:
287 unattended_contents = re.sub(dummy_cdkey_re, self.cdkey,
288 unattended_contents)
289 else:
290 print ("WARNING: 'cdkey' required but not specified for "
291 "this unattended installation")
292
293 dummy_medium_re = r'\bKVM_TEST_MEDIUM\b'
294 if self.medium == "cdrom":
295 content = "cdrom"
296 elif self.medium == "url":
297 content = "url --url %s" % self.url
298 elif self.medium == "nfs":
299 content = "nfs --server=%s --dir=%s" % (self.nfs_server,
300 self.nfs_dir)
301 else:
302 raise ValueError("Unexpected installation medium %s" % self.url)
303
304 unattended_contents = re.sub(dummy_medium_re, content,
305 unattended_contents)
306
307 def replace_virtio_key(contents, dummy_re, attribute_name):
308 """
309 Replace a virtio dummy string with contents.
310
311 If install_virtio is not set, replace it with a dummy string.
312
313 @param contents: Contents of the unattended file
314 @param dummy_re: Regular expression used to search on the.
315 unattended file contents.
316 @param env: Name of the environment variable.
317 """
318 dummy_path = "C:"
319 driver = getattr(self, attribute_name, '')
320
321 if re.search(dummy_re, contents):
322 if self.install_virtio == "yes":
323 if driver.endswith("msi"):
324 driver = 'msiexec /passive /package ' + driver
325 else:
326 try:
327 # Let's escape windows style paths properly
328 drive, path = driver.split(":")
329 driver = drive + ":" + re.escape(path)
330 except:
331 pass
332 contents = re.sub(dummy_re, driver, contents)
333 else:
334 contents = re.sub(dummy_re, dummy_path, contents)
335 return contents
336
337 vdict = {r'\bKVM_TEST_STORAGE_DRIVER_PATH\b':
338 'virtio_storage_path',
339 r'\bKVM_TEST_NETWORK_DRIVER_PATH\b':
340 'virtio_network_path',
341 r'\bKVM_TEST_VIRTIO_NETWORK_INSTALLER\b':
342 'virtio_network_installer_path'}
343
344 for vkey in vdict:
345 unattended_contents = replace_virtio_key(
346 contents=unattended_contents,
347 dummy_re=vkey,
348 attribute_name=vdict[vkey])
349
350 logging.debug("Unattended install contents:")
351 for line in unattended_contents.splitlines():
352 logging.debug(line)
353 return unattended_contents
354
355
356 def setup_boot_disk(self):
357 answer_contents = self.render_answer_file()
358
359 if self.unattended_file.endswith('.sif'):
360 dest_fname = 'winnt.sif'
361 setup_file = 'winnt.bat'
362 boot_disk = FloppyDisk(self.floppy, self.qemu_img_binary,
363 self.tmpdir)
364 boot_disk.setup_answer_file(dest_fname, answer_contents)
365 setup_file_path = os.path.join(self.unattended_dir, setup_file)
366 boot_disk.copy_to(setup_file_path)
367 if self.install_virtio == "yes":
368 boot_disk.setup_virtio_win2003(self.virtio_floppy,
369 self.virtio_oemsetup_id)
370 boot_disk.copy_to(self.finish_program)
371
372 elif self.unattended_file.endswith('.ks'):
373 # Red Hat kickstart install
374 dest_fname = 'ks.cfg'
375 if self.cdrom_unattended:
376 boot_disk = CdromDisk(self.cdrom_unattended, self.tmpdir)
377 elif self.floppy:
378 boot_disk = FloppyDisk(self.floppy, self.qemu_img_binary,
379 self.tmpdir)
380 else:
381 raise ValueError("Neither cdrom_unattended nor floppy set "
382 "on the config file, please verify")
383 boot_disk.setup_answer_file(dest_fname, answer_contents)
384
385 elif self.unattended_file.endswith('.xml'):
386 if "autoyast" in self.extra_params:
387 # SUSE autoyast install
388 dest_fname = "autoinst.xml"
389 if self.cdrom_unattended:
390 boot_disk = CdromDisk(self.cdrom_unattended)
391 elif self.floppy:
392 boot_disk = FloppyDisk(self.floppy, self.qemu_img_binary,
393 self.tmpdir)
394 else:
395 raise ValueError("Neither cdrom_unattended nor floppy set "
396 "on the config file, please verify")
397 boot_disk.setup_answer_file(dest_fname, answer_contents)
398
399 else:
400 # Windows unattended install
401 dest_fname = "autounattend.xml"
402 boot_disk = FloppyDisk(self.floppy, self.qemu_img_binary,
403 self.tmpdir)
404 boot_disk.setup_answer_file(dest_fname, answer_contents)
405 if self.install_virtio == "yes":
406 boot_disk.setup_virtio_win2008(self.virtio_floppy)
407 boot_disk.copy_to(self.finish_program)
408
409 else:
410 raise ValueError('Unknown answer file type: %s' %
411 self.unattended_file)
412
413 boot_disk.close()
414
415
416 @error.context_aware
417 def setup_cdrom(self):
418 """
419 Mount cdrom and copy vmlinuz and initrd.img.
420 """
421 error.context("Copying vmlinuz and initrd.img from install cdrom %s" %
422 self.cdrom_cd1)
423 m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' %
424 (self.cdrom_cd1, self.cdrom_cd1_mount))
425 utils.run(m_cmd)
426
427 try:
428 if not os.path.isdir(self.image_path):
429 os.makedirs(self.image_path)
430 kernel_fetch_cmd = ("cp %s/%s/%s %s" %
431 (self.cdrom_cd1_mount, self.boot_path,
432 os.path.basename(self.kernel), self.kernel))
433 utils.run(kernel_fetch_cmd)
434 initrd_fetch_cmd = ("cp %s/%s/%s %s" %
435 (self.cdrom_cd1_mount, self.boot_path,
436 os.path.basename(self.initrd), self.initrd))
437 utils.run(initrd_fetch_cmd)
438 finally:
439 cleanup(self.cdrom_cd1_mount)
440
441
442 @error.context_aware
443 def setup_url(self):
444 """
445 Download the vmlinuz and initrd.img from URL.
446 """
447 error.context("downloading vmlinuz and initrd.img from %s" % self.url)
448 os.chdir(self.image_path)
449 kernel_fetch_cmd = "wget -q %s/%s/%s" % (self.url, self.boot_path,
450 os.path.basename(self.kernel))
451 initrd_fetch_cmd = "wget -q %s/%s/%s" % (self.url, self.boot_path,
452 os.path.basename(self.initrd))
453
454 if os.path.exists(self.kernel):
455 os.remove(self.kernel)
456 if os.path.exists(self.initrd):
457 os.remove(self.initrd)
458
459 utils.run(kernel_fetch_cmd)
460 utils.run(initrd_fetch_cmd)
461
462
463 def setup_nfs(self):
464 """
465 Copy the vmlinuz and initrd.img from nfs.
466 """
467 error.context("copying the vmlinuz and initrd.img from NFS share")
468
469 m_cmd = ("mount %s:%s %s -o ro" %
470 (self.nfs_server, self.nfs_dir, self.nfs_mount))
471 utils.run(m_cmd)
472
473 try:
474 kernel_fetch_cmd = ("cp %s/%s/%s %s" %
475 (self.nfs_mount, self.boot_path,
476 os.path.basename(self.kernel), self.image_path))
477 utils.run(kernel_fetch_cmd)
478 initrd_fetch_cmd = ("cp %s/%s/%s %s" %
479 (self.nfs_mount, self.boot_path,
480 os.path.basename(self.initrd), self.image_path))
481 utils.run(initrd_fetch_cmd)
482 finally:
483 cleanup(self.nfs_mount)
484
485
486 def setup(self):
487 """
488 Configure the environment for unattended install.
489
490 Uses an appropriate strategy according to each install model.
491 """
492 logging.info("Starting unattended install setup")
493 display_attributes(self)
494
495 if self.unattended_file and (self.floppy or self.cdrom_unattended):
496 self.setup_boot_disk()
497 if self.medium == "cdrom":
498 if self.kernel and self.initrd:
499 self.setup_cdrom()
500 elif self.medium == "url":
501 self.setup_url()
502 elif self.medium == "nfs":
503 self.setup_nfs()
504 else:
505 raise ValueError("Unexpected installation method %s" %
506 self.medium)
507
508
509 class HugePageConfig(object): 9 class HugePageConfig(object):
510 def __init__(self, params): 10 def __init__(self, params):
511 """ 11 """
512 Gets environment variable values and calculates the target number 12 Gets environment variable values and calculates the target number
513 of huge memory pages. 13 of huge memory pages.
514 14
515 @param params: Dict like object containing parameters for the test. 15 @param params: Dict like object containing parameters for the test.
516 """ 16 """
517 self.vms = len(params.objects("vms")) 17 self.vms = len(params.objects("vms"))
518 self.mem = int(params.get("mem")) 18 self.mem = int(params.get("mem"))
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
598 98
599 @error.context_aware 99 @error.context_aware
600 def cleanup(self): 100 def cleanup(self):
601 error.context("trying to dealocate hugepage memory") 101 error.context("trying to dealocate hugepage memory")
602 try: 102 try:
603 utils.system("umount %s" % self.hugepage_path) 103 utils.system("umount %s" % self.hugepage_path)
604 except error.CmdError: 104 except error.CmdError:
605 return 105 return
606 utils.system("echo 0 > %s" % self.kernel_hp_file) 106 utils.system("echo 0 > %s" % self.kernel_hp_file)
607 logging.debug("Hugepage memory successfuly dealocated") 107 logging.debug("Hugepage memory successfuly dealocated")
608
609
610 class EnospcConfig(object):
611 """
612 Performs setup for the test enospc. This is a borg class, similar to a
613 singleton. The idea is to keep state in memory for when we call cleanup()
614 on postprocessing.
615 """
616 __shared_state = {}
617 def __init__(self, test, params):
618 self.__dict__ = self.__shared_state
619 root_dir = test.bindir
620 self.tmpdir = test.tmpdir
621 self.qemu_img_binary = params.get('qemu_img_binary')
622 if not os.path.isfile(self.qemu_img_binary):
623 self.qemu_img_binary = os.path.join(root_dir,
624 self.qemu_img_binary)
625 self.raw_file_path = os.path.join(self.tmpdir, 'enospc.raw')
626 # Here we're trying to choose fairly explanatory names so it's less
627 # likely that we run in conflict with other devices in the system
628 self.vgtest_name = params.get("vgtest_name")
629 self.lvtest_name = params.get("lvtest_name")
630 self.lvtest_device = "/dev/%s/%s" % (self.vgtest_name, self.lvtest_name)
631 image_dir = os.path.dirname(params.get("image_name"))
632 self.qcow_file_path = os.path.join(image_dir, 'enospc.qcow2')
633 try:
634 getattr(self, 'loopback')
635 except AttributeError:
636 self.loopback = ''
637
638
639 @error.context_aware
640 def setup(self):
641 logging.debug("Starting enospc setup")
642 error.context("performing enospc setup")
643 display_attributes(self)
644 # Double check if there aren't any leftovers
645 self.cleanup()
646 try:
647 utils.run("%s create -f raw %s 10G" %
648 (self.qemu_img_binary, self.raw_file_path))
649 # Associate a loopback device with the raw file.
650 # Subject to race conditions, that's why try here to associate
651 # it with the raw file as quickly as possible
652 l_result = utils.run("losetup -f")
653 utils.run("losetup -f %s" % self.raw_file_path)
654 self.loopback = l_result.stdout.strip()
655 # Add the loopback device configured to the list of pvs
656 # recognized by LVM
657 utils.run("pvcreate %s" % self.loopback)
658 utils.run("vgcreate %s %s" % (self.vgtest_name, self.loopback))
659 # Create an lv inside the vg with starting size of 200M
660 utils.run("lvcreate -L 200M -n %s %s" %
661 (self.lvtest_name, self.vgtest_name))
662 # Create a 10GB qcow2 image in the logical volume
663 utils.run("%s create -f qcow2 %s 10G" %
664 (self.qemu_img_binary, self.lvtest_device))
665 # Let's symlink the logical volume with the image name that autotest
666 # expects this device to have
667 os.symlink(self.lvtest_device, self.qcow_file_path)
668 except Exception, e:
669 self.cleanup()
670 raise
671
672 @error.context_aware
673 def cleanup(self):
674 error.context("performing enospc cleanup")
675 if os.path.isfile(self.lvtest_device):
676 utils.run("fuser -k %s" % self.lvtest_device)
677 time.sleep(2)
678 l_result = utils.run("lvdisplay")
679 # Let's remove all volumes inside the volume group created
680 if self.lvtest_name in l_result.stdout:
681 utils.run("lvremove -f %s" % self.lvtest_device)
682 # Now, removing the volume group itself
683 v_result = utils.run("vgdisplay")
684 if self.vgtest_name in v_result.stdout:
685 utils.run("vgremove -f %s" % self.vgtest_name)
686 # Now, if we can, let's remove the physical volume from lvm list
687 if self.loopback:
688 p_result = utils.run("pvdisplay")
689 if self.loopback in p_result.stdout:
690 utils.run("pvremove -f %s" % self.loopback)
691 l_result = utils.run('losetup -a')
692 if self.loopback and (self.loopback in l_result.stdout):
693 try:
694 utils.run("losetup -d %s" % self.loopback)
695 except error.CmdError:
696 logging.error("Failed to liberate loopback %s", self.loopback)
697 if os.path.islink(self.qcow_file_path):
698 os.remove(self.qcow_file_path)
699 if os.path.isfile(self.raw_file_path):
700 os.remove(self.raw_file_path)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698