4f3f0e7a66e6b3fbd5a7a20a76012bb5d825833d
[osm/devops.git] / tools / vmware_ovf_upload.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2016-2017 VMware Inc.
5 # This file is part of ETSI OSM
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact: osslegalrouting@vmware.com
22 ##
23
24
25 from xml.etree import ElementTree as XmlElementTree
26 from pyvcloud.vcd.client import BasicLoginCredentials,Client
27 from pyvcloud.vcd.vdc import VDC
28 from pyvcloud.vcd.org import Org
29 import sys,os
30 import logging
31 import requests
32 import time
33 import re
34 import hashlib
35 from progressbar import Percentage, Bar, ETA, FileTransferSpeed, ProgressBar
36
37
38 API_VERSION = '5.6'
39
40 class vCloudconfig(object):
41 def __init__(self, host=None, user=None, password=None,orgname=None, logger=None):
42 self.url = host
43 self.user = user
44 self.password = password
45 self.org = orgname
46 self.logger = logging.getLogger('vmware_ovf_upload')
47 self.logger.setLevel(10)
48
49 def connect(self):
50 """ Method connect as normal user to vCloud director.
51
52 Returns:
53 The return vca object that letter can be used to connect to vCloud director as admin for VDC
54 """
55
56 try:
57 self.logger.debug("Logging in to a vcd {} as user {}".format(self.org,
58 self.user))
59 client = Client(self.url, verify_ssl_certs=False)
60 client.set_credentials(BasicLoginCredentials(self.user, self.org, self.password))
61 except:
62 raise Exception("Can't connect to a vCloud director org: "
63 "{} as user: {}".format(self.org, self.user))
64
65 return client
66
67 def get_catalog_id_from_path(self, catalog_name=None, path=None, progress=False):
68 """
69 Args
70 catalog - catalog name to be created
71 path: - valid path to OVF file.
72 progress - boolean progress bar show progress bar.
73
74 Return: if image uploaded correct method will provide image catalog UUID.
75 """
76 if not path:
77 raise Exception("Image path can't be None.")
78
79 if not os.path.isfile(path):
80 raise Exception("Can't read file. File not found.")
81
82 if not os.access(path, os.R_OK):
83 raise Exception("Can't read file. Check file permission to read.")
84
85 self.logger.debug("get_catalog_id_from_path() client requesting {} ".format(path))
86
87 dirpath, filename = os.path.split(path)
88 flname, file_extension = os.path.splitext(path)
89 if file_extension != '.ovf':
90 self.logger.debug("Wrong file extension {} connector support only OVF container.".format(file_extension))
91 raise Exception("Wrong container. vCloud director supports only OVF.")
92
93 self.logger.debug("File name {} Catalog Name {} file path {} ".format(filename,
94 catalog_name,
95 path))
96 try:
97 client = self.connect()
98 if not client:
99 raise Exception("Failed to connect vCD")
100 org = Org(client, resource=client.get_org())
101 catalogs = org.list_catalogs()
102 except Exception as exp:
103 self.logger.debug("Failed get catalogs() with Exception {} ".format(exp))
104 raise Exception("Failed get catalogs() with Exception {} ".format(exp))
105
106 if len(catalogs) == 0:
107 self.logger.info("Creating a new catalog entry {} in vcloud director".format(catalog_name))
108 result = org.create_catalog(catalog_name, catalog_name)
109 if result is None:
110 raise Exception("Failed to create new catalog {} ".format(catalog_name))
111 result = self.upload_ovf(org=org, catalog_name=catalog_name, image_name=filename.split(".")[0],
112 media_file_name=path, description='medial_file_name', progress=progress)
113 if not result:
114 raise Exception("Failed to create vApp template for catalog {} ".format(catalog_name))
115 return self.get_catalogid(catalog_name, catalogs)
116 else:
117 for catalog in catalogs:
118 # search for existing catalog if we find same name we return ID
119 if catalog['name'] == catalog_name:
120 self.logger.debug("Found existing catalog entry for {} "
121 "catalog id {}".format(catalog_name,
122 self.get_catalogid(catalog_name, catalogs)))
123 return self.get_catalogid(catalog_name, catalogs)
124
125 # if we didn't find existing catalog we create a new one and upload image.
126 self.logger.debug("Creating new catalog entry {} - {}".format(catalog_name, catalog_name))
127 result = org.create_catalog(catalog_name, catalog_name)
128 if result is None:
129 raise Exception("Failed to create new catalog {} ".format(catalog_name))
130
131 result = self.upload_ovf(org=org, catalog_name=catalog_name, image_name=filename.split(".")[0],
132 media_file_name=path, description='medial_file_name', progress=progress)
133 if not result:
134 raise Exception("Failed create vApp template for catalog {} ".format(catalog_name))
135
136 def get_catalogid(self, catalog_name=None, catalogs=None):
137 """ Method check catalog and return catalog ID in UUID format.
138
139 Args
140 catalog_name: catalog name as string
141 catalogs: list of catalogs.
142
143 Return: catalogs uuid
144 """
145
146 for catalog in catalogs:
147 if catalog['name'] == catalog_name:
148 catalog_id = catalog['id']
149 return catalog_id
150 return None
151
152 def upload_ovf(self, org=None, catalog_name=None, image_name=None, media_file_name=None,
153 description='', progress=False, chunk_bytes=128 * 1024):
154 """
155 Uploads a OVF file to a vCloud catalog
156
157 org : organization object
158 catalog_name: (str): The name of the catalog to upload the media.
159 media_file_name: (str): The name of the local media file to upload.
160 return: (bool) True if the media file was successfully uploaded, false otherwise.
161 """
162 client = self.connect()
163 if not client:
164 raise Exception("Failed to connect vCD!")
165
166 os.path.isfile(media_file_name)
167 statinfo = os.stat(media_file_name)
168
169 # find a catalog entry where we upload OVF.
170 # create vApp Template and check the status if vCD able to read OVF it will respond with appropirate
171 # status change.
172 # if VCD can parse OVF we upload VMDK file
173 try:
174 for catalog in org.list_catalogs():
175 if catalog_name != catalog['name']:
176 continue
177 catalog_href = "{}/api/catalog/{}/action/upload".format(self.url, catalog['id'])
178 data = """
179 <UploadVAppTemplateParams name="{}" xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"><Description>{} vApp Template</Description></UploadVAppTemplateParams>
180 """.format(catalog_name, description)
181
182 if client:
183 headers = {'Accept':'application/*+xml;version=' + API_VERSION,
184 'x-vcloud-authorization': client._session.headers['x-vcloud-authorization']}
185 headers['Content-Type'] = 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml'
186
187 response = requests.post(url=catalog_href,
188 headers=headers,
189 data=data,
190 verify=False)
191 if response.status_code != 201:
192 self.logger.debug("Failed to create vApp template")
193 raise Exception("Failed to create vApp template")
194
195 if response.status_code == requests.codes.created:
196 catalogItem = XmlElementTree.fromstring(response.content)
197 entity = [child for child in catalogItem if
198 child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0]
199 href = entity.get('href')
200 template = href
201
202 response = requests.get(url=href,
203 headers=headers,
204 verify=False)
205 if response.status_code == requests.codes.ok:
206 headers['Content-Type'] = 'Content-Type text/xml'
207 result = re.search('rel="upload:default"\shref="(.*?\/descriptor.ovf)"',response.content)
208 if result:
209 transfer_href = result.group(1)
210
211 response = requests.put(url=transfer_href, headers=headers,
212 data=open(media_file_name, 'rb'),
213 verify=False)
214
215 if response.status_code != requests.codes.ok:
216 self.logger.debug(
217 "Failed create vApp template for catalog name {} and image {}".format(catalog_name,
218 media_file_name))
219 return False
220
221 # TODO fix this with aync block
222 time.sleep(5)
223
224 self.logger.debug("vApp template for catalog name {} and image {}".format(catalog_name, media_file_name))
225
226 # uploading VMDK file
227 # check status of OVF upload and upload remaining files.
228
229 response = requests.get(url=template,
230 headers=headers,
231 verify=False)
232
233 if response.status_code == requests.codes.ok:
234 result = re.search('rel="upload:default"\s*href="(.*?vmdk)"',response.content)
235 if result:
236 link_href = result.group(1)
237 # we skip ovf since it already uploaded.
238 if 'ovf' in link_href:
239 continue
240 # The OVF file and VMDK must be in a same directory
241 head, tail = os.path.split(media_file_name)
242 file_vmdk = head + '/' + link_href.split("/")[-1]
243 if not os.path.isfile(file_vmdk):
244 return False
245 statinfo = os.stat(file_vmdk)
246 if statinfo.st_size == 0:
247 return False
248 hrefvmdk = link_href
249 if progress:
250 widgets = ['Uploading file: ', Percentage(), ' ', Bar(), ' ', ETA(), ' ',
251 FileTransferSpeed()]
252 progress_bar = ProgressBar(widgets=widgets, maxval=statinfo.st_size).start()
253
254 bytes_transferred = 0
255 f = open(file_vmdk, 'rb')
256 while bytes_transferred < statinfo.st_size:
257 my_bytes = f.read(chunk_bytes)
258 if len(my_bytes) <= chunk_bytes:
259 headers['Content-Range'] = 'bytes %s-%s/%s' % (
260 bytes_transferred, len(my_bytes) - 1, statinfo.st_size)
261 headers['Content-Length'] = str(len(my_bytes))
262 response = requests.put(url=hrefvmdk,
263 headers=headers,
264 data=my_bytes,
265 verify=False)
266 if response.status_code == requests.codes.ok:
267 bytes_transferred += len(my_bytes)
268 if progress:
269 progress_bar.update(bytes_transferred)
270 else:
271 self.logger.debug(
272 'file upload failed with error: [%s] %s' % (response.status_code,
273 response.content))
274
275 f.close()
276 return False
277 f.close()
278 if progress:
279 progress_bar.finish()
280 time.sleep(60)
281 return True
282 else:
283 self.logger.debug("Failed retrieve vApp template for catalog name {} for OVF {}".
284 format(catalog_name, media_file_name))
285 return False
286 except Exception as exp:
287 self.logger.debug("Failed while uploading OVF to catalog {} for OVF file {} with Exception {}"
288 .format(catalog_name,media_file_name, exp))
289 raise Exception(
290 "Failed while uploading OVF to catalog {} for OVF file {} with Exception {}"
291 .format(catalog_name,media_file_name, exp))
292 self.logger.debug("Failed to retrieve catalog name {} for OVF file {}".format(catalog_name, media_file_name))
293 return False
294
295 if __name__ == "__main__":
296
297 # vmware vcloud director credentials
298 vcd_hostname = sys.argv[1]
299 vcd_username = sys.argv[2]
300 vcd_password = sys.argv[3]
301 orgname = sys.argv[4]
302 # OVF image path to be upload to vCD
303 ovf_file_path = sys.argv[5]
304
305 # changing virtual system type in ovf file
306 fh = open(ovf_file_path,'r')
307 content = fh.read()
308 content = content.replace('<vssd:VirtualSystemType>vmx-7','<vssd:VirtualSystemType>vmx-07')
309 fh.close()
310 fh1 = open(ovf_file_path,'w')
311 fh1.write(content)
312 fh1.close()
313
314
315 logging.basicConfig(filename='ovf_upload.log',level=logging.DEBUG)
316 logger = logging.getLogger(__name__)
317
318 obj = vCloudconfig(vcd_hostname, vcd_username, vcd_password, orgname, logger)
319
320 dirpath, filename = os.path.split(ovf_file_path)
321 filename_name, file_extension = os.path.splitext(filename)
322
323 # Get image name from cirros vnfd
324 cirros_yaml = '../descriptor-packages/vnfd/cirros_vnf/src/cirros_vnfd.yaml'
325 rh = open(cirros_yaml,'r')
326 match = re.search("image:\s'(.*?)'\n",rh.read())
327 if match: catalog = match.group(1)
328
329 if file_extension == '.ovf':
330 obj.get_catalog_id_from_path(catalog_name=catalog, path=ovf_file_path,
331 progress=True)