cc1  v2.1
CC1 source code docs
 All Classes Namespaces Files Functions Variables Pages
image.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 # @COPYRIGHT_begin
3 #
4 # Copyright [2010-2014] Institute of Nuclear Physics PAN, Krakow, Poland
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # @COPYRIGHT_end
19 
20 ##
21 # @package src.cm.utils.threads.image
22 #
23 import subprocess
24 
25 import threading
26 import urllib2
27 from common.states import image_states
28 from common.hardware import disk_format_commands, disk_filesystems_reversed
29 import os
30 from cm.utils import log
31 import random
32 import hashlib
33 from cm.utils import message
34 
35 
36 class CreateImage(threading.Thread):
37  image = None
38  filesystem = None
39 
40  def __init__(self, image, filesystem):
41  threading.Thread.__init__(self)
42  self.image = image
43  self.filesystem = filesystem
44 
45  def run(self):
46  if os.path.exists(self.image.path):
47  self.image.state = image_states['failed']
48  self.image.save(update_fields=['state'])
49  log.error(self.image.user.id, "Destination image %d for user %d exists! Aborting creation" % (self.image.id, self.image.user.id))
50  return
51  self.image.progress = 0
52 
53  if self.format() == 'failed':
54  self.image.state = image_states['failed']
55  self.image.save(update_fields=['state'])
56  else:
57  self.image.progress = 100
58  self.image.state = image_states['ok']
59  self.image.save(update_fields=['state', 'progress'])
60 
61  log.debug(self.image.user.id, 'stage [6/6] cleaning..')
62  try:
63  os.remove('%s' % os.path.join('/var/lib/cc1/images-tmp/', os.path.split(self.image.path)[1]))
64  except Exception, e:
65  log.error(self.image.user.id, 'error remove file: %s' % str(e))
66 
67  def exec_cmd(self, args):
68  p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
69  retr_std = p.stdout.read()
70  ret = p.wait()
71  if ret:
72  retr_err = str(p.stderr.read())
73  log.error(self.image.user.id, retr_err)
74  log.error(self.image.user.id, retr_std)
75  return retr_err
76 
77  def set_progress(self, prg):
78  self.image.progress = prg
79  self.image.save(update_fields=['progress'])
80 
81  def format(self):
82 
83  if not os.path.exists(os.path.dirname(self.image.path)):
84  os.makedirs(os.path.dirname(self.image.path))
85  format_cmd = disk_format_commands[disk_filesystems_reversed[self.filesystem]].split()
86  if format_cmd:
87  tmp_path = os.path.join('/var/lib/cc1/images-tmp/', os.path.split(self.image.path)[1])
88  if not os.path.exists(os.path.dirname('/var/lib/cc1/images-tmp/')):
89  os.makedirs(os.path.dirname('/var/lib/cc1/images-tmp/'))
90  else:
91  tmp_path = str(self.image.path)
92 
93  log.debug(self.image.user.id, 'stage [1/6] truncate partition file')
94  if self.exec_cmd(['truncate', '-s', '%dM' % self.image.size, '%s' % tmp_path]):
95  return 'failed'
96  self.set_progress(random.randint(0, 15))
97 
98  if format_cmd:
99  format_cmd.append('%s' % tmp_path)
100  log.debug(self.image.user.id, 'stage [2/6] creating partition filesystem')
101  if self.exec_cmd(format_cmd):
102  return 'failed'
103  self.set_progress(random.randint(15, 50))
104 
105  log.debug(self.image.user.id, 'stage [3/6] creating disk')
106  if self.exec_cmd(['/usr/bin/ddrescue', '-S', '-o', '1048576', '%s' % tmp_path, str(self.image.path)]):
107  return 'failed'
108  self.set_progress(random.randint(50, 80))
109 
110  log.debug(self.image.user.id, 'stage [4/6] creating new partition table')
111  if self.exec_cmd(['/sbin/parted', '-s', str(self.image.path), 'mklabel', 'msdos']):
112  return 'failed'
113  self.set_progress(random.randint(80, 90))
114 
115  log.debug(self.image.user.id, 'stage [5/6] adding partition')
116  if self.exec_cmd(['/sbin/parted', '-s', str(self.image.path), 'mkpart', 'primary', '1048576b', '100%']):
117  return 'failed'
118  self.set_progress(random.randint(90, 100))
119 
120  log.info(self.image.user.id, 'disk succesfully formatted')
121 
122 
123 class DownloadImage(threading.Thread):
124  image = None
125  url = None
126  size = 0
127 
128  def __init__(self, image, url, size):
129  threading.Thread.__init__(self)
130  self.image = image
131  self.url = url
132  self.size = size
133 
134  def run(self):
135  try:
136  if self.url.startswith('/'):
137  src_image = open(self.url, 'r')
138  else:
139  src_image = urllib2.urlopen(self.url)
140  except Exception, e:
141  log.exception(self.image.user.id, "Cannot open url %s: %s" % (self.url, str(e)))
142  self.image.state = image_states['failed']
143  return
144 
145  if os.path.exists(self.image.path):
146  self.image.state = image_states['failed']
147  self.image.save(update_fields=['state'])
148  log.error(self.image.user.id, "Destination image %d for user %d exists! Aborting download" % (self.image.id, self.image.user.id))
149  return
150 
151  try:
152  dirpath = os.path.dirname(self.image.path)
153  if not os.path.exists(dirpath):
154  os.mkdir(dirpath)
155  dest_image = open(self.image.path, 'w')
156  downloaded_size = 0
157  md5sum = hashlib.md5()
158  while downloaded_size < self.size:
159  buff = src_image.read(1024 * 1024)
160  md5sum.update(buff)
161  downloaded_size += len(buff)
162  dest_image.write(buff)
163 
164  progress = int(downloaded_size * 100 / self.size)
165  if progress != self.image.progress:
166  self.image.progress = progress
167  self.image.save(update_fields=['progress'])
168 
169  dest_image.close()
170 
171  log.info(self.image.user.id, 'md5 hash of image %d is %s' % (self.image.id, md5sum.hexdigest()))
172  self.image.state = image_states['ok']
173  self.image.size = downloaded_size / (1024 * 1024)
174  self.image.save(update_fields=['progress', 'state', 'size'])
175  message.info(self.image.user.id, 'image_downloaded', {'name': self.image.name, 'md5sum': md5sum.hexdigest()})
176  except Exception, e:
177  log.exception(self.image.user.id, "Failed to download image: %s" % str(e))
178  self.image.state = image_states['failed']
179  self.image.save(update_fields=['state'])
180 
181 
182 class CopyImage(threading.Thread):
183  def __init__(self, src_image, dest_image):
184  threading.Thread.__init__(self)
185  self.src_image = src_image
186  self.dest_image = dest_image
187 
188  def run(self):
189  copied = 0
190  prev_progress = 0
191  try:
192  size = os.path.getsize(self.src_image.path)
193  dirpath = os.path.dirname(self.dest_image.path)
194  if not os.path.exists(dirpath):
195  os.mkdir(dirpath)
196  src = open(self.src_image.path, "r")
197  dst = open(self.dest_image.path, "w")
198  while 1:
199  buff = src.read(1024 * 1024) # Should be less than MTU?
200  if len(buff) > 0 and copied <= size:
201  dst.write(buff)
202  copied = copied + len(buff)
203  else:
204  break
205  # Update image information:
206  progress = 100 * copied / size
207  if progress > prev_progress:
208  prev_progress = progress
209  self.dest_image.progress = progress
210  self.dest_image.save(update_fields=['progress'])
211 
212  self.dest_image.state = image_states['ok']
213  self.dest_image.size = self.src_image.size
214  self.dest_image.save(update_fields=['progress', 'state', 'size'])
215 
216  src.close()
217  dst.close()
218  except Exception, e:
219  log.exception(self.dest_image.user.id, "Failed to copy image: %s" % str(e))
220  self.dest_image.state = image_states['failed']
221  self.dest_image.save(update_fields=['state'])
222