blob: 7cb168fc9b972bd4c946056b72ed3bc82581398a [file] [log] [blame]
beierlc6b00f02019-10-07 13:09:24 -04001# #
2# Copyright 2019 VMware Inc.
3# This file is part of ETSI OSM
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# 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, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17#
18# For those usages not covered by the Apache License, Version 2.0 please
19# contact: osslegalrouting@vmware.com
20# #
21
22import logging
23from lxml import etree
24import os
beierl90a9ab82019-11-18 11:26:59 -050025from pyvcloud.vcd.client import BasicLoginCredentials, Client, QueryResultFormat, ResourceType, TaskStatus, ApiVersion
beierlc6b00f02019-10-07 13:09:24 -040026from pyvcloud.vcd.exceptions import EntityNotFoundException, InternalServerException
27from pyvcloud.vcd.org import Org
28import sys
29import tarfile
30import time
31
32MODULE_DIR = os.path.dirname(__file__)
33
34# Set logger
35LOG_FILE = os.path.join(MODULE_DIR, "logs/ovf_uploader.log")
36os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
37logger = logging.getLogger(__name__)
38file_handler = logging.FileHandler(LOG_FILE)
39file_handler.setLevel(logging.DEBUG)
40formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
41file_handler.setFormatter(formatter)
42logger.addHandler(file_handler)
43stdout_handler = logging.StreamHandler(sys.stdout)
44stdout_handler.setLevel(logging.INFO)
45logger.addHandler(stdout_handler)
46logger.setLevel(10)
47logging.captureWarnings(True)
48
49__version__ = "1.0"
50__description__ = "Initial Release"
51
52
53def get_version():
54 """ get version of this application"""
55 version = str(__version__) + " - " + str(__description__)
56 return version
57
58
59def report_progress(bytes_written, total_size):
60 percent_complete = int((bytes_written * 100) / total_size)
61 print("{}% complete \r".format(percent_complete), end='')
62
63
64class OVFUploader(object):
65 """ Class to convert input image into OVF format """
66
67 def __init__(self, ovf_file, vcd_url=None, username=None, password=None, orgname=None):
68 self.ovf_file = os.path.abspath(ovf_file)
69 self.vcd_url = vcd_url
70 self.username = username
71 self.password = password
72 self.orgname = orgname
73 try:
beierl90a9ab82019-11-18 11:26:59 -050074 client = Client(self.vcd_url, verify_ssl_certs=False, api_version=ApiVersion.VERSION_32.value,
75 log_requests=True,
76 log_headers=True,
77 log_bodies=True,
78 log_file=LOG_FILE)
79 # sclient.set_highest_supported_version()
beierlc6b00f02019-10-07 13:09:24 -040080 client.set_credentials(BasicLoginCredentials(self.username, self.orgname,
81 self.password))
82 logger.info("Logged into {} using version {}".format(self.vcd_url, client.get_api_version()))
83 self.client = client
84 self.org = Org(self.client, resource=self.client.get_org())
85
86 except Exception as exp:
87 problem = Exception("Failed to connect to vCD at {}, org {}, username {}:\n{}".format(
88 self.vcd_url, self.orgname, self.username, exp))
89 logger.error(problem)
90 raise problem
91
92 try:
93 # Retrieve the VM name from the OVF. We will use this as both the image and catalog name
94 OVF_tree = etree.parse(self.ovf_file)
95 root = OVF_tree.getroot()
96 nsmap = {k: v for k, v in root.nsmap.items() if k}
97 nsmap["xmlns"] = "http://schemas.dmtf.org/ovf/envelope/1"
98
99 virtuasystem = root.find('xmlns:VirtualSystem', nsmap)
100 name_tag = virtuasystem.find('xmlns:Name', nsmap)
101 self.image_name = name_tag.text
102 info_tag = virtuasystem.find('xmlns:Info', nsmap)
103 self.image_description = info_tag.text
104
105 references = root.find('xmlns:References', nsmap)
106 file = references.find('xmlns:File', nsmap)
107 self.vmdk_file = "{}/{}".format(
108 os.path.dirname(self.ovf_file),
109 file.attrib['{http://schemas.dmtf.org/ovf/envelope/1}href'])
110 logger.info("Loaded VM {}: {}".format(self.image_name, self.image_description))
111
112 except Exception as exp:
113 problem = Exception("Failed to fetch VirtualSystem Name element from OVF {}:\n{}".format(
114 self.ovf_file, exp))
115 logger.error(problem)
116 raise problem
117
118 def make_catalog(self):
beierl90a9ab82019-11-18 11:26:59 -0500119 self.catalog_id = None
beierlc6b00f02019-10-07 13:09:24 -0400120 try:
beierl90a9ab82019-11-18 11:26:59 -0500121 for catalog in self.org.list_catalogs():
122 if catalog['name'] == self.image_name:
123 self.catalog_id = catalog['id']
124 if self.catalog_id is None:
beierlc6b00f02019-10-07 13:09:24 -0400125 logger.info("Creating a new catalog entry {} in vCD".format(self.image_name))
126 result = self.org.create_catalog(self.image_name, self.image_description)
127 if result is None:
128 raise Exception("Failed to create new catalog entry")
129 self.catalog_id = result.attrib['id'].split(':')[-1]
130 self.org.reload()
131
132 logger.debug("Using catalog {}, id {}".format(self.image_name, self.catalog_id))
133
134 except Exception as exp:
135 problem = Exception("Failed to fetch catalog for {}:\n{} ".format(self.image_name, exp))
136 logger.error(problem)
137 raise problem
138
139 def upload_ovf(self):
140
beierl90a9ab82019-11-18 11:26:59 -0500141 ova_tarfilename, _ = os.path.splitext(self.ovf_file)
142 ova_tarfilename += '.ova'
beierlc6b00f02019-10-07 13:09:24 -0400143 try:
144 # Check if the content already exists:
beierl90a9ab82019-11-18 11:26:59 -0500145 resource_type = ResourceType.CATALOG_ITEM.value
146 q = self.client.get_typed_query(
147 resource_type,
148 query_result_format=QueryResultFormat.ID_RECORDS,
149 equality_filter=('catalogName', self.image_name))
150 for item in list(q.execute()):
151 if item.get('name') == self.image_name:
beierlc6b00f02019-10-07 13:09:24 -0400152 logger.info("Removing old version from catalog")
153 try:
154 self.org.delete_catalog_item(self.image_name, self.image_name)
155 except InternalServerException as exp:
156 problem = Exception(
157 "Cannot delete vAppTemplate {}. Please check in vCD if "
158 "the content is still being imported into the catalog".format(
159 self.image_name))
160 raise problem
161
162 # Create a single OVA bundle
beierlc6b00f02019-10-07 13:09:24 -0400163 ova = tarfile.open(name=ova_tarfilename,
164 mode='w')
165 ova.add(self.ovf_file, arcname=os.path.basename(self.ovf_file))
166 ova.add(self.vmdk_file, arcname=os.path.basename(self.vmdk_file))
167 ova.close()
168 logger.info("Uploading content to vCD")
169 self.org.upload_ovf(self.image_name,
170 ova_tarfilename,
171 item_name=self.image_name,
172 description=self.image_description,
173 callback=report_progress)
174 except Exception as exp:
175 problem = Exception("Failed to upload OVF {}:\n{} ".format(self.ovf_file, exp))
176 logger.error(problem)
177 raise problem
178 finally:
179 if os.path.exists(ova_tarfilename):
180 os.remove(ova_tarfilename)
181
182 def wait_for_task_completion(self):
183
184 logger.info("Importing content to vCD")
185 try:
186
187 query = self.client.get_typed_query(
188 query_type_name=ResourceType.TASK.value,
189 qfilter='ownerName==' + self.username + ';(status==queued,status==preRunning,status==running)',
190 query_result_format=QueryResultFormat.REFERENCES)
191
192 upload_task = None
193 tasks = list(query.execute())
194 for task in tasks:
195 if task.get('name') == 'VDC_UPLOAD_OVF_CONTENTS':
196 upload_task = self.client.get_resource(task.get('href'))
197 break
198
199 bad_statuses = [
200 TaskStatus.ABORTED,
201 TaskStatus.CANCELED,
202 TaskStatus.ERROR
203 ]
204
205 except Exception as exp:
206 problem = Exception("Failed to import OVF {}:\n{} ".format(self.ovf_file, exp))
207 logger.error(problem)
208 raise problem
209
210 while(True):
211 task_status = upload_task.get('status').lower()
212 if(hasattr(upload_task, 'Progress')):
213 print("{}% complete \r".format(upload_task.Progress), end='')
214
215 for status in bad_statuses:
216 if task_status == status.value.lower():
217 problem = Exception(
218 "vCD failed to import OVF {}:\n{}: {} ".format(self.ovf_file,
219 task_status,
220 upload_task.Error.get('Message')))
221 logger.error(problem)
222 raise problem
223 if task_status == str(TaskStatus.SUCCESS.value).lower():
224 break
225
226 time.sleep(2)
227 upload_task = self.client.get_resource(upload_task.get('href'))
228
229 logger.info("OVF upload and import complete, content is ready to use")