Add support for required images file

If a package contains a file with a list of required images
check if they are present in glance and attempt to download them
if not. Supports v1 glanceclient only.

Change-Id: Ibb39fbbadef109b117094afefd383fe24de0a792
Partially implements: blueprint muranoclient-marketplace-support
This commit is contained in:
Kirill Zaitsev 2015-03-03 17:46:46 +03:00 committed by Stan Lagun
parent 51058b6238
commit 82a1d10162
5 changed files with 151 additions and 9 deletions

View File

@ -255,25 +255,47 @@ class Package(FileWrapperMixin):
file_obj = File(file_obj)
return Package(file_obj)
@property
def contents(self):
"""Contents of a package."""
if not hasattr(self, '_contents'):
try:
self._file.seek(0)
self._zip_obj = zipfile.ZipFile(
StringIO.StringIO(self._file.read()))
except Exception:
LOG.exception("An error occurred,"
" while parsint the package")
raise
return self._zip_obj
@property
def manifest(self):
"""Parsed manifest file of a package."""
if not hasattr(self, '_manifest'):
try:
self._file.seek(0)
zip_obj = zipfile.ZipFile(
StringIO.StringIO(self._file.read()))
self._manifest = yaml.safe_load(zip_obj.open('manifest.yaml'))
except (zipfile.BadZipfile, KeyError, yaml.error.YAMLError) as e:
self._manifest = yaml.safe_load(
self.contents.open('manifest.yaml'))
except Exception:
LOG.exception("An error occurred,"
" while extracting manifest from package")
raise ValueError(e)
raise
return self._manifest
def images(self):
"""Returns a list of required image specifications."""
if 'images.lst' not in self.contents.namelist():
return []
try:
return yaml.safe_load(
self.contents.open('images.lst')).get('Images', [])
except Exception:
return []
def requirements(self, base_url, dep_dict=None):
"""Recursively scan Require section of manifests of all the
dependencies. Returns a dict with FQPNs as keys and respective
PackageFiles as values
Package objects as values
"""
if not dep_dict:
dep_dict = {}
@ -297,6 +319,76 @@ class Package(FileWrapperMixin):
return dep_dict
def ensure_images(glance_client, image_specs, base_url):
"""Ensure that images from image_specs are available in glance. If not
attempts: instructs glance to download the images and sets murano-specific
metadata for it.
"""
def _image_valid(image, keys):
for key in keys:
if key not in image:
LOG.warning("Image specification invalid: "
"No {0} key in image ".format(key))
return False
return True
keys = ['Name', 'Hash', 'DiskFormat', 'ContainerFormat', ]
for image_spec in image_specs:
if not _image_valid(image_spec, keys):
continue
filters = {
'name': image_spec["Name"],
'disk_format': image_spec["DiskFormat"],
'container_format': image_spec["ContainerFormat"],
}
# NOTE(kzaitsev): glance v1 client does not allow checksum in
# a filter, so we have to filter ourselves
for img_obj in glance_client.images.list(filters=filters):
img = img_obj.to_dict()
if img['checksum'] == image_spec['Hash']:
break
else:
img = None
update_metadata = False
if img:
LOG.info("Found desired image {0}, id {1}".format(
img['name'], img['id']))
# check for murano meta-data
if 'murano_image_info' in img.get('properties', {}):
LOG.info("Image {0} already has murano meta-data".format(
image_spec['Name']))
else:
update_metadata = True
else:
LOG.info("Desired image {0} not found attempting "
"to download".format(image_spec['Name']))
update_metadata = True
download_url = to_url(
image_spec.get("Url", image_spec['Name']),
base_url=base_url,
path='/images/',
)
LOG.info("Instructing glance to download image {0}".format(
image_spec['Name']))
img = glance_client.images.create(
name=image_spec["Name"],
container_format=image_spec['ContainerFormat'],
disk_format=image_spec['DiskFormat'],
copy_from=download_url)
img = img.to_dict()
if update_metadata and 'Meta' in image_spec:
LOG.info("Updating image {0} metadata".format(
image_spec['Name']))
murano_image_info = jsonutils.dumps(image_spec['Meta'])
glance_client.images.update(
img['id'], properties={'murano_image_info':
murano_image_info})
class Bundle(FileWrapperMixin):
"""Represents murano bundle contents."""
@staticmethod

View File

@ -22,6 +22,7 @@ import argparse
import logging
import sys
import glanceclient
from keystoneclient.v2_0 import client as ksclient
from oslo.utils import encodeutils
import six
@ -135,6 +136,10 @@ class MuranoShell(object):
default=utils.env('MURANO_URL'),
help='Defaults to env[MURANO_URL]')
parser.add_argument('--glance-url',
default=utils.env('GLANCE_URL'),
help='Defaults to env[GLANCE_URL]')
parser.add_argument('--murano-api-version',
default=utils.env(
'MURANO_API_VERSION', default='1'),
@ -320,8 +325,11 @@ class MuranoShell(object):
'cacert': args.os_cacert,
'include_pass': args.include_password
}
glance_kwargs = kwargs
glance_kwargs = kwargs.copy()
endpoint = args.murano_url
glance_endpoint = args.glance_url
if not args.os_no_client_auth:
_ksclient = self._get_ksclient(**kwargs)
@ -338,9 +346,11 @@ class MuranoShell(object):
'endpoint_type': args.os_endpoint_type,
'include_pass': args.include_password
}
glance_kwargs = kwargs.copy()
if args.os_region_name:
kwargs['region_name'] = args.os_region_name
glance_kwargs['region_name'] = args.os_region_name
if not endpoint:
endpoint = self._get_endpoint(_ksclient, **kwargs)
@ -348,6 +358,27 @@ class MuranoShell(object):
if args.api_timeout:
kwargs['timeout'] = args.api_timeout
if not glance_endpoint:
try:
glance_endpoint = self._get_endpoint(
_ksclient, service_type='image')
except Exception:
pass
glance_client = None
if glance_endpoint:
try:
glance_client = glanceclient.Client(
'1', glance_endpoint, **glance_kwargs)
except Exception:
pass
if glance_client:
kwargs['glance_client'] = glance_client
else:
logger.warning("Could not initialise glance client. "
"Image creation will be unavailable.")
kwargs['glance_client'] = None
client = apiclient.Client(api_version, endpoint, **kwargs)
args.func(client, args)

View File

@ -35,6 +35,7 @@ class Client(http.HTTPClient):
def __init__(self, *args, **kwargs):
"""Initialize a new client for the Murano v1 API."""
self.glance_client = kwargs.pop('glance_client', None)
super(Client, self).__init__(*args, **kwargs)
self.environments = environments.EnvironmentManager(self)
self.sessions = sessions.SessionManager(self)

View File

@ -217,6 +217,14 @@ def do_package_import(mc, args):
package = utils.Package.fromFile(filename)
reqs = package.requirements(base_url=args.murano_repo_url)
for name, package in reqs.iteritems():
try:
utils.ensure_images(
glance_client=mc.glance_client,
image_specs=package.images(),
base_url=args.murano_repo_url)
except Exception as e:
print("Error {0} occurred while installing "
"images for {1}".format(e, name))
try:
mc.packages.create(data, {name: package.file()})
except Exception as e:
@ -254,10 +262,19 @@ def do_bundle_import(mc, args):
except Exception as e:
print("Error {0} occurred while "
"parsing package {1}".format(e, package_info['Name']))
reqs = package.requirements(base_url=args.murano_repo_url)
for name, package in reqs.iteritems():
for name, dep_package in reqs.iteritems():
try:
mc.packages.create(data, {name: package.file()})
utils.ensure_images(
glance_client=mc.glance_client,
image_specs=dep_package.images(),
base_url=args.murano_repo_url)
except Exception as e:
print("Error {0} occurred while installing "
"images for {1}".format(e, name))
try:
mc.packages.create(data, {name: dep_package.file()})
except Exception as e:
print("Error {0} occurred while "
"installing package {1}".format(e, name))

View File

@ -4,6 +4,7 @@
pbr>=0.6,!=0.7,<1.0
argparse
PrettyTable>=0.7,<0.8
python-glanceclient>=0.15.0
python-keystoneclient>=0.11.1
httplib2>=0.7.5
iso8601>=0.1.9