blob: 4f3f0e7a66e6b3fbd5a7a20a76012bb5d825833d [file] [log] [blame]
kasar12aa2cf2018-02-02 02:51:07 -08001# -*- 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
kasar12aa2cf2018-02-02 02:51:07 -080025from xml.etree import ElementTree as XmlElementTree
kasara4347782018-11-01 04:30:50 -070026from pyvcloud.vcd.client import BasicLoginCredentials,Client
27from pyvcloud.vcd.vdc import VDC
28from pyvcloud.vcd.org import Org
kasar12aa2cf2018-02-02 02:51:07 -080029import sys,os
30import logging
31import requests
32import time
33import re
kasara4347782018-11-01 04:30:50 -070034import hashlib
35from progressbar import Percentage, Bar, ETA, FileTransferSpeed, ProgressBar
kasar12aa2cf2018-02-02 02:51:07 -080036
37
kasara4347782018-11-01 04:30:50 -070038API_VERSION = '5.6'
kasar12aa2cf2018-02-02 02:51:07 -080039
40class 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
kasara4347782018-11-01 04:30:50 -070046 self.logger = logging.getLogger('vmware_ovf_upload')
47 self.logger.setLevel(10)
kasar12aa2cf2018-02-02 02:51:07 -080048
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:
kasara4347782018-11-01 04:30:50 -070057 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))
kasar12aa2cf2018-02-02 02:51:07 -080061 except:
kasara4347782018-11-01 04:30:50 -070062 raise Exception("Can't connect to a vCloud director org: "
kasar12aa2cf2018-02-02 02:51:07 -080063 "{} as user: {}".format(self.org, self.user))
64
kasara4347782018-11-01 04:30:50 -070065 return client
kasar12aa2cf2018-02-02 02:51:07 -080066
kasara4347782018-11-01 04:30:50 -070067 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,
kasar12aa2cf2018-02-02 02:51:07 -0800153 description='', progress=False, chunk_bytes=128 * 1024):
154 """
155 Uploads a OVF file to a vCloud catalog
156
kasara4347782018-11-01 04:30:50 -0700157 org : organization object
kasar12aa2cf2018-02-02 02:51:07 -0800158 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 """
kasara4347782018-11-01 04:30:50 -0700162 client = self.connect()
163 if not client:
164 raise Exception("Failed to connect vCD!")
kasar12aa2cf2018-02-02 02:51:07 -0800165
166 os.path.isfile(media_file_name)
167 statinfo = os.stat(media_file_name)
168
kasar12aa2cf2018-02-02 02:51:07 -0800169 # 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:
kasara4347782018-11-01 04:30:50 -0700174 for catalog in org.list_catalogs():
175 if catalog_name != catalog['name']:
kasar12aa2cf2018-02-02 02:51:07 -0800176 continue
kasara4347782018-11-01 04:30:50 -0700177 catalog_href = "{}/api/catalog/{}/action/upload".format(self.url, catalog['id'])
kasar12aa2cf2018-02-02 02:51:07 -0800178 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>
kasara4347782018-11-01 04:30:50 -0700180 """.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
kasar12aa2cf2018-02-02 02:51:07 -0800195 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
kasar12aa2cf2018-02-02 02:51:07 -0800201
kasara4347782018-11-01 04:30:50 -0700202 response = requests.get(url=href,
203 headers=headers,
204 verify=False)
kasar12aa2cf2018-02-02 02:51:07 -0800205 if response.status_code == requests.codes.ok:
kasar12aa2cf2018-02-02 02:51:07 -0800206 headers['Content-Type'] = 'Content-Type text/xml'
kasara4347782018-11-01 04:30:50 -0700207 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
kasar12aa2cf2018-02-02 02:51:07 -0800215 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.
kasara4347782018-11-01 04:30:50 -0700228
229 response = requests.get(url=template,
230 headers=headers,
231 verify=False)
kasar12aa2cf2018-02-02 02:51:07 -0800232
233 if response.status_code == requests.codes.ok:
kasara4347782018-11-01 04:30:50 -0700234 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()
kasar12aa2cf2018-02-02 02:51:07 -0800276 return False
kasara4347782018-11-01 04:30:50 -0700277 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
kasar12aa2cf2018-02-02 02:51:07 -0800286 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))
kasara4347782018-11-01 04:30:50 -0700289 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
kasar12aa2cf2018-02-02 02:51:07 -0800294
295if __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
kasar12aa2cf2018-02-02 02:51:07 -0800329 if file_extension == '.ovf':
kasara4347782018-11-01 04:30:50 -0700330 obj.get_catalog_id_from_path(catalog_name=catalog, path=ovf_file_path,
331 progress=True)