[libvirt] [PATCH sandbox] virt-sandbox-image: switch to use URI to identify templates
by Daniel P. Berrange
Currently the CLI syntax is somewhat docker specific requiring
inclusion of --registry arg to identify the docker download
server. Other app containers have a notion of download server,
but don't separate it from the template name.
This patch removes that docker-ism by changing to use a URI
for identifying the template image. So instead of
virt-sandbox-image download \
--source docker --registry index.docker.io
--username dan --password 123456 ubuntu:15.04
You can use
virt-sandbox-image download docker://dan@123456:index.docker.io/ubuntu?tag=15.04
The only mandatory part is the source prefix and image name, so
that can shorten to just
virt-sandbox-image download docker:///ubuntu
to pull down the latest ubuntu image, from the default registry
using no authentication.
---
libvirt-sandbox/image/cli.py | 73 +++++--------
libvirt-sandbox/image/sources/DockerSource.py | 146 ++++++++++++++------------
libvirt-sandbox/image/sources/Source.py | 33 +++---
libvirt-sandbox/image/template.py | 109 +++++++++++++++++++
4 files changed, 232 insertions(+), 129 deletions(-)
create mode 100644 libvirt-sandbox/image/template.py
diff --git a/libvirt-sandbox/image/cli.py b/libvirt-sandbox/image/cli.py
index c8b5ba5..dd109a8 100755
--- a/libvirt-sandbox/image/cli.py
+++ b/libvirt-sandbox/image/cli.py
@@ -3,7 +3,7 @@
# Authors: Daniel P. Berrange <berrange(a)redhat.com>
# Eren Yagdiran <erenyagdiran(a)gmail.com>
#
-# Copyright (C) 2013 Red Hat, Inc.
+# Copyright (C) 2013-2015 Red Hat, Inc.
# Copyright (C) 2015 Universitat Politècnica de Catalunya.
#
# This program is free software; you can redistribute it and/or modify
@@ -34,6 +34,8 @@ import subprocess
import random
import string
+from libvirt_sandbox.image import template
+
if os.geteuid() == 0:
default_template_dir = "/var/lib/libvirt/templates"
default_image_dir = "/var/lib/libvirt/images"
@@ -44,15 +46,6 @@ else:
debug = False
verbose = False
-import importlib
-def dynamic_source_loader(name):
- name = name[0].upper() + name[1:]
- modname = "libvirt_sandbox.image.sources." + name + "Source"
- mod = importlib.import_module(modname)
- classname = name + "Source"
- classimpl = getattr(mod, classname)
- return classimpl()
-
gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale")
gettext.textdomain("libvirt-sandbox")
try:
@@ -73,11 +66,10 @@ def info(msg):
def download(args):
try:
- dynamic_source_loader(args.source).download_template(templatename=args.template,
- templatedir=args.template_dir,
- registry=args.registry,
- username=args.username,
- password=args.password)
+ tmpl = template.Template.from_uri(args.template)
+ source = tmpl.get_source_impl()
+ source.download_template(template=tmpl,
+ templatedir=args.template_dir)
except IOError,e:
print "Source %s cannot be found in given path" %args.source
except Exception,e:
@@ -85,17 +77,21 @@ def download(args):
def delete(args):
try:
- dynamic_source_loader(args.source).delete_template(templatename=args.template,
- templatedir=args.template_dir)
+ tmpl = template.Template.from_uri(args.template)
+ source = tmpl.get_source_impl()
+ source.delete_template(template=tmpl,
+ templatedir=args.template_dir)
except Exception,e:
print "Delete Error %s", str(e)
def create(args):
try:
- dynamic_source_loader(args.source).create_template(templatename=args.template,
- templatedir=args.template_dir,
- connect=args.connect,
- format=args.format)
+ tmpl = template.Template.from_uri(args.template)
+ source = tmpl.get_source_impl()
+ source.create_template(template=tmpl,
+ templatedir=args.template_dir,
+ connect=args.connect,
+ format=args.format)
except Exception,e:
print "Create Error %s" % str(e)
@@ -104,19 +100,22 @@ def run(args):
global image_dir
if args.connect is not None:
check_connect(args.connect)
- source = dynamic_source_loader(args.source)
+
+ tmpl = template.Template.from_uri(args.template)
+ source = tmpl.get_source_impl()
+
name = args.name
if name is None:
randomid = ''.join(random.choice(string.lowercase) for i in range(10))
- name = args.template + ":" + randomid
+ name = tmpl.path[1:] + ":" + randomid
- diskfile = source.get_disk(templatename=args.template,
+ diskfile = source.get_disk(template=tmpl,
templatedir=args.template_dir,
imagedir=args.image_dir,
sandboxname=name)
format = "qcow2"
- commandToRun = source.get_command(args.template, args.template_dir, args.args)
+ commandToRun = source.get_command(tmpl, args.template_dir, args.args)
if len(commandToRun) == 0:
commandToRun = ["/bin/sh"]
cmd = ['virt-sandbox', '--name', name]
@@ -130,7 +129,7 @@ def run(args):
params.append('-N')
params.append(networkArgs)
- allEnvs = source.get_env(args.template, args.template_dir)
+ allEnvs = source.get_env(tmpl, args.template_dir)
envArgs = args.env
if envArgs is not None:
allEnvs = allEnvs + envArgs
@@ -143,7 +142,7 @@ def run(args):
else:
pass
- allVolumes = source.get_volumes(args.template, args.template_dir)
+ allVolumes = source.get_volumes(tmpl, args.template_dir)
volumeArgs = args.volume
if volumeArgs is not None:
allVolumes = allVolumes + volumeArgs
@@ -172,7 +171,7 @@ def run(args):
def requires_template(parser):
parser.add_argument("template",
- help=_("name of the template"))
+ help=_("URI of the template"))
def requires_name(parser):
parser.add_argument("-n","--name",
@@ -184,23 +183,10 @@ def check_connect(connectstr):
raise ValueError("URI '%s' is not supported by virt-sandbox-image" % connectstr)
return True
-def requires_source(parser):
- parser.add_argument("-s","--source",
- default="docker",
- help=_("name of the template"))
-
def requires_connect(parser):
parser.add_argument("-c","--connect",
help=_("Connect string for libvirt"))
-def requires_auth_conn(parser):
- parser.add_argument("-r","--registry",
- help=_("Url of the custom registry"))
- parser.add_argument("-u","--username",
- help=_("Username for the custom registry"))
- parser.add_argument("-p","--password",
- help=_("Password for the custom registry"))
-
def requires_template_dir(parser):
global default_template_dir
parser.add_argument("-t","--template-dir",
@@ -217,8 +203,6 @@ def gen_download_args(subparser):
parser = subparser.add_parser("download",
help=_("Download template data"))
requires_template(parser)
- requires_source(parser)
- requires_auth_conn(parser)
requires_template_dir(parser)
parser.set_defaults(func=download)
@@ -226,7 +210,6 @@ def gen_delete_args(subparser):
parser = subparser.add_parser("delete",
help=_("Delete template data"))
requires_template(parser)
- requires_source(parser)
requires_template_dir(parser)
parser.set_defaults(func=delete)
@@ -234,7 +217,6 @@ def gen_create_args(subparser):
parser = subparser.add_parser("create",
help=_("Create image from template data"))
requires_template(parser)
- requires_source(parser)
requires_connect(parser)
requires_template_dir(parser)
parser.add_argument("-f","--format",
@@ -247,7 +229,6 @@ def gen_run_args(subparser):
help=_("Run an already built image"))
requires_name(parser)
requires_template(parser)
- requires_source(parser)
requires_connect(parser)
requires_template_dir(parser)
requires_image_dir(parser)
diff --git a/libvirt-sandbox/image/sources/DockerSource.py b/libvirt-sandbox/image/sources/DockerSource.py
index ff950ce..f367c8f 100644
--- a/libvirt-sandbox/image/sources/DockerSource.py
+++ b/libvirt-sandbox/image/sources/DockerSource.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Universitat Politècnica de Catalunya.
+# Copyright (C) 2015 Red Hat, Inc
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -29,6 +30,8 @@ import os
import subprocess
import shutil
import collections
+import urlparse
+
class DockerConfParser():
@@ -55,12 +58,6 @@ class DockerConfParser():
class DockerSource(Source):
- www_auth_username = None
- www_auth_password = None
-
- def __init__(self):
- self.default_index_server = "index.docker.io"
-
def _check_cert_validate(self):
major = sys.version_info.major
SSL_WARNING = "SSL certificates couldn't be validated by default. You need to have 2.7.9/3.4.3 or higher"
@@ -70,43 +67,38 @@ class DockerSource(Source):
if (major == 2 and sys.hexversion < py2_7_9_hexversion) or (major == 3 and sys.hexversion < py3_4_3_hexversion):
sys.stderr.write(SSL_WARNING)
- def download_template(self, templatename, templatedir,
- registry=None, username=None, password=None):
- if registry is None:
- registry = self.default_index_server
-
- if username is not None:
- self.www_auth_username = username
- self.www_auth_password = password
-
+ def download_template(self, template, templatedir):
self._check_cert_validate()
- tag = "latest"
- offset = templatename.find(':')
- if offset != -1:
- tag = templatename[offset + 1:]
- templatename = templatename[0:offset]
+
try:
- (data, res) = self._get_json(registry, "/v1/repositories/" + templatename + "/images",
- {"X-Docker-Token": "true"})
+ (data, res) = self._get_json(template,
+ None,
+ "/v1/repositories" + template.path + "/images",
+ {"X-Docker-Token": "true"})
except urllib2.HTTPError, e:
- raise ValueError(["Image '%s' does not exist" % templatename])
+ raise ValueError(["Image '%s' does not exist" % template])
registryendpoint = res.info().getheader('X-Docker-Endpoints')
token = res.info().getheader('X-Docker-Token')
checksums = {}
for layer in data:
pass
- (data, res) = self._get_json(registryendpoint, "/v1/repositories/" + templatename + "/tags",
- { "Authorization": "Token " + token })
+ (data, res) = self._get_json(template,
+ registryendpoint,
+ "/v1/repositories" + template.path + "/tags",
+ { "Authorization": "Token " + token })
cookie = res.info().getheader('Set-Cookie')
+ tag = template.params.get("tag", "latest")
if not tag in data:
- raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, templatename)])
+ raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, template)])
imagetagid = data[tag]
- (data, res) = self._get_json(registryendpoint, "/v1/images/" + imagetagid + "/ancestry",
- { "Authorization": "Token "+token })
+ (data, res) = self._get_json(template,
+ registryendpoint,
+ "/v1/images/" + imagetagid + "/ancestry",
+ { "Authorization": "Token "+ token })
if data[0] != imagetagid:
raise ValueError(["Expected first layer id '%s' to match image id '%s'",
@@ -126,8 +118,11 @@ class DockerSource(Source):
datafile = layerdir + "/template.tar.gz"
if not os.path.exists(jsonfile) or not os.path.exists(datafile):
- res = self._save_data(registryendpoint, "/v1/images/" + layerid + "/json",
- { "Authorization": "Token " + token }, jsonfile)
+ res = self._save_data(template,
+ registryendpoint,
+ "/v1/images/" + layerid + "/json",
+ { "Authorization": "Token " + token },
+ jsonfile)
createdFiles.append(jsonfile)
layersize = int(res.info().getheader("Content-Length"))
@@ -136,12 +131,15 @@ class DockerSource(Source):
if layerid in checksums:
datacsum = checksums[layerid]
- self._save_data(registryendpoint, "/v1/images/" + layerid + "/layer",
- { "Authorization": "Token "+token }, datafile, datacsum, layersize)
+ self._save_data(template,
+ registryendpoint,
+ "/v1/images/" + layerid + "/layer",
+ { "Authorization": "Token "+token },
+ datafile, datacsum, layersize)
createdFiles.append(datafile)
index = {
- "name": templatename,
+ "name": template.path,
}
indexfile = templatedir + "/" + imagetagid + "/index.json"
@@ -160,9 +158,11 @@ class DockerSource(Source):
shutil.rmtree(d)
except:
pass
- def _save_data(self,server, path, headers, dest, checksum=None, datalen=None):
+
+ def _save_data(self, template, server, path, headers,
+ dest, checksum=None, datalen=None):
try:
- res = self._get_url(server, path, headers)
+ res = self._get_url(template, server, path, headers)
csum = None
if checksum is not None:
@@ -201,8 +201,22 @@ class DockerSource(Source):
debug("FAIL %s\n" % str(e))
raise
- def _get_url(self,server, path, headers):
- url = "https://" + server + path
+ def _get_url(self, template, server, path, headers):
+ if template.protocol is None:
+ protocol = "https"
+ else:
+ protocol = template.protocol
+
+ if server is None:
+ if template.hostname is None:
+ server = "index.docker.io"
+ else:
+ if template.port is not None:
+ server = template.hostname + ":" + template.port
+ else:
+ server = template.hostname
+
+ url = urlparse.urlunparse((protocol, server, path, None, None, None))
debug("Fetching %s..." % url)
req = urllib2.Request(url=url)
@@ -212,16 +226,18 @@ class DockerSource(Source):
req.add_header(h, headers[h])
#www Auth header starts
- if self.www_auth_username is not None:
- base64string = base64.encodestring('%s:%s' % (self.www_auth_username, self.www_auth_password)).replace('\n', '')
+ if template.username and template.password:
+ base64string = base64.encodestring(
+ '%s:%s' % (template.username,
+ template.password)).replace('\n', '')
req.add_header("Authorization", "Basic %s" % base64string)
#www Auth header finish
return urllib2.urlopen(req)
- def _get_json(self,server, path, headers):
+ def _get_json(self, template, server, path, headers):
try:
- res = self._get_url(server, path, headers)
+ res = self._get_url(template, server, path, headers)
data = json.loads(res.read())
debug("OK\n")
return (data, res)
@@ -229,11 +245,11 @@ class DockerSource(Source):
debug("FAIL %s\n" % str(e))
raise
- def create_template(self, templatename, templatedir, connect=None, format=None):
+ def create_template(self, template, templatedir, connect=None, format=None):
if format is None:
format = self.default_disk_format
self._check_disk_format(format)
- imagelist = self._get_image_list(templatename,templatedir)
+ imagelist = self._get_image_list(template, templatedir)
imagelist.reverse()
parentImage = None
@@ -260,7 +276,7 @@ class DockerSource(Source):
if not format in supportedFormats:
raise ValueError(["Unsupported image format %s" % format])
- def _get_image_list(self,templatename,destdir):
+ def _get_image_list(self, template, destdir):
imageparent = {}
imagenames = {}
imagedirs = os.listdir(destdir)
@@ -273,13 +289,13 @@ class DockerSource(Source):
jsonfile = destdir + "/" + imagetagid + "/template.json"
if os.path.exists(jsonfile):
with open(jsonfile,"r") as f:
- template = json.load(f)
- parent = template.get("parent",None)
+ data = json.load(f)
+ parent = data.get("parent",None)
if parent:
imageparent[imagetagid] = parent
- if not templatename in imagenames:
- raise ValueError(["Image %s does not exist locally" %templatename])
- imagetagid = imagenames[templatename]
+ if not template.path in imagenames:
+ raise ValueError(["Image %s does not exist locally" % template.path])
+ imagetagid = imagenames[template.path]
imagelist = []
while imagetagid != None:
imagelist.append(imagetagid)
@@ -318,7 +334,7 @@ class DockerSource(Source):
cmd = cmd + params
subprocess.call(cmd)
- def delete_template(self, templatename, templatedir):
+ def delete_template(self, template, templatedir):
imageusage = {}
imageparent = {}
imagenames = {}
@@ -332,9 +348,9 @@ class DockerSource(Source):
jsonfile = templatedir + "/" + imagetagid + "/template.json"
if os.path.exists(jsonfile):
with open(jsonfile,"r") as f:
- template = json.load(f)
+ data = json.load(f)
- parent = template.get("parent",None)
+ parent = data.get("parent",None)
if parent:
if parent not in imageusage:
imageusage[parent] = []
@@ -342,10 +358,10 @@ class DockerSource(Source):
imageparent[imagetagid] = parent
- if not templatename in imagenames:
- raise ValueError(["Image %s does not exist locally" %templatename])
+ if not template.path in imagenames:
+ raise ValueError(["Image %s does not exist locally" % template.path])
- imagetagid = imagenames[templatename]
+ imagetagid = imagenames[template.path]
while imagetagid != None:
debug("Remove %s\n" % imagetagid)
parent = imageparent.get(imagetagid,None)
@@ -368,15 +384,15 @@ class DockerSource(Source):
parent = None
imagetagid = parent
- def _get_template_data(self, templatename, templatedir):
- imageList = self._get_image_list(templatename, templatedir)
+ def _get_template_data(self, template, templatedir):
+ imageList = self._get_image_list(template, templatedir)
toplayer = imageList[0]
diskfile = templatedir + "/" + toplayer + "/template.qcow2"
configfile = templatedir + "/" + toplayer + "/template.json"
return configfile, diskfile
- def get_disk(self,templatename, templatedir, imagedir, sandboxname):
- configfile, diskfile = self._get_template_data(templatename, templatedir)
+ def get_disk(self, template, templatedir, imagedir, sandboxname):
+ configfile, diskfile = self._get_template_data(template, templatedir)
tempfile = imagedir + "/" + sandboxname + ".qcow2"
if not os.path.exists(imagedir):
os.makedirs(imagedir)
@@ -387,8 +403,8 @@ class DockerSource(Source):
subprocess.call(cmd)
return tempfile
- def get_command(self, templatename, templatedir, userargs):
- configfile, diskfile = self._get_template_data(templatename, templatedir)
+ def get_command(self, template, templatedir, userargs):
+ configfile, diskfile = self._get_template_data(template, templatedir)
configParser = DockerConfParser(configfile)
cmd = configParser.getCommand()
entrypoint = configParser.getEntrypoint()
@@ -401,13 +417,13 @@ class DockerSource(Source):
else:
return entrypoint + cmd
- def get_env(self, templatename, templatedir):
- configfile, diskfile = self._get_template_data(templatename, templatedir)
+ def get_env(self, template, templatedir):
+ configfile, diskfile = self._get_template_data(template, templatedir)
configParser = DockerConfParser(configfile)
return configParser.getEnvs()
- def get_volumes(self, templatename, templatedir):
- configfile, diskfile = self._get_template_data(templatename, templatedir)
+ def get_volumes(self, template, templatedir):
+ configfile, diskfile = self._get_template_data(template, templatedir)
configParser = DockerConfParser(configfile)
return configParser.getVolumes()
diff --git a/libvirt-sandbox/image/sources/Source.py b/libvirt-sandbox/image/sources/Source.py
index f1dd3e7..bb69682 100644
--- a/libvirt-sandbox/image/sources/Source.py
+++ b/libvirt-sandbox/image/sources/Source.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Universitat Politècnica de Catalunya.
+# Copyright (C) 2015 Red Hat, Inc
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -33,14 +34,10 @@ class Source():
pass
@abstractmethod
- def download_template(self, templatename, templatedir,
- registry=None, username=None, password=None):
+ def download_template(self, template, templatedir):
"""
- :param templatename: name of the template image to download
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path in which to store the template
- :param registry: optional hostname of image registry server
- :param username: optional username to authenticate against registry server
- :param password: optional password to authenticate against registry server
Download a template from the registry, storing it in the local
filesystem
@@ -48,10 +45,10 @@ class Source():
pass
@abstractmethod
- def create_template(self, templatename, templatedir,
+ def create_template(self, template, templatedir,
connect=None, format=None):
"""
- :param templatename: name of the template image to create
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path in which to store the template
:param connect: libvirt connection URI
:param format: disk image format
@@ -63,9 +60,9 @@ class Source():
pass
@abstractmethod
- def delete_template(self, templatename, templatedir):
+ def delete_template(self, template, templatedir):
"""
- :param templatename: name of the template image to delete
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path from which to delete template
Delete all local files associated with the template
@@ -73,9 +70,9 @@ class Source():
pass
@abstractmethod
- def get_command(self, templatename, templatedir, userargs):
+ def get_command(self, template, templatedir, userargs):
"""
- :param templatename: name of the template image to query
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path in which templates are stored
:param userargs: user specified arguments to run
@@ -85,9 +82,9 @@ class Source():
pass
@abstractmethod
- def get_disk(self,templatename, templatedir, imagedir, sandboxname):
+ def get_disk(self, template, templatedir, imagedir, sandboxname):
"""
- :param templatename: name of the template image to download
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path in which to find template
:param imagedir: local directory in which to storage disk image
@@ -97,9 +94,9 @@ class Source():
pass
@abstractmethod
- def get_env(self,templatename, templatedir):
+ def get_env(self, template, templatedir):
"""
- :param templatename: name of the template image to download
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path in which to find template
Get the dict of environment variables to set
@@ -107,9 +104,9 @@ class Source():
pass
@abstractmethod
- def get_volumes(self,templatename, templatedir):
+ def get_volumes(self, template, templatedir):
"""
- :param templatename: name of the template image to download
+ :param template: libvirt_sandbox.template.Template object
:param templatedir: local directory path in which to find template
Get the list of volumes associated with the template
diff --git a/libvirt-sandbox/image/template.py b/libvirt-sandbox/image/template.py
new file mode 100644
index 0000000..9021242
--- /dev/null
+++ b/libvirt-sandbox/image/template.py
@@ -0,0 +1,109 @@
+#
+# -*- coding: utf-8 -*-
+# Authors: Daniel P. Berrange <berrange(a)redhat.com>
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+import urlparse
+import importlib
+
+class Template(object):
+
+ def __init__(self,
+ source, protocol,
+ hostname, port,
+ username, password,
+ path, params):
+ """
+ :param source: template source name
+ :param protocol: network transport protocol or None
+ :param hostname: registry hostname or None
+ :param port: registry port or None
+ :param username: username or None
+ :param password: password or None
+ :param path: template path identifier
+ :param params: template parameters
+
+ docker:///ubuntu
+
+ docker+https://index.docker.io/ubuntu?tag=latest
+ """
+
+ self.source = source
+ self.protocol = protocol
+ self.hostname = hostname
+ self.port = port
+ self.username = username
+ self.password = password
+ self.path = path
+ self.params = params
+ if self.params is None:
+ self.params = {}
+
+ def get_source_impl(self):
+ mod = importlib.import_module(
+ "libvirt_sandbox.image.sources." +
+ self.source.capitalize() + "Source")
+ classname = self.source.capitalize() + "Source"
+ classimpl = getattr(mod, classname)
+ return classimpl()
+
+ def __repr__(self):
+ if self.protocol is not None:
+ scheme = self.source + "+" + self.protocol
+ else:
+ scheme = self.source
+ if self.hostname:
+ if self.port:
+ netloc = self.hostname + ":" + self.port
+ else:
+ netloc = self.hostname
+
+ if self.username:
+ if self.password:
+ auth = self.username + ":" + self.password
+ else:
+ auth = self.username
+ netloc = auth + "@" + netloc
+ else:
+ netloc = None
+
+ query = "&".join([key + "=" + self.params[key] for key in self.params.keys()])
+ return urlparse.urlunparse((scheme, netloc, self.path, None, query, None))
+
+ @classmethod
+ def from_uri(klass, uri):
+ o = urlparse.urlparse(uri)
+
+ idx = o.scheme.find("+")
+ if idx == -1:
+ source = o.scheme
+ protocol = None
+ else:
+ source = o.scheme[0:idx]
+ protocol = o.schema[idx + 1:]
+
+ query = {}
+ for param in o.query.split("&"):
+ (key, val) = param.split("=")
+ query[key] = val
+ return klass(source, protocol,
+ o.hostname, o.port,
+ o.username, o.password,
+ o.path, query)
+
--
2.4.3
9 years, 7 months
[libvirt] [PATCH] qemu: Update balloon state after migration finishes
by Peter Krempa
Since qemu doesn't know at the beginning of migration what the actual
balloon size is until the migration stream transfers the appropriate
fields we also need to update the balloon size unconditionally in the
finish phase of the migration.
---
src/qemu/qemu_migration.c | 4 ++++
src/qemu/qemu_process.c | 2 +-
src/qemu/qemu_process.h | 4 ++++
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index 903612b..38649ed 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -5701,6 +5701,10 @@ qemuMigrationFinish(virQEMUDriverPtr driver,
if (qemuMigrationStopNBDServer(driver, vm, mig) < 0)
goto endjob;
+ if (qemuProcessRefreshBalloonState(driver, vm,
+ QEMU_ASYNC_JOB_MIGRATION_IN) < 0)
+ goto endjob;
+
if (flags & VIR_MIGRATE_PERSIST_DEST) {
if (qemuMigrationPersist(driver, vm, mig, !v3proto) < 0) {
/* Hmpf. Migration was successful, but making it persistent
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 7187dc1..f14582b 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -2091,7 +2091,7 @@ qemuProcessReconnectRefreshChannelVirtioState(virQEMUDriverPtr driver,
}
-static int
+int
qemuProcessRefreshBalloonState(virQEMUDriverPtr driver,
virDomainObjPtr vm,
int asyncJob)
diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h
index d40f68d..b198c2b 100644
--- a/src/qemu/qemu_process.h
+++ b/src/qemu/qemu_process.h
@@ -115,4 +115,8 @@ virDomainDiskDefPtr qemuProcessFindDomainDiskByAlias(virDomainObjPtr vm,
int qemuConnectAgent(virQEMUDriverPtr driver, virDomainObjPtr vm);
+int qemuProcessRefreshBalloonState(virQEMUDriverPtr driver,
+ virDomainObjPtr vm,
+ int asyncJob);
+
#endif /* __QEMU_PROCESS_H__ */
--
2.4.5
9 years, 7 months
[libvirt] [PATCH 0/4] Remove open coding virProcessWait for root-squash
by John Ferlan
A followup of sorts to recently pushed patches regarding NFS root-squash.
During libvirt-security list review it was pointed out that the new code
was essentially open coding what virProcessWait does. However, since the
model being used also was open coded and there was a time element, the
change was allowed as is with the expectation that a cleanup patch would
follow. Which is what leads into this series....
The series started out purely as removing the open code and replacing
with the call to virProcessWait, but during that exercise I also realized
that it was possible to create a 'netdir' in a NFS root-squash environment
(eg, virDirCreate); however, the corrollary to remove the directory using
a fork/exec didn't exist - in fact all that was called was rmdir, which
failed to delete in the NFS root-squash environment. Rather than having
a whole new interface, the first patch reworks virFileUnlink to check
whether the target is a directory or a file and either call rmdir or
unlink appropriately.
The one common thread amongst the 3 API's changed here is they each looked
to return an errno value, while typically virProcessWait consumers only
return -1 and errno. Determining which failure in virProcessWait returns
-1 is possible because one exit path uses virReportSystemError to report
the error that caused the waitpid() to fail, while the other error path
either receives the errno from the child process or if not present had
already "assumed" EACCES, so these changes follow that model, except that
if it's determined the waitpid failed, EINTR is returned similar to how
virFileAccessibleAs sets errno and returns -1.
John Ferlan (4):
storage: Use virFileUnlink instead of rmdir
virfile: Use virProcessWait in virFileOpenForked
virfile: Use virProcessWait in virFileUnlink
virfile: Use virProcessWait in virDirCreate
src/storage/storage_backend_fs.c | 20 +++--
src/util/virfile.c | 153 ++++++++++++++++-----------------------
2 files changed, 71 insertions(+), 102 deletions(-)
--
2.1.0
9 years, 7 months
[libvirt] [PATCH] vz: remove error logging from prlsdkUUIDParse
by Maxim Nestratov
From: Maxim Nestratov <mnestratov(a)virtuozzo.com>
As far as not every call of prlsdkUUIDParse assume correct UUID
supplied, there is no use to complain about wrong format in it.
Otherwise our log is flooded with false error messages.
For instance, calling prlsdkUUIDParse from prlsdkEventsHandler
works as a filter and in case of uuid absence for event issuer,
we simply know that we shouldn't continue further processing.
Instead of error logging for all calls we should explicitly take
into accaunt where it is called from.
Signed-off-by: Maxim Nestratov <mnestratov(a)virtuozzo.com>
---
src/vz/vz_sdk.c | 11 +++++++----
1 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/vz/vz_sdk.c b/src/vz/vz_sdk.c
index 744b58a..32ca1ef 100644
--- a/src/vz/vz_sdk.c
+++ b/src/vz/vz_sdk.c
@@ -329,8 +329,6 @@ prlsdkUUIDParse(const char *uuidstr, unsigned char *uuid)
/* trim curly braces */
if (virUUIDParse(tmp + 1, uuid) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("UUID in config file malformed"));
ret = -1;
goto error;
}
@@ -365,8 +363,11 @@ prlsdkGetDomainIds(PRL_HANDLE sdkdom,
PrlVmCfg_GetUuid(sdkdom, uuidstr, &len);
prlsdkCheckRetGoto(pret, error);
- if (prlsdkUUIDParse(uuidstr, uuid) < 0)
+ if (prlsdkUUIDParse(uuidstr, uuid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Domain UUID is malformed or empty"));
goto error;
+ }
return 0;
@@ -1724,8 +1725,10 @@ prlsdkEventsHandler(PRL_HANDLE prlEvent, PRL_VOID_PTR opaque)
pret = PrlEvent_GetType(prlEvent, &prlEventType);
prlsdkCheckRetGoto(pret, cleanup);
- if (prlsdkUUIDParse(uuidstr, uuid) < 0)
+ if (prlsdkUUIDParse(uuidstr, uuid) < 0) {
+ VIR_DEBUG("Skipping event type %d", prlEventType);
goto cleanup;
+ }
switch (prlEventType) {
case PET_DSP_EVT_VM_STATE_CHANGED:
--
1.7.1
9 years, 7 months
[libvirt] [PATCH] vz: set mount point for container image-based disks
by Maxim Nestratov
From: Maxim Nestratov <mnestratov(a)virtuozzo.com>
In order to support not only root disks with type=file for containers,
we need to specify mount points for them.
For instance, if a secondary disk is added by the following record in
xml:
<disk type='file' device='disk'>
<driver type='ploop' cache='writeback'/>
<source file='/vz/some_path_to_image_dir'/>
<target bus='sata' dev='sdb'/>
</disk>
we are going to add it to container mounted to '/mnt/sdb' path.
Signed-off-by: Maxim Nestratov <mnestratov(a)virtuozzo.com>
---
src/vz/vz_sdk.c | 35 +++++++++++++++++++++++++++++++----
1 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/src/vz/vz_sdk.c b/src/vz/vz_sdk.c
index 744b58a..6642933 100644
--- a/src/vz/vz_sdk.c
+++ b/src/vz/vz_sdk.c
@@ -3292,21 +3292,48 @@ static int prlsdkAddDisk(PRL_HANDLE sdkdom,
goto cleanup;
}
- if (bootDisk == true) {
+ if (bootDisk) {
pret = PrlVmDev_GetIndex(sdkdisk, &devIndex);
prlsdkCheckRetGoto(pret, cleanup);
if (prlsdkAddDeviceToBootList(sdkdom, devIndex, devType, 0) < 0)
goto cleanup;
- /* If we add physical device as a boot disk to container
- * we have to specify mount point for it */
- if (isCt) {
+ }
+
+ if (isCt) {
+
+ /* If we add a disk as a boot disk to a container
+ * (it doesn't matter whether it has image or real device backend)
+ * we have to specify the root mount point for it */
+ if (bootDisk) {
pret = PrlVmDevHd_SetMountPoint(sdkdisk, "/");
prlsdkCheckRetGoto(pret, cleanup);
+ } else {
+
+ /* In case of image we need to specify a mount point
+ * to make it usable within a container */
+ if (disk->src->type == VIR_STORAGE_TYPE_FILE) {
+ char *mount_point;
+ char *dev_name = strrchr(disk->dst, '/');
+
+ if (!dev_name) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("Unexpected format of disk destination '%s'."));
+ goto cleanup;
+ }
+
+ if (virAsprintf(&mount_point, "/mnt%s", dev_name) < 0)
+ goto cleanup;
+
+ pret = PrlVmDevHd_SetMountPoint(sdkdisk, mount_point);
+ VIR_FREE(mount_point);
+ prlsdkCheckRetGoto(pret, cleanup);
+ }
}
}
+
return 0;
cleanup:
PrlHandle_Free(sdkdisk);
--
1.7.1
9 years, 7 months
[libvirt] [PATCH] Add a new test case for setUserPassword
by Luyao Huang
Signed-off-by: Luyao Huang <lhuang(a)redhat.com>
---
cases/linux_domain.conf | 22 ++++++++
repos/domain/set_user_passwd.py | 111 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 133 insertions(+)
create mode 100644 repos/domain/set_user_passwd.py
diff --git a/cases/linux_domain.conf b/cases/linux_domain.conf
index 19daded..9a65cf3 100644
--- a/cases/linux_domain.conf
+++ b/cases/linux_domain.conf
@@ -299,6 +299,28 @@ domain:info_iothread
conn
qemu:///system
+domain:set_user_passwd
+ guestname
+ $defaultname
+ username
+ $username
+ userpassword
+ $password
+ conn
+ qemu:///system
+
+domain:set_user_passwd
+ guestname
+ $defaultname
+ username
+ $username
+ userpassword
+ $password
+ conn
+ qemu:///system
+ flags
+ encrypted
+
domain:destroy
guestname
$defaultname
diff --git a/repos/domain/set_user_passwd.py b/repos/domain/set_user_passwd.py
new file mode 100644
index 0000000..bf9bd06
--- /dev/null
+++ b/repos/domain/set_user_passwd.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+import libvirt
+from libvirt import libvirtError
+import lxml
+import lxml.etree
+import crypt
+from utils import utils
+
+required_params = ('guestname', 'username', 'userpassword',)
+optional_params = {'conn': 'qemu:///system', 'flags': '',}
+
+def get_guest_mac(vm):
+ tree = lxml.etree.fromstring(vm.XMLDesc(0))
+ set = tree.xpath("/domain/devices/interface/mac")
+
+ for n in set:
+ return n.attrib['address']
+
+ return False
+
+
+def check_agent_status(vm):
+ """ make sure agent is okay to use """
+
+ tree = lxml.etree.fromstring(vm.XMLDesc(0))
+
+ set = tree.xpath("//channel[@type='unix']/target[@name='org.qemu.guest_agent.0']")
+ for n in set:
+ if n.attrib['state'] == 'connected':
+ return True
+
+ return False
+
+def create_new_user(ipaddr, newusername, username, userpasswd, logger):
+ cmd = "useradd %s" % newusername
+ ret, retinfo = utils.remote_exec_pexpect(ipaddr, username, userpasswd, cmd)
+ if ret == 0 or "already exists" in retinfo:
+ return 0
+ else:
+ logger.error("Fail: cannot create a new user: %s" % retinfo)
+ return 1
+
+def verify_cur_user(ipaddr, username, userpasswd):
+ cmd = "whoami"
+ ret, retinfo = utils.remote_exec_pexpect(ipaddr, username, userpasswd, cmd)
+
+ return ret
+
+def set_user_passwd(params):
+ """
+ test API for setUserPassword in class virDomain
+ """
+
+ logger = params['logger']
+ guest = params['guestname']
+ username = params['username']
+ userpasswd = params['userpassword']
+
+ if 'flags' in params:
+ if params['flags'] == 'encrypted':
+ flags = libvirt.VIR_DOMAIN_PASSWORD_ENCRYPTED
+ else:
+ flags = 0
+ else:
+ flags = 0
+
+ try:
+ if 'conn' in params:
+ conn = libvirt.open(params['conn'])
+ else:
+ conn = libvirt.open(optional_params['conn'])
+
+ logger.info("get connection to libvirtd")
+ vm = conn.lookupByName(guest)
+ logger.info("test guest name: %s" % guest)
+
+ if not check_agent_status(vm):
+ logger.error("guest agent is not connected")
+ return 1
+
+ mac = get_guest_mac(vm)
+ if not mac:
+ logger.error("cannot get guest interface mac")
+ return 1
+
+ ipaddr = utils.mac_to_ip(mac, 180)
+ if not ipaddr:
+ logger.error("cannot get guest IP")
+ return 1
+
+ if flags > 0:
+ passwd = crypt.crypt("123456", crypt.mksalt(crypt.METHOD_SHA512))
+ else:
+ passwd = "123456"
+
+
+ if create_new_user(ipaddr, "usertestapi", username, userpasswd, logger) != 0:
+ return 1
+
+ vm.setUserPassword("usertestapi", passwd, flags)
+
+ if verify_cur_user(ipaddr, "usertestapi", "123456") != 0:
+ logger.error("cannot login guest via new user")
+ return 1
+
+ except libvirtError, e:
+ logger.error("API error message: %s" % e.message)
+ return 1
+
+ return 0
--
1.8.3.1
9 years, 7 months
[libvirt] [PATCH v2 0/7] Allow startupPolicy change on update-device
by Michal Privoznik
So, as Peter pointed out, we may want to updated startupPolicy for
live domains too. That's what patches 2/7-7/7 do.
Michal Privoznik (7):
qemuDomainUpdateDeviceConfig: Allow startupPolicy update, yet again
qemu: s/qemuDomainChangeDiskMediaLive/qemuDomainChangeDiskLive/
qemu_domain: Introduce qemuDomainDiskSourceDiffers
qemuDomainChangeDiskLive: rework slightly
qemu: s/virDomainDiskDiffersSourceOnly/qemuDomainDiskChangeSupported/
qemuDomainDiskChangeSupported: Fill in missing checks
qemuDomainChangeDiskLive: Allow startupPolicy change
src/conf/domain_conf.c | 127 ----------------------------------
src/conf/domain_conf.h | 2 -
src/libvirt_private.syms | 1 -
src/qemu/qemu_domain.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_domain.h | 7 ++
src/qemu/qemu_driver.c | 80 ++++++++++++---------
6 files changed, 232 insertions(+), 161 deletions(-)
--
2.4.6
9 years, 7 months
[libvirt] [libvirt-php] error compiling on OS X 10.11 GM
by Andy Blyler
I’m attempting to compile the HEAD of the git repo on OS X 10.11 GM and I’m getting the following error:
/Applications/Xcode.app/Contents/Developer/usr/bin/make all-recursive
Making all in src
/bin/sh ../libtool --tag=CC --mode=link clang -I/usr/local/Cellar/php56/5.6.13_2/include/php -I/usr/local/Cellar/php56/5.6.13_2/include/php/main -I/usr/local/Cellar/php56/5.6.13_2/include/php/TSRM -I/usr/local/Cellar/php56/5.6.13_2/include/php/Zend -I/usr/local/Cellar/php56/5.6.13_2/include/php/ext -I/usr/local/Cellar/php56/5.6.13_2/include/php/ext/date/lib -I/usr/local/Cellar/libxml2/2.9.2/include/libxml2 -I/usr/local/Cellar/libvirt/1.2.19/include -I/usr/local/Cellar/libvirt/1.2.19/include -DHAVE_CONFIG_H -I../winsrc -DCOMPILE_DL_LIBVIRT=1 -g -O2 -L/usr/local/Cellar/libxml2/2.9.2/lib -lxml2 -L/usr/local/Cellar/libvirt/1.2.19/lib -lvirt -L/usr/local/Cellar/libvirt/1.2.19/lib -lvirt-qemu -lvirt -o libvirt-php.la -rpath /usr/local/Cellar/php56-libvirt/HEAD/lib libvirt_php_la-vncfunc.lo libvirt_php_la-sockets.lo libvirt_php_la-libvirt-php.lo
libtool: link: clang -dynamiclib -Wl,-undefined -Wl,dynamic_lookup -o .libs/libvirt-php.0.dylib .libs/libvirt_php_la-vncfunc.o .libs/libvirt_php_la-sockets.o .libs/libvirt_php_la-libvirt-php.o -L/usr/local/Cellar/libxml2/2.9.2/lib -lxml2 -L/usr/local/Cellar/libvirt/1.2.19/lib -lvirt-qemu -lvirt -g -O2 -install_name /usr/local/Cellar/php56-libvirt/HEAD/lib/libvirt-php.0.dylib -compatibility_version 1 -current_version 1.0 -Wl,-single_module
duplicate symbol _gdebug in:
.libs/libvirt_php_la-vncfunc.o
.libs/libvirt_php_la-sockets.o
duplicate symbol _gdebug in:
.libs/libvirt_php_la-vncfunc.o
.libs/libvirt_php_la-libvirt-php.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
My environment is:
OS X: 10.11-x86_64
Xcode: 7.0
CLT: 7.0.0.0.1.1441394355
Clang: 7.0 build 700
X11: N/A
9 years, 7 months
[libvirt] [libvirt-php] error compiling on OS X 10.11 GM
by Andy Blyler
I’m attempting to compile the HEAD of the git repo on OS X 10.11 GM and I’m getting the following error:
/Applications/Xcode.app/Contents/Developer/usr/bin/make all-recursive
Making all in src
/bin/sh ../libtool --tag=CC --mode=link clang -I/usr/local/Cellar/php56/5.6.13_2/include/php -I/usr/local/Cellar/php56/5.6.13_2/include/php/main -I/usr/local/Cellar/php56/5.6.13_2/include/php/TSRM -I/usr/local/Cellar/php56/5.6.13_2/include/php/Zend -I/usr/local/Cellar/php56/5.6.13_2/include/php/ext -I/usr/local/Cellar/php56/5.6.13_2/include/php/ext/date/lib -I/usr/local/Cellar/libxml2/2.9.2/include/libxml2 -I/usr/local/Cellar/libvirt/1.2.19/include -I/usr/local/Cellar/libvirt/1.2.19/include -DHAVE_CONFIG_H -I../winsrc -DCOMPILE_DL_LIBVIRT=1 -g -O2 -L/usr/local/Cellar/libxml2/2.9.2/lib -lxml2 -L/usr/local/Cellar/libvirt/1.2.19/lib -lvirt -L/usr/local/Cellar/libvirt/1.2.19/lib -lvirt-qemu -lvirt -o libvirt-php.la -rpath /usr/local/Cellar/php56-libvirt/HEAD/lib libvirt_php_la-vncfunc.lo libvirt_php_la-sockets.lo libvirt_php_la-libvirt-php.lo
libtool: link: clang -dynamiclib -Wl,-undefined -Wl,dynamic_lookup -o .libs/libvirt-php.0.dylib .libs/libvirt_php_la-vncfunc.o .libs/libvirt_php_la-sockets.o .libs/libvirt_php_la-libvirt-php.o -L/usr/local/Cellar/libxml2/2.9.2/lib -lxml2 -L/usr/local/Cellar/libvirt/1.2.19/lib -lvirt-qemu -lvirt -g -O2 -install_name /usr/local/Cellar/php56-libvirt/HEAD/lib/libvirt-php.0.dylib -compatibility_version 1 -current_version 1.0 -Wl,-single_module
duplicate symbol _gdebug in:
.libs/libvirt_php_la-vncfunc.o
.libs/libvirt_php_la-sockets.o
duplicate symbol _gdebug in:
.libs/libvirt_php_la-vncfunc.o
.libs/libvirt_php_la-libvirt-php.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
My environment is:
OS X: 10.11-x86_64
Xcode: 7.0
CLT: 7.0.0.0.1.1441394355
Clang: 7.0 build 700
X11: N/A
9 years, 7 months
[libvirt] [PATCH] libxl: fix AttachDeviceConfig on hostdev type
by Chunyan Liu
After attach-device a <hostdev> with --config, new device doesn't
show up in dumpxml and in guest.
To fix that, set dev->data.hostdev = NULL after work so that the
pointer is not freed, since vmdef has the pointer and still need it.
Signed-off-by: Chunyan Liu <cyliu(a)suse.com>
---
src/libxl/libxl_driver.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c
index e2797d5..8087c27 100644
--- a/src/libxl/libxl_driver.c
+++ b/src/libxl/libxl_driver.c
@@ -3312,6 +3312,7 @@ libxlDomainAttachDeviceConfig(virDomainDefPtr vmdef, virDomainDeviceDefPtr dev)
if (virDomainHostdevInsert(vmdef, hostdev) < 0)
return -1;
+ dev->data.hostdev = NULL;
break;
default:
--
1.8.5.6
9 years, 7 months