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 from binascii import unhexlify
20 import os
21 import uuid
22 from xml.dom import minidom
23 import thread
24 from common.hardware import disk_controllers, network_devices, video_devices
25 from common.states import image_access, image_types, image_states, group_states
26 from ec2.base.action import Action, CLMException
27 from ec2.error import InvalidAMIID, MissingParameter, InternalError, \
28  UndefinedError, InvalidParameterValue, InvalidFilter, InvalidManifest, InvalidParameter
29 from ec2.helpers.entities import Entity
30 from ec2.helpers.filters import applyEc2Filters, validateEc2Filters
31 from ec2.helpers.parse import parseFilters, parseID
32 from ec2.settings import BUCKETS_PATH
33 from ec2.settings import EC2_PRIVATE_KEY, EC2_CM_INTERFACE
34 from ec2.settings import UPLOAD_IMAGES_PATH
35 import subprocess
36 
37 """@package src.ec2.image
38 EC2 actions for images
39 
40 @copyright Copyright (c) 2012 Institute of Nuclear Physics PAS <http://www.ifj.edu.pl/>
41 @author Rafał Grzymkowski
42 @author Oleksandr Gituliar <gituliar@gmail.com>
43 @author Miłosz Zdybał
44 @author Łukasz Chrząszcz <l.chrzaszcz@gmail.com>
45 """
46 
47 
48 # Use windows if you have Windows based AMIs; otherwise leave blank.
49 # Type: String
50 # Valid Value: windows
51 PLATFORM = {
52  0: '', # unknown
53  1: '', # linux
54  2: '', # unix
55  3: 'windows', # windows
56  4: '', # mac os
57  5: '', # other
58 }
59 
60 # State of the image.
61 # Type: String
62 # Valid Values: available | pending | failed
63 STATE = {
64  0: 'available', # ok
65  1: 'failed', # locked
66  2: 'pending', # adding
67  3: 'failed', # failed
68  4: 'failed', # unavailable
69 }
70 
71 IMAGE_READ_BUFFER = 65536
72 
73 class DeregisterImage(Action):
74  def _execute(self):
75  try:
76  image_id = parseID(self.parameters['ImageId'], Entity.image)
77  if not image_id:
78  raise InvalidParameterValue
79  image_id = int(image_id)
80  except KeyError:
81  raise MissingParameter(parameter='ImageId')
82  except ValueError:
83  raise InvalidAMIID.Malformed
84 
85  try:
86  self.cluster_manager.user.system_image.delete({'system_image_id':image_id})
87  except CLMException, error:
88  if error.status == 'system_image_get' or error.status == 'image_unavailable':
89  raise InvalidAMIID.NotFound(image_id=image_id)
90  if error.status == 'image_delete':
91  raise InternalError
92  raise UndefinedError
93 
94  return {'result': 'true'}
95 
96 
97 class DescribeImages(Action):
98 
99  available_filters = ['description', 'image-id', 'name', 'state']
100 
101  def _execute(self):
102  GROUP_ACCESS = image_access['group']
103  PRIVATE_ACCESS = image_access['private']
104  PUBLIC_ACCESS = image_access['public']
105 
106  filters = parseFilters( self.parameters )
107  if not validateEc2Filters( filters, self.available_filters ):
108  raise InvalidFilter
109 
110  image_ids = []
111  for param, value in self.parameters.iteritems():
112  if param.startswith('ImageId'):
113  image_id = parseID(value, Entity.image)
114  if not image_id:
115  raise InvalidParameterValue
116  image_ids.append( image_id )
117 
118  images = []
119  for access in (PRIVATE_ACCESS, PUBLIC_ACCESS):
120 
121  access_images = self.cluster_manager.user.system_image.get_list({
122  'access': access,
123  })
124 
125 
126  for image in access_images:
127  if image_ids and str(image.get('image_id')) not in image_ids:
128  continue
129  images.append({
130  'description': image.get('description').replace('<', ' '),
131  'image-id': image.get('image_id'),
132  'is_public': 'true' if access == PUBLIC_ACCESS else 'false',
133  'name': image['name'],
134  'owner_id': image.get('user_id'),
135  'platform': PLATFORM.get(image.get('platform')),
136  'state': STATE.get(image.get('state')),
137  })
138 
139  # listowanie obrazów grupowych - one są zwracane w innej strukturze
140  access_images = self.cluster_manager.user.system_image.get_list({'access': GROUP_ACCESS})
141 
142 
143  for images_dict in access_images:
144  for image in images_dict['images']:
145  if image_ids and str(image.get('image_id')) not in image_ids:
146  continue
147  images.append({
148  'description': image.get('description').replace('<', ' '),
149  'image-id': image.get('image_id'),
150  'is_public': 'true' if access == PUBLIC_ACCESS else 'false',
151  'name': image['name'],
152  'owner_id': image.get('user_id'),
153  'platform': PLATFORM.get(image.get('platform')),
154  'state': STATE.get(image.get('state')),
155  })
156 
157 
158 
159  if filters.get('state'):
160  for state in filters['state']:
161  state = [k for k,v in STATE.iteritems() if v == STATE.get(state) ] # ?? wymaga testu
162  del filters['state']
163 
164  images = applyEc2Filters(images, filters)
165 
166  return {'images': images}
167 
168 class RegisterImage(Action):
169  def _execute(self):
170  try:
171  user_name = self.parameters['AWSAccessKeyId']
172  manifest_url = self.parameters['ImageLocation']
173  except KeyError, e:
174  raise MissingParameter(parameter=e.message)
175 
176  if manifest_url.startswith('/'):
177  manifest_url = manifest_url[1:]
178 
179  manifest_path = os.path.join(BUCKETS_PATH, user_name, manifest_url)
180  dom = minidom.parse(manifest_path)
181  manifest_elem = dom.getElementsByTagName('manifest')[0]
182 
183  size = manifest_elem.getElementsByTagName('image')[0].getElementsByTagName('size')[0].childNodes[0].data
184  name = manifest_elem.getElementsByTagName('image')[0].getElementsByTagName('name')[0].childNodes[0].data
185 
186  response = self.cluster_manager.user.system_image.create({'description': 'Uploaded by EC2',
187  'name': name,
188  'disk_controller': disk_controllers['virtio'],
189  'network_device': network_devices['rtl8139'],
190  'platform': 0,
191  'video_device': video_devices['vga'],
192  'size': size})
193 
194  thread.start_new_thread(merge_and_upload, (self.cluster_manager, self.parameters, response['system_image_id']))
195 
196  return response
197 
198 def merge_and_upload(cluster_manager, parameters, system_image_id):
199  try:
200  manifest_url = parameters['ImageLocation']
201  except KeyError, e:
202  raise MissingParameter(parameter=e.message)
203 
204  # delete slash at the beginning, to join that to BUCKETS_PATH
205  if manifest_url.startswith('/'):
206  manifest_url = manifest_url[1:]
207 
208  user_name = parameters.get('AWSAccessKeyId')
209 
210  manifest_path = os.path.join(BUCKETS_PATH, user_name, manifest_url)
211 
212  if os.path.abspath(manifest_path) != manifest_path:
213  raise InvalidParameter
214 
215  parts_directory = os.path.dirname(manifest_path)
216 
217  # parse manifest
218  dom = minidom.parse(manifest_path)
219  manifest_elem = dom.getElementsByTagName('manifest')[0]
220 
221  # ====== collecting image parts, encrypted keys and ivs ======
222 
223  parts = []
224 
225  parts_list = manifest_elem.getElementsByTagName('filename')
226  for part_elem in parts_list:
227  nodes = part_elem.childNodes
228  for node in nodes:
229  if node.nodeType == node.TEXT_NODE:
230  parts.append(node.data)
231 
232  encrypted_key_elem = manifest_elem.getElementsByTagName('ec2_encrypted_key')[0]
233  encrypted_iv_elem = manifest_elem.getElementsByTagName('ec2_encrypted_iv')[0]
234 
235  encrypted_key = encrypted_key_elem.firstChild.nodeValue
236  encrypted_iv = encrypted_iv_elem.firstChild.nodeValue
237 
238  # COLLECT PARTS ================================================================
239  # encrypted_filename = manifest_path.replace('.manifest.xml', '.enc.tar.gz')
240  result_filename = manifest_path.replace('.manifest.xml', '')
241 
242  #result_file = open(result_filename,'w')
243 
244  part_files = []
245  if len(parts) > 0:
246  for part in parts:
247  part_filename = os.path.join(parts_directory, part)
248  if not part_filename.startswith(parts_directory):
249  raise InvalidManifest
250 
251  part_files.append(part_filename)
252  # ==============================================================================
253 
254  # DECRYPT KEY AND IV ===========================================================
255  user_priv_key = RSA.load_key(EC2_PRIVATE_KEY)
256  key = user_priv_key.private_decrypt(unhexlify(encrypted_key),
257  RSA.pkcs1_padding)
258  iv = user_priv_key.private_decrypt(unhexlify(encrypted_iv),
259  RSA.pkcs1_padding)
260  # ==============================================================================
261 
262  # SUBPROCESSES =================================================================
263  cat_process = subprocess.Popen(['cat'] + part_files, stdout=subprocess.PIPE)
264  openssl_process = subprocess.Popen(['openssl', 'enc', '-d', '-aes-128-cbc', '-K', key, '-iv', iv],
265  stdin=cat_process.stdout, stdout=subprocess.PIPE)
266  uncompress_process = subprocess.Popen(['gzip', '-c', '-d'], stdin=openssl_process.stdout,
267  stdout=subprocess.PIPE)
268  untar_process = subprocess.Popen(['tar', '-xvm', '-C', parts_directory], stdin=uncompress_process.stdout, stdout=subprocess.PIPE)
269  # ==============================================================================
270 
271  # WAIT FOR COMMAND CHAIN ============================================================
272  untar_process.wait()
273 
274  # CLEANUP ===========================================================================
275  #result_file.close()
276 
277  # CREATING LINK =====================================================================
278  link_name = user_name + str(uuid.uuid4())
279  link_path = os.path.join(UPLOAD_IMAGES_PATH, link_name)
280 
281  os.symlink(result_filename, link_path)
282 
283  # GENERATING URL FOR CM =============================================================
284  image_url = EC2_CM_INTERFACE + '?image_name=' + link_name
285 
286  # request CM download =================
287  cluster_manager.user.system_image.download_ec2({'system_image_id': system_image_id,
288  'path': image_url})
289