cf77385929
Including all python modules for nova-zvm-virt-driver and neutron-zvm-plugin. Change-Id: I72dd9f64fc412cbf10f5e7ab6e4ac465a977e849
801 lines
32 KiB
Python
Executable File
801 lines
32 KiB
Python
Executable File
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2013 IBM Corp.
|
|
#
|
|
# 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.
|
|
|
|
import commands
|
|
import datetime
|
|
import os
|
|
import re
|
|
import shutil
|
|
import tarfile
|
|
import xml.dom.minidom as Dom
|
|
|
|
from oslo.config import cfg
|
|
|
|
from nova import exception as nova_exception
|
|
from nova.image import glance
|
|
from nova.openstack.common import excutils
|
|
from nova.openstack.common.gettextutils import _
|
|
from nova.openstack.common import log as logging
|
|
from nova.virt import images
|
|
from nova.virt.zvm import const
|
|
from nova.virt.zvm import exception
|
|
from nova.virt.zvm import utils as zvmutils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
QUEUE_BUFFER_SIZE = 10
|
|
|
|
|
|
class ZVMImages(object):
|
|
|
|
def __init__(self):
|
|
self._xcat_url = zvmutils.XCATUrl()
|
|
self._pathutils = zvmutils.PathUtils()
|
|
|
|
def create_zvm_image(self, instance, image_name, image_href):
|
|
"""Create z/VM image from z/VM instance by invoking xCAT REST API
|
|
imgcapture.
|
|
"""
|
|
nodename = instance['name']
|
|
profile = image_name + "_" + image_href
|
|
body = ['nodename=' + nodename,
|
|
'profile=' + profile]
|
|
if CONF.zvm_image_compression_level:
|
|
if CONF.zvm_image_compression_level.isdigit() and (
|
|
int(CONF.zvm_image_compression_level) in range(0, 10)):
|
|
body.append('compress=%s' % CONF.zvm_image_compression_level)
|
|
else:
|
|
msg = _("Invalid zvm_image_compression_level detected, please"
|
|
"specify it with a integer between 0 and 9 in your nova.conf")
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
url = self._xcat_url.imgcapture()
|
|
LOG.debug(_('Capturing %s start') % instance['name'])
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMImageError):
|
|
res = zvmutils.xcat_request("POST", url, body)
|
|
|
|
os_image = self._get_os_image(res)
|
|
|
|
return os_image
|
|
|
|
def _get_os_image(self, response):
|
|
"""Return the image_name by parsing the imgcapture rest api
|
|
response.
|
|
"""
|
|
image_name_xcat = ""
|
|
if len(response['info']) > 0:
|
|
for info in response['info']:
|
|
for info_element in info:
|
|
if "Completed capturing the image" in info_element:
|
|
start_index = info_element.find('(')
|
|
end_index = info_element.find(')')
|
|
image_name_xcat = info_element[start_index + 1:
|
|
end_index]
|
|
return image_name_xcat
|
|
if len(image_name_xcat) == 0:
|
|
msg = _("Capture image failed.")
|
|
LOG.error(msg)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
else:
|
|
msg = _("Capture image returns bad response.")
|
|
LOG.error(msg)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
def get_snapshot_time_path(self):
|
|
return self._pathutils.get_snapshot_time_path()
|
|
|
|
def get_image_from_xcat(self, image_name_xcat, image_name,
|
|
snapshot_time_path):
|
|
"""Import image from xCAT to nova, by invoking the imgexport
|
|
REST API.
|
|
"""
|
|
LOG.debug(_("Getting image from xCAT"))
|
|
destination = os.path.join(snapshot_time_path, image_name + '.tgz')
|
|
host = zvmutils.get_host()
|
|
body = ['osimage=' + image_name_xcat,
|
|
'destination=' + destination,
|
|
'remotehost=' + host]
|
|
url = self._xcat_url.imgexport()
|
|
|
|
try:
|
|
zvmutils.xcat_request("POST", url, body)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError) as err:
|
|
msg = (_("Transfer image to compute node failed: %s") % err)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
return destination
|
|
|
|
def delete_image_glance(self, image_service, context, image_href):
|
|
"""
|
|
delete the image from glance database and image repository.
|
|
Can be used for a rollback step if operations to image fail.
|
|
"""
|
|
image_service.delete(context, image_href)
|
|
|
|
def clean_up_snapshot_time_path(self, snapshot_time_path):
|
|
"""
|
|
Clean up the time_path and its contents under "snapshot_tmp" after
|
|
image uploaded to glance.
|
|
Also be used for a rollback step if operations to the image file fail.
|
|
"""
|
|
if os.path.exists(snapshot_time_path):
|
|
LOG.debug(_("Cleaning up nova local image file"))
|
|
shutil.rmtree(snapshot_time_path)
|
|
|
|
def _delete_image_file_from_xcat(self, image_name_xcat):
|
|
"""
|
|
Delete image file from xCAT MN.
|
|
When capturing, image in the xCAT MN's repository will be removed after
|
|
it is imported to nova compute node.
|
|
"""
|
|
LOG.debug(_("Removing image files from xCAT MN image repository"))
|
|
url = self._xcat_url.rmimage('/' + image_name_xcat)
|
|
try:
|
|
zvmutils.xcat_request("DELETE", url)
|
|
except (exception.ZVMXCATInternalError,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATRequestFailed):
|
|
LOG.warn(_("Failed to delete image file %s from xCAT") %
|
|
image_name_xcat)
|
|
|
|
def _delete_image_object_from_xcat(self, image_name_xcat):
|
|
"""Delete image object from xCAT MN.
|
|
|
|
After capturing, image definition in the xCAT MN's table osimage and
|
|
linuximage will be removed after it is imported to nova compute node.
|
|
|
|
"""
|
|
LOG.debug(_("Deleting the image object"))
|
|
url = self._xcat_url.rmobject('/' + image_name_xcat)
|
|
try:
|
|
zvmutils.xcat_request("DELETE", url)
|
|
except (exception.ZVMXCATInternalError,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATRequestFailed):
|
|
LOG.warn(_("Failed to delete image definition %s from xCAT") %
|
|
image_name_xcat)
|
|
|
|
def delete_image_from_xcat(self, image_name_xcat):
|
|
self._delete_image_file_from_xcat(image_name_xcat)
|
|
self._delete_image_object_from_xcat(image_name_xcat)
|
|
|
|
def _getxmlnode(self, node, name):
|
|
return node.getElementsByTagName(name)[0] if node else []
|
|
|
|
def _getnode(self, node_root, tagname):
|
|
"""For parse manifest."""
|
|
nodename = node_root.getElementsByTagName(tagname)[0]
|
|
nodevalue = nodename.childNodes[0].data
|
|
return nodevalue
|
|
|
|
def parse_manifest_xml(self, image_package_path):
|
|
"""Return the image properties from manifest.xml."""
|
|
LOG.debug(_("Parsing the manifest.xml"))
|
|
manifest_xml = os.path.join(image_package_path, "manifest.xml")
|
|
|
|
manifest = {}
|
|
|
|
if os.path.exists(manifest_xml):
|
|
xml_file = Dom.parse(manifest_xml)
|
|
else:
|
|
LOG.warn(_('manifest.xml does not exist'))
|
|
manifest['imagename'] = ''
|
|
manifest['imagetype'] = ''
|
|
manifest['osarch'] = ''
|
|
manifest['osname'] = ''
|
|
manifest['osvers'] = ''
|
|
manifest['profile'] = ''
|
|
manifest['provmethod'] = ''
|
|
return manifest
|
|
|
|
node_root = xml_file.documentElement
|
|
node_root = self._getxmlnode(node_root, 'osimage')
|
|
manifest['imagename'] = self._getnode(node_root, "imagename")
|
|
manifest['imagetype'] = self._getnode(node_root, "imagetype")
|
|
manifest['osarch'] = self._getnode(node_root, "osarch")
|
|
manifest['osname'] = self._getnode(node_root, "osname")
|
|
manifest['osvers'] = self._getnode(node_root, "osvers")
|
|
manifest['profile'] = self._getnode(node_root, "profile")
|
|
manifest['provmethod'] = self._getnode(node_root, "provmethod")
|
|
|
|
return manifest
|
|
|
|
def untar_image_bundle(self, snapshot_time_path, image_bundle):
|
|
"""Untar the image bundle *.tgz from xCAT and remove the *.tgz."""
|
|
if os.path.exists(image_bundle):
|
|
LOG.debug(_("Untarring the image bundle ... "))
|
|
tarobj = tarfile.open(image_bundle, "r:gz")
|
|
for tarinfo in tarobj:
|
|
tarobj.extract(tarinfo.name, path=snapshot_time_path)
|
|
tarobj.close()
|
|
os.remove(image_bundle)
|
|
else:
|
|
self.clean_up_snapshot_time_path(snapshot_time_path)
|
|
msg = _("Image bundle does not exist")
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
def get_image_file_name(self, image_package_path):
|
|
file_contents = os.listdir(image_package_path)
|
|
for f in file_contents:
|
|
if f.endswith('.img'):
|
|
return f
|
|
msg = _("Can not find image file")
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
def image_exist_xcat(self, image_id):
|
|
"""To see if the specific image exist in xCAT MN's image
|
|
repository.
|
|
"""
|
|
LOG.debug(_("Checking if the image %s exists or not in xCAT "
|
|
"MN's image repository ") % image_id)
|
|
image_uuid = image_id.replace('-', '_')
|
|
parm = '&criteria=profile=~' + image_uuid
|
|
url = self._xcat_url.lsdef_image(addp=parm)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMImageError):
|
|
res = zvmutils.xcat_request("GET", url)
|
|
|
|
res_image = res['info']
|
|
|
|
if '_' in str(res_image):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def fetch_image(self, context, image_id, target, user, project):
|
|
LOG.debug(_("Downloading image %s from glance image server") %
|
|
image_id)
|
|
try:
|
|
images.fetch(context, image_id, target, user, project)
|
|
except Exception as err:
|
|
msg = _("Download image file of image %(id)s failed with reason:"
|
|
" %(err)s") % {'id': image_id, 'err': err}
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
def generate_manifest_file(self, image_meta, image_name, disk_file,
|
|
manifest_path):
|
|
"""Generate the manifest.xml file from glance's image metadata
|
|
as a part of the image bundle.
|
|
"""
|
|
image_id = image_meta['id']
|
|
image_type = image_meta['properties']['image_type_xcat']
|
|
os_version = image_meta['properties']['os_version']
|
|
os_name = image_meta['properties']['os_name']
|
|
os_arch = image_meta['properties']['architecture']
|
|
prov_method = image_meta['properties']['provisioning_method']
|
|
|
|
image_profile = '_'.join((image_name, image_id.replace('-', '_')))
|
|
image_name_xcat = '-'.join((os_version, os_arch,
|
|
prov_method, image_profile))
|
|
rootimgdir_str = ('/install', prov_method, os_version,
|
|
os_arch, image_profile)
|
|
rootimgdir = '/'.join(rootimgdir_str)
|
|
today_date = datetime.date.today()
|
|
last_use_date_string = today_date.strftime("%Y-%m-%d")
|
|
is_deletable = "auto:last_use_date:" + last_use_date_string
|
|
|
|
doc = Dom.Document()
|
|
xcatimage = doc.createElement('xcatimage')
|
|
doc.appendChild(xcatimage)
|
|
|
|
# Add linuximage section
|
|
imagename = doc.createElement('imagename')
|
|
imagename_value = doc.createTextNode(image_name_xcat)
|
|
imagename.appendChild(imagename_value)
|
|
rootimagedir = doc.createElement('rootimgdir')
|
|
rootimagedir_value = doc.createTextNode(rootimgdir)
|
|
rootimagedir.appendChild(rootimagedir_value)
|
|
linuximage = doc.createElement('linuximage')
|
|
linuximage.appendChild(imagename)
|
|
linuximage.appendChild(rootimagedir)
|
|
xcatimage.appendChild(linuximage)
|
|
|
|
# Add osimage section
|
|
osimage = doc.createElement('osimage')
|
|
manifest = {'imagename': image_name_xcat,
|
|
'imagetype': image_type,
|
|
'isdeletable': is_deletable,
|
|
'osarch': os_arch,
|
|
'osname': os_name,
|
|
'osvers': os_version,
|
|
'profile': image_profile,
|
|
'provmethod': prov_method}
|
|
|
|
for item in manifest.keys():
|
|
itemkey = doc.createElement(item)
|
|
itemvalue = doc.createTextNode(manifest[item])
|
|
itemkey.appendChild(itemvalue)
|
|
osimage.appendChild(itemkey)
|
|
xcatimage.appendChild(osimage)
|
|
f = open(manifest_path + '/manifest.xml', 'w')
|
|
f.write(doc.toprettyxml(indent=''))
|
|
f.close()
|
|
|
|
# Add the rawimagefiles section
|
|
rawimagefiles = doc.createElement('rawimagefiles')
|
|
xcatimage.appendChild(rawimagefiles)
|
|
|
|
files = doc.createElement('files')
|
|
files_value = doc.createTextNode(rootimgdir + '/' + disk_file)
|
|
files.appendChild(files_value)
|
|
|
|
rawimagefiles.appendChild(files)
|
|
|
|
f = open(manifest_path + '/manifest.xml', 'w')
|
|
f.write(doc.toprettyxml(indent=' '))
|
|
f.close()
|
|
|
|
self._rewr(manifest_path)
|
|
|
|
return manifest_path + '/manifest.xml'
|
|
|
|
def _rewr(self, manifest_path):
|
|
f = open(manifest_path + '/manifest.xml', 'r')
|
|
lines = f.read()
|
|
f.close()
|
|
|
|
lines = lines.replace('\n', '')
|
|
lines = re.sub(r'>(\s*)<', r'>\n\1<', lines)
|
|
lines = re.sub(r'>[ \t]*(\S+)[ \t]*<', r'>\1<', lines)
|
|
|
|
f = open(manifest_path + '/manifest.xml', 'w')
|
|
f.write(lines)
|
|
f.close()
|
|
|
|
def generate_image_bundle(self, spawn_path, tmp_file_fn, image_name):
|
|
"""Generate the image bundle which is used to import to xCAT MN's
|
|
image repository.
|
|
"""
|
|
image_bundle_name = image_name + '.tgz'
|
|
tar_file = spawn_path + '/' + tmp_file_fn + '_' + image_bundle_name
|
|
LOG.debug(_("The generate the image bundle file is %s") % tar_file)
|
|
|
|
os.chdir(spawn_path)
|
|
tarFile = tarfile.open(tar_file, mode='w:gz')
|
|
|
|
try:
|
|
tarFile.add(tmp_file_fn)
|
|
tarFile.close()
|
|
except Exception as err:
|
|
msg = (_("Generate image bundle failed: %s") % err)
|
|
LOG.error(msg)
|
|
if os.path.isfile(tar_file):
|
|
os.remove(tar_file)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
finally:
|
|
self._pathutils.clean_temp_folder(tmp_file_fn)
|
|
|
|
return tar_file
|
|
|
|
def check_space_imgimport_xcat(self, context, instance, tar_file,
|
|
xcat_free_space_threshold, zvm_xcat_master):
|
|
image_href = instance['image_ref']
|
|
try:
|
|
free_space_xcat = self.get_free_space_xcat(
|
|
xcat_free_space_threshold, zvm_xcat_master)
|
|
img_transfer_needed = self._get_transfer_needed_space_xcat(context,
|
|
image_href, tar_file)
|
|
larger = max(xcat_free_space_threshold, img_transfer_needed)
|
|
if img_transfer_needed > free_space_xcat:
|
|
larger = max(xcat_free_space_threshold, img_transfer_needed)
|
|
size_needed = float(larger - free_space_xcat)
|
|
self.prune_image_xcat(context, size_needed,
|
|
img_transfer_needed)
|
|
else:
|
|
LOG.debug(_("Image transfer needed space satisfied in xCAT"))
|
|
except exception.ZVMImageError:
|
|
with excutils.save_and_reraise_exception():
|
|
os.remove(tar_file)
|
|
|
|
def put_image_to_xcat(self, image_bundle_package, image_profile):
|
|
"""Import the image bundle from compute node to xCAT MN's image
|
|
repository.
|
|
"""
|
|
remote_host_info = zvmutils.get_host()
|
|
body = ['osimage=%s' % image_bundle_package,
|
|
'profile=%s' % image_profile,
|
|
'remotehost=%s' % remote_host_info]
|
|
url = self._xcat_url.imgimport()
|
|
|
|
try:
|
|
zvmutils.xcat_request("POST", url, body)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError) as err:
|
|
msg = _("Import the image bundle to xCAT MN failed: %s") % err
|
|
raise exception.ZVMImageError(msg=msg)
|
|
finally:
|
|
os.remove(image_bundle_package)
|
|
|
|
def get_imgname_xcat(self, image_id):
|
|
"""Get the xCAT deployable image name by image id."""
|
|
image_uuid = image_id.replace('-', '_')
|
|
parm = '&criteria=profile=~' + image_uuid
|
|
url = self._xcat_url.lsdef_image(addp=parm)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMImageError):
|
|
res = zvmutils.xcat_request("GET", url)
|
|
with zvmutils.expect_invalid_xcat_resp_data():
|
|
res_image = res['info'][0][0]
|
|
res_img_name = res_image.strip().split(" ")[0]
|
|
|
|
if res_img_name:
|
|
return res_img_name
|
|
else:
|
|
LOG.error(_("Fail to find the right image to deploy"))
|
|
|
|
def _get_image_list_xcat(self):
|
|
"""Get an image list from xcat osimage table.
|
|
|
|
criteria: osarch=s390x and provmethod=netboot|raw|sysclone and
|
|
isdeletable field
|
|
|
|
"""
|
|
display_field = '&field=isdeletable'
|
|
isdeletable_criteria = '&criteria=isdeletable=~^auto:last_use_date:'
|
|
osarch_criteria = '&criteria=osarch=s390x'
|
|
provmethod_criteria = '&criteria=provmethod=~netboot|raw|sysclone'
|
|
|
|
addp = ''.join([isdeletable_criteria, osarch_criteria,
|
|
provmethod_criteria, display_field])
|
|
url = self._xcat_url.lsdef_image(addp=addp)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMImageError):
|
|
output = zvmutils.xcat_request("GET", url)
|
|
|
|
image_list = []
|
|
if len(output['info']) <= 0:
|
|
return image_list
|
|
|
|
if len(output['info'][0]) > 0:
|
|
i = 0
|
|
while i < len(output['info'][0]):
|
|
if "Object name:" in output['info'][0][i]:
|
|
sub_list = []
|
|
len_objectname = len("Object name: ")
|
|
is_deletable = output['info'][0][i + 1]
|
|
last_use_date = self._validate_last_use_date(
|
|
output['info'][0][i][len_objectname:],
|
|
is_deletable)
|
|
if last_use_date is None:
|
|
i += 2
|
|
continue
|
|
sub_list.append(output['info'][0][i][len_objectname:])
|
|
sub_list.append(last_use_date)
|
|
image_list.append(sub_list)
|
|
i += 2
|
|
|
|
return image_list
|
|
|
|
def update_last_use_date(self, image_name_xcat):
|
|
"""Update the last_use_date in xCAT osimage table after a
|
|
successful deploy.
|
|
"""
|
|
LOG.debug(_("Update the last_use_date in xCAT osimage table "
|
|
"after a successful deploy"))
|
|
|
|
today_date = datetime.date.today()
|
|
last_use_date_string = today_date.strftime("%Y-%m-%d")
|
|
url = self._xcat_url.tabch('/osimage')
|
|
is_deletable = "auto:last_use_date:" + last_use_date_string
|
|
body = ["imagename=" + image_name_xcat,
|
|
"osimage.isdeletable=" + is_deletable]
|
|
|
|
try:
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError) as err:
|
|
LOG.warn(_("Illegal date for last_use_date %s") % err)
|
|
|
|
return last_use_date_string
|
|
|
|
def _validate_last_use_date(self, image_name, is_deletable):
|
|
"""Validate the isdeletable date format."""
|
|
last_use_date_string = is_deletable.split(":")[2]
|
|
timere = "^\d{4}[-]((0([1-9]{1}))|"\
|
|
"(1[0|1|2]))[-](([0-2]([0-9]{1}))|(3[0|1]))$"
|
|
|
|
if (len(last_use_date_string) == 10) and (
|
|
re.match(timere, last_use_date_string)):
|
|
LOG.debug(_("The format for last_use_date is valid "))
|
|
else:
|
|
LOG.warn(_("The format for image %s record in xcat table osimage's"
|
|
" last_used_date is not valid. The correct format is "
|
|
"auto:last_use_date:yyyy-mm-dd") % image_name)
|
|
return
|
|
|
|
try:
|
|
last_use_date_datetime = datetime.datetime.strptime(
|
|
last_use_date_string, '%Y-%m-%d')
|
|
except Exception as err:
|
|
LOG.warn(_("Illegal date for last_use_date %(msg)s") % err)
|
|
return
|
|
|
|
return last_use_date_datetime.date()
|
|
|
|
def _verify_is_deletable_periodic(self, last_use_date, clean_period):
|
|
"""Check the last_use_date of an image to determine if the image
|
|
need to be cleaned.
|
|
"""
|
|
now = datetime.date.today()
|
|
delta = (now - last_use_date).days
|
|
if (delta - clean_period) >= 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def clean_image_cache_xcat(self, clean_period):
|
|
"""Clean the old image."""
|
|
image_list = self._get_image_list_xcat()
|
|
if len(image_list) <= 0:
|
|
return
|
|
else:
|
|
i = 0
|
|
while i < len(image_list):
|
|
image_name_xcat = image_list[i][0]
|
|
last_use_date = image_list[i][1]
|
|
if self._verify_is_deletable_periodic(last_use_date,
|
|
clean_period):
|
|
LOG.debug(_('Delete the image %s') % image_name_xcat)
|
|
self.delete_image_from_xcat(image_name_xcat)
|
|
else:
|
|
LOG.debug(_("Keep the image"))
|
|
i += 1
|
|
|
|
def _get_image_bundle_size(self, tar_file):
|
|
size_byte = os.path.getsize(tar_file)
|
|
return float(size_byte) / 1024 / 1024 / 1024
|
|
|
|
def _get_image_size_glance(self, context, image_href):
|
|
(image_service, image_id) = glance.get_remote_image_service(
|
|
context, image_href)
|
|
|
|
try:
|
|
image_meta = image_service.show(context, image_href)
|
|
except nova_exception.ImageNotFound:
|
|
image_meta = {}
|
|
return 0
|
|
|
|
size_byte = image_meta['size']
|
|
return float(size_byte) / 1024 / 1024 / 1024
|
|
|
|
def get_free_space_xcat(self, xcat_free_space_threshold, zvm_xcat_master):
|
|
"""Get the free space in xCAT MN /install."""
|
|
LOG.debug(_("Get the xCAT MN /install free space"))
|
|
addp = "&field=--freerepospace"
|
|
if isinstance(zvm_xcat_master, str):
|
|
url = self._xcat_url.rinv("/" + zvm_xcat_master, addp=addp)
|
|
else:
|
|
msg = _("zvm_xcat_master should be specified as a string")
|
|
LOG.error(msg)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMImageError):
|
|
result = zvmutils.xcat_request("GET", url)
|
|
with zvmutils.expect_invalid_xcat_resp_data():
|
|
if len(result['info']) == 0:
|
|
msg = _("'rinv <zvm_xcat_master> --freerepospace' returns "
|
|
"null, please check 'df -h /install', there may "
|
|
"be something wrong with the mount of /install")
|
|
raise exception.ZVMImageError(msg=msg)
|
|
free_space_line = result['info'][0][0]
|
|
free_space = free_space_line.split()[4]
|
|
if free_space.endswith("G"):
|
|
free_disk_value = free_space.rstrip("G")
|
|
return float(free_disk_value)
|
|
elif free_space.endswith("M"):
|
|
free_disk_value = free_space.rstrip("M")
|
|
return float(free_disk_value) / 1024
|
|
elif free_space == "0":
|
|
return 0
|
|
else:
|
|
return xcat_free_space_threshold
|
|
|
|
def get_imgcapture_needed(self, instance):
|
|
"""Get the space needed on xCAT MN for an image capture."""
|
|
LOG.debug(_("Getting image capture needed size for %s") %
|
|
instance['name'])
|
|
|
|
cmd = "df -h /"
|
|
result = None
|
|
result = zvmutils.xdsh(instance['name'], cmd)['data'][0]
|
|
imgcapture_needed_space = ""
|
|
try:
|
|
result_data = result[0].split()
|
|
if CONF.zvm_image_compression_level and \
|
|
int(CONF.zvm_image_compression_level) == 0:
|
|
imgcapture_needed_space = result_data[10]
|
|
else:
|
|
imgcapture_needed_space = result_data[11]
|
|
|
|
if imgcapture_needed_space.endswith("G"):
|
|
imgcapture_needed_space_value =\
|
|
imgcapture_needed_space.rstrip("G")
|
|
return float(imgcapture_needed_space_value) * 2
|
|
elif imgcapture_needed_space.endswith("M"):
|
|
imgcapture_needed_space_value =\
|
|
imgcapture_needed_space.rstrip("M")
|
|
return (float(imgcapture_needed_space_value) / 1024) * 2
|
|
else:
|
|
return const.ZVM_IMAGE_SIZE_MAX
|
|
except (IndexError, ValueError, TypeError) as err:
|
|
raise exception.ZVMImageError(msg=err)
|
|
|
|
def _get_transfer_needed_space_xcat(self, context, image_href, tar_file):
|
|
"""To transfer an image bundle from glance to xCAT, the needed size is
|
|
image_bundle_size + image_size.
|
|
"""
|
|
image_bundle_size = self._get_image_bundle_size(tar_file)
|
|
image_size = self._get_image_size_glance(context, image_href)
|
|
return image_bundle_size + image_size
|
|
|
|
def _get_image_href_by_osimage(self, image_name_xcat):
|
|
"""If we have the xCAT.osimage.imagename, we want to get the
|
|
image_href.
|
|
"""
|
|
try:
|
|
image_profile = image_name_xcat.split("-")
|
|
if len(image_profile) >= 4:
|
|
return image_profile[3]
|
|
else:
|
|
return image_name_xcat
|
|
except (TypeError, IndexError):
|
|
LOG.error(_("xCAT imagename format for %s is not as expected")
|
|
% image_name_xcat)
|
|
|
|
def _sort_image_by_use_date(self, image_list, left, right):
|
|
"""Sort the image_list by last_use_date from oldest image to latest."""
|
|
if (left < right):
|
|
i = left
|
|
j = right
|
|
x = image_list[left]
|
|
while (i < j):
|
|
while (i < j and image_list[j][1] >= x[1]):
|
|
j -= 1
|
|
if(i < j):
|
|
image_list[i] = image_list[j]
|
|
i += 1
|
|
while(i < j and image_list[i][1] < x[1]):
|
|
i += 1
|
|
if(i < j):
|
|
image_list[j] = image_list[i]
|
|
j -= 1
|
|
image_list[i] = x
|
|
self._sort_image_by_use_date(image_list, left, i - 1)
|
|
self._sort_image_by_use_date(image_list, i + 1, right)
|
|
|
|
def _get_to_be_deleted_images_xcat(self, context, size_needed,
|
|
current_needed):
|
|
"""To get a list of images which is to be removed from xCAT image
|
|
repository because it cannot provide enough space for image operations
|
|
from OpenStack.
|
|
"""
|
|
image_list = self._get_image_list_xcat()
|
|
size_sum = 0
|
|
to_be_deleted_image_profile = []
|
|
|
|
if len(image_list) <= 0:
|
|
msg = _("No image to be deleted, please create space manually "
|
|
"on xcat(%s).") % CONF.zvm_xcat_server
|
|
raise exception.ZVMImageError(msg=msg)
|
|
else:
|
|
self._sort_image_by_use_date(image_list, 0, len(image_list) - 1)
|
|
for img in image_list:
|
|
image_name_xcat = img[0]
|
|
image_profile = self._get_image_href_by_osimage(
|
|
image_name_xcat)
|
|
image_uuid = image_profile.partition('_')[2].replace("_", "-")
|
|
image_size = self._get_image_size_glance(context,
|
|
image_uuid)
|
|
if image_size > 0:
|
|
to_be_deleted_image_profile.append(image_profile)
|
|
size_sum += image_size
|
|
if size_sum >= size_needed:
|
|
return to_be_deleted_image_profile
|
|
|
|
if size_sum >= current_needed:
|
|
return to_be_deleted_image_profile
|
|
else:
|
|
msg = _("xCAT MN space not enough for the current image operation")
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
def prune_image_xcat(self, context, size_needed, current_needed):
|
|
"""Remove the images which meet remove criteria from xCAT."""
|
|
LOG.debug(_("Clear up space by clean images in xCAT"))
|
|
to_be_deleted_image_profile = self._get_to_be_deleted_images_xcat(
|
|
context, size_needed, current_needed)
|
|
if len(to_be_deleted_image_profile) > 0:
|
|
for image_profile in to_be_deleted_image_profile:
|
|
image_name_xcat = self.get_imgname_xcat(image_profile)
|
|
self.delete_image_from_xcat(image_name_xcat)
|
|
|
|
def zimage_check(self, image_meta):
|
|
"""Do a brief check to see if the image is a valid zVM image."""
|
|
property_ = ['image_file_name', 'image_type_xcat', 'architecture',
|
|
'os_name', 'provisioning_method', 'os_version']
|
|
for prop in property_:
|
|
if prop not in image_meta['properties'].keys():
|
|
msg = (_("The image %s is not a valid zVM image,please check "
|
|
"if the image properties match the requirements.")
|
|
% image_meta['id'])
|
|
LOG.error(msg)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
def cleanup_image_after_migration(self, inst_name):
|
|
"""Cleanup osimages in xCAT image repository while confirm migration
|
|
or revert migration at source compute.
|
|
"""
|
|
image_list = self._get_image_list_xcat()
|
|
matchee = ''.join(['rsz', inst_name])
|
|
for img in image_list:
|
|
img_name = img[0]
|
|
if matchee in img_name:
|
|
self.delete_image_from_xcat(img_name)
|
|
|
|
def get_root_disk_units(self, image_file_path):
|
|
"""use 'hexdump' to get the root_disk_units"""
|
|
cmd = "hexdump -n 48 -C %s" % image_file_path
|
|
(result, output) = commands.getstatusoutput(cmd)
|
|
if result != 0:
|
|
msg = (_("Get image property failed,"
|
|
" please check whether the image file exists!"))
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
LOG.debug(_("hexdump result is %s") % output)
|
|
try:
|
|
root_disk_units = int(output[144:156])
|
|
except ValueError:
|
|
msg = (_("Image file at %s is missing imbeded disk size "
|
|
"metadata, it was probably not captured with xCAT")
|
|
% image_file_path)
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
if 'FBA' not in output and 'CKD' not in output:
|
|
msg = (_("The image's disk type is not valid. Currently we only"
|
|
" support FBA and CKD disk"))
|
|
raise exception.ZVMImageError(msg=msg)
|
|
|
|
LOG.debug(_("The image's root_disk_units is %s") % root_disk_units)
|
|
return root_disk_units
|
|
|
|
def set_image_root_disk_units(self, context, image_meta, image_file_path):
|
|
"""Set the property 'root_disk_units'to image. """
|
|
new_image_meta = image_meta
|
|
root_disk_units = self.get_root_disk_units(image_file_path)
|
|
LOG.debug(_("The image's root_disk_units is %s") % root_disk_units)
|
|
|
|
(glance_image_service, image_id) = glance.get_remote_image_service(
|
|
context, image_meta['id'])
|
|
new_image_meta = glance_image_service.show(context, image_id)
|
|
new_image_meta['properties']['root_disk_units'] = str(root_disk_units)
|
|
|
|
try:
|
|
glance_image_service.update(context, image_id,
|
|
new_image_meta, None)
|
|
except nova_exception.ImageNotAuthorized:
|
|
msg = _('Not allowed to modify attributes for image %s') % image_id
|
|
LOG.error(msg)
|
|
|
|
return new_image_meta
|