2 # Copyright 2019 VMware Inc.
3 # This file is part of ETSI OSM
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
10 # http://www.apache.org/licenses/LICENSE-2.0
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
18 # For those usages not covered by the Apache License, Version 2.0 please
19 # contact: osslegalrouting@vmware.com
23 from lxml
import etree
25 from pyvcloud
.vcd
.client
import BasicLoginCredentials
, Client
, QueryResultFormat
, ResourceType
, TaskStatus
, ApiVersion
26 from pyvcloud
.vcd
.exceptions
import EntityNotFoundException
, InternalServerException
27 from pyvcloud
.vcd
.org
import Org
32 MODULE_DIR
= os
.path
.dirname(__file__
)
35 LOG_FILE
= os
.path
.join(MODULE_DIR
, "logs/ovf_uploader.log")
36 os
.makedirs(os
.path
.dirname(LOG_FILE
), exist_ok
=True)
37 logger
= logging
.getLogger(__name__
)
38 file_handler
= logging
.FileHandler(LOG_FILE
)
39 file_handler
.setLevel(logging
.DEBUG
)
40 formatter
= logging
.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
41 file_handler
.setFormatter(formatter
)
42 logger
.addHandler(file_handler
)
43 stdout_handler
= logging
.StreamHandler(sys
.stdout
)
44 stdout_handler
.setLevel(logging
.INFO
)
45 logger
.addHandler(stdout_handler
)
47 logging
.captureWarnings(True)
50 __description__
= "Initial Release"
54 """ get version of this application"""
55 version
= str(__version__
) + " - " + str(__description__
)
59 def report_progress(bytes_written
, total_size
):
60 percent_complete
= int((bytes_written
* 100) / total_size
)
61 print("{}% complete \r".format(percent_complete
), end
='')
64 class OVFUploader(object):
65 """ Class to convert input image into OVF format """
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
74 client
= Client(self
.vcd_url
, verify_ssl_certs
=False, api_version
=ApiVersion
.VERSION_32
.value
,
79 # sclient.set_highest_supported_version()
80 client
.set_credentials(BasicLoginCredentials(self
.username
, self
.orgname
,
82 logger
.info("Logged into {} using version {}".format(self
.vcd_url
, client
.get_api_version()))
84 self
.org
= Org(self
.client
, resource
=self
.client
.get_org())
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
))
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"
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
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
))
112 except Exception as exp
:
113 problem
= Exception("Failed to fetch VirtualSystem Name element from OVF {}:\n{}".format(
115 logger
.error(problem
)
118 def make_catalog(self
):
119 self
.catalog_id
= None
121 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:
125 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
)
128 raise Exception("Failed to create new catalog entry")
129 self
.catalog_id
= result
.attrib
['id'].split(':')[-1]
132 logger
.debug("Using catalog {}, id {}".format(self
.image_name
, self
.catalog_id
))
134 except Exception as exp
:
135 problem
= Exception("Failed to fetch catalog for {}:\n{} ".format(self
.image_name
, exp
))
136 logger
.error(problem
)
139 def upload_ovf(self
):
141 ova_tarfilename
, _
= os
.path
.splitext(self
.ovf_file
)
142 ova_tarfilename
+= '.ova'
144 # Check if the content already exists:
145 resource_type
= ResourceType
.CATALOG_ITEM
.value
146 q
= self
.client
.get_typed_query(
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
:
152 logger
.info("Removing old version from catalog")
154 self
.org
.delete_catalog_item(self
.image_name
, self
.image_name
)
155 except InternalServerException
as exp
:
157 "Cannot delete vAppTemplate {}. Please check in vCD if "
158 "the content is still being imported into the catalog".format(
162 # Create a single OVA bundle
163 ova
= tarfile
.open(name
=ova_tarfilename
,
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
))
168 logger
.info("Uploading content to vCD")
169 self
.org
.upload_ovf(self
.image_name
,
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
)
179 if os
.path
.exists(ova_tarfilename
):
180 os
.remove(ova_tarfilename
)
182 def wait_for_task_completion(self
):
184 logger
.info("Importing content to vCD")
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
)
193 tasks
= list(query
.execute())
195 if task
.get('name') == 'VDC_UPLOAD_OVF_CONTENTS':
196 upload_task
= self
.client
.get_resource(task
.get('href'))
205 except Exception as exp
:
206 problem
= Exception("Failed to import OVF {}:\n{} ".format(self
.ovf_file
, exp
))
207 logger
.error(problem
)
211 task_status
= upload_task
.get('status').lower()
212 if(hasattr(upload_task
, 'Progress')):
213 print("{}% complete \r".format(upload_task
.Progress
), end
='')
215 for status
in bad_statuses
:
216 if task_status
== status
.value
.lower():
218 "vCD failed to import OVF {}:\n{}: {} ".format(self
.ovf_file
,
220 upload_task
.Error
.get('Message')))
221 logger
.error(problem
)
223 if task_status
== str(TaskStatus
.SUCCESS
.value
).lower():
227 upload_task
= self
.client
.get_resource(upload_task
.get('href'))
229 logger
.info("OVF upload and import complete, content is ready to use")