blob: 7cb168fc9b972bd4c946056b72ed3bc82581398a [file] [log] [blame]
# #
# Copyright 2019 VMware Inc.
# This file is part of ETSI OSM
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact: osslegalrouting@vmware.com
# #
import logging
from lxml import etree
import os
from pyvcloud.vcd.client import BasicLoginCredentials, Client, QueryResultFormat, ResourceType, TaskStatus, ApiVersion
from pyvcloud.vcd.exceptions import EntityNotFoundException, InternalServerException
from pyvcloud.vcd.org import Org
import sys
import tarfile
import time
MODULE_DIR = os.path.dirname(__file__)
# Set logger
LOG_FILE = os.path.join(MODULE_DIR, "logs/ovf_uploader.log")
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
logger = logging.getLogger(__name__)
file_handler = logging.FileHandler(LOG_FILE)
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setLevel(logging.INFO)
logger.addHandler(stdout_handler)
logger.setLevel(10)
logging.captureWarnings(True)
__version__ = "1.0"
__description__ = "Initial Release"
def get_version():
""" get version of this application"""
version = str(__version__) + " - " + str(__description__)
return version
def report_progress(bytes_written, total_size):
percent_complete = int((bytes_written * 100) / total_size)
print("{}% complete \r".format(percent_complete), end='')
class OVFUploader(object):
""" Class to convert input image into OVF format """
def __init__(self, ovf_file, vcd_url=None, username=None, password=None, orgname=None):
self.ovf_file = os.path.abspath(ovf_file)
self.vcd_url = vcd_url
self.username = username
self.password = password
self.orgname = orgname
try:
client = Client(self.vcd_url, verify_ssl_certs=False, api_version=ApiVersion.VERSION_32.value,
log_requests=True,
log_headers=True,
log_bodies=True,
log_file=LOG_FILE)
# sclient.set_highest_supported_version()
client.set_credentials(BasicLoginCredentials(self.username, self.orgname,
self.password))
logger.info("Logged into {} using version {}".format(self.vcd_url, client.get_api_version()))
self.client = client
self.org = Org(self.client, resource=self.client.get_org())
except Exception as exp:
problem = Exception("Failed to connect to vCD at {}, org {}, username {}:\n{}".format(
self.vcd_url, self.orgname, self.username, exp))
logger.error(problem)
raise problem
try:
# Retrieve the VM name from the OVF. We will use this as both the image and catalog name
OVF_tree = etree.parse(self.ovf_file)
root = OVF_tree.getroot()
nsmap = {k: v for k, v in root.nsmap.items() if k}
nsmap["xmlns"] = "http://schemas.dmtf.org/ovf/envelope/1"
virtuasystem = root.find('xmlns:VirtualSystem', nsmap)
name_tag = virtuasystem.find('xmlns:Name', nsmap)
self.image_name = name_tag.text
info_tag = virtuasystem.find('xmlns:Info', nsmap)
self.image_description = info_tag.text
references = root.find('xmlns:References', nsmap)
file = references.find('xmlns:File', nsmap)
self.vmdk_file = "{}/{}".format(
os.path.dirname(self.ovf_file),
file.attrib['{http://schemas.dmtf.org/ovf/envelope/1}href'])
logger.info("Loaded VM {}: {}".format(self.image_name, self.image_description))
except Exception as exp:
problem = Exception("Failed to fetch VirtualSystem Name element from OVF {}:\n{}".format(
self.ovf_file, exp))
logger.error(problem)
raise problem
def make_catalog(self):
self.catalog_id = None
try:
for catalog in self.org.list_catalogs():
if catalog['name'] == self.image_name:
self.catalog_id = catalog['id']
if self.catalog_id is None:
logger.info("Creating a new catalog entry {} in vCD".format(self.image_name))
result = self.org.create_catalog(self.image_name, self.image_description)
if result is None:
raise Exception("Failed to create new catalog entry")
self.catalog_id = result.attrib['id'].split(':')[-1]
self.org.reload()
logger.debug("Using catalog {}, id {}".format(self.image_name, self.catalog_id))
except Exception as exp:
problem = Exception("Failed to fetch catalog for {}:\n{} ".format(self.image_name, exp))
logger.error(problem)
raise problem
def upload_ovf(self):
ova_tarfilename, _ = os.path.splitext(self.ovf_file)
ova_tarfilename += '.ova'
try:
# Check if the content already exists:
resource_type = ResourceType.CATALOG_ITEM.value
q = self.client.get_typed_query(
resource_type,
query_result_format=QueryResultFormat.ID_RECORDS,
equality_filter=('catalogName', self.image_name))
for item in list(q.execute()):
if item.get('name') == self.image_name:
logger.info("Removing old version from catalog")
try:
self.org.delete_catalog_item(self.image_name, self.image_name)
except InternalServerException as exp:
problem = Exception(
"Cannot delete vAppTemplate {}. Please check in vCD if "
"the content is still being imported into the catalog".format(
self.image_name))
raise problem
# Create a single OVA bundle
ova = tarfile.open(name=ova_tarfilename,
mode='w')
ova.add(self.ovf_file, arcname=os.path.basename(self.ovf_file))
ova.add(self.vmdk_file, arcname=os.path.basename(self.vmdk_file))
ova.close()
logger.info("Uploading content to vCD")
self.org.upload_ovf(self.image_name,
ova_tarfilename,
item_name=self.image_name,
description=self.image_description,
callback=report_progress)
except Exception as exp:
problem = Exception("Failed to upload OVF {}:\n{} ".format(self.ovf_file, exp))
logger.error(problem)
raise problem
finally:
if os.path.exists(ova_tarfilename):
os.remove(ova_tarfilename)
def wait_for_task_completion(self):
logger.info("Importing content to vCD")
try:
query = self.client.get_typed_query(
query_type_name=ResourceType.TASK.value,
qfilter='ownerName==' + self.username + ';(status==queued,status==preRunning,status==running)',
query_result_format=QueryResultFormat.REFERENCES)
upload_task = None
tasks = list(query.execute())
for task in tasks:
if task.get('name') == 'VDC_UPLOAD_OVF_CONTENTS':
upload_task = self.client.get_resource(task.get('href'))
break
bad_statuses = [
TaskStatus.ABORTED,
TaskStatus.CANCELED,
TaskStatus.ERROR
]
except Exception as exp:
problem = Exception("Failed to import OVF {}:\n{} ".format(self.ovf_file, exp))
logger.error(problem)
raise problem
while(True):
task_status = upload_task.get('status').lower()
if(hasattr(upload_task, 'Progress')):
print("{}% complete \r".format(upload_task.Progress), end='')
for status in bad_statuses:
if task_status == status.value.lower():
problem = Exception(
"vCD failed to import OVF {}:\n{}: {} ".format(self.ovf_file,
task_status,
upload_task.Error.get('Message')))
logger.error(problem)
raise problem
if task_status == str(TaskStatus.SUCCESS.value).lower():
break
time.sleep(2)
upload_task = self.client.get_resource(upload_task.get('href'))
logger.info("OVF upload and import complete, content is ready to use")