diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/changelog uvtool-0~bzr98/debian/changelog --- uvtool-0~bzr92+0~snap141205.1/debian/changelog 2014-12-06 03:38:26.000000000 +0000 +++ uvtool-0~bzr98/debian/changelog 2015-04-02 15:35:02.000000000 +0000 @@ -1,24 +1,115 @@ -uvtool (0~bzr92+0~snap141205.1-0ubuntu1) vivid; urgency=medium +uvtool (0~bzr98-0ubuntu1~snap1) vivid; urgency=medium - * new upstream snapshot - + update url referenced by '--snappy' - + temporarily increase default disk size to 10G. + * fix 'uvt-simplestreams-libvirt query' due to lack of 'label' + in the snappy streams. - -- Scott Moser Fri, 05 Dec 2014 20:53:00 -0500 + -- Scott Moser Thu, 02 Apr 2015 11:35:01 -0400 -uvtool (0~bzr92+0~snap141205.0-0ubuntu1) vivid; urgency=low +uvtool (0~bzr98-0ubuntu1~snap0) vivid; urgency=medium - * new upstream snapshot - + add '--snappy' flag to 'uvt-libvirt-simplestreams sync' - + add default arch unless an arch is specified. - + add '--add-user-data' flag for appending user-data. + * New upstream snapshot. + * add snappy support - -- Scott Moser Fri, 05 Dec 2014 14:20:43 -0500 + -- Scott Moser Wed, 01 Apr 2015 16:23:32 -0400 -uvtool (0~bzr92+0~snap141204.1-0ubuntu1) vivid; urgency=low +uvtool (0~bzr92-0ubuntu2) utopic; urgency=medium - * new development upstream release - + no longer need 'uvtool ssh --insecure' - + add 'uvtool create --wait' - + do not wait for runlevel 2 - -- Scott Moser Wed, 03 Dec 2014 20:42:30 -0500 + * With libvirt 1.2.8, the second disk is being reported as type iso, + which confuses libvirt. As a temporary workaround, intercept that and call + it raw. Thanks to Serge Hallyn and Stefan Bader for the patch (LP: + #1372368). + + -- Robie Basak Thu, 25 Sep 2014 10:45:32 +0000 + +uvtool (0~bzr92-0ubuntu1) trusty; urgency=low + + * New upstream snapshot (bugfixes only): + - Fix manpage to reflect correct "create" default. + - Correctly handle wait with custom ssh keys (LP: #1287140). + + -- Robie Basak Tue, 01 Apr 2014 13:07:55 +0100 + +uvtool (0~bzr90-0ubuntu1) trusty; urgency=low + + * New upstream snapshot (no new features): + - Add missing copyright and licence notices. + - Correctly print missing ssh key path when not found. + - Add manpages. + + -- Robie Basak Wed, 12 Mar 2014 11:20:53 +0000 + +uvtool (0~bzr87-0ubuntu1) trusty; urgency=low + + * New upstream snapshot: + - Suppress spurious terminal output from libvirt API (LP: #1228231). + - Add --packages option, drop avahi-daemon default. + - Fail wait if the libvirt domain is not running. + - Remote wait support (LP: #1245733). + - Skip some tests when backported to Ubuntu Precise. + - Do not cause backtraces on some standard errors (LP: #1245641). + - Add standard Intel architecture features (LP: #1256658). + - Default to an ssh key from an agent if available. + - Add support for --meta-data passing. + - Default to 'ubuntu' login name for ssh (LP: #1280588). + - Add yaml and pyinotify build dependencies. + - Test for ssh parameter handling. + - purge: explicitly specify flags for compatibility (LP: #1248389). + - Drop "uvt-kvm import". + - Drop experimental CLI warnings. + + -- Robie Basak Wed, 19 Feb 2014 22:26:29 +0000 + +uvtool (0~bzr68-0ubuntu1) trusty; urgency=low + + * New upstream snapshot: + - Delete the created volume if the stream write fails. + - Handle new "virsh -q pool-list" format. + + -- Robie Basak Thu, 23 Jan 2014 17:32:25 +0000 + +uvtool (0~bzr66-0ubuntu1) trusty; urgency=low + + * New upstream snapshot: + - New subcommands: ip, ssh and wait. + - Warn about unimplemented boot-finished wait. + - Add required dependency python-yaml (LP: #1242383). + - New uvt-kvm create options: --run-script-once and --ssh-public-key-file. + - New dependency on python-pyinotify. + - Diagnose libvirtd failure on postinst failure. + - Don't drop model=virtio when specifying bridge. + - Fix 'TypeError: not all arguments converted during string formatting' + error. + - New uvt-simplestreams-libvirt option: --no-authentication. + - Do not remove old volumes immediately after sync (LP: #1251296). + - Add simplestreams sync tests. + - More human-readable "query" command output. + + -- Robie Basak Thu, 12 Dec 2013 12:08:47 +0000 + +uvtool (0~bzr42-0ubuntu1) saucy; urgency=low + + New upstream snapshot: + - Add --disk option and default to 8G (LP: #1234830). + - Use a constant for the volume pool name. + - Add --backing-image-file option. + - Add experimental CLI notice. + - Depend on cloud-image-utils >= 0.27 (LP: #1236724). + - Add purge subcommand to uvt-simplestreams-libvirt (LP: #1234824). + + -- Robie Basak Tue, 08 Oct 2013 16:53:09 +0100 + +uvtool (0~bzr36-0ubuntu1) saucy; urgency=low + + * New upstream snapshot: + - Workaround to make sure that libvirtd is running and ready before + attempting to create the volume pool (LP: #1228210). + + d/uvtool-libvirt-postinst: wait logic. + + d/control: add dependency on socat (used by the wait logic). + + -- Robie Basak Wed, 02 Oct 2013 11:35:06 +0100 + +uvtool (0~bzr35-0ubuntu1) saucy; urgency=low + + * Initial release (LP: #1218508). + + -- Robie Basak Fri, 20 Sep 2013 17:33:54 +0100 diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/control uvtool-0~bzr98/debian/control --- uvtool-0~bzr92+0~snap141205.1/debian/control 2014-12-03 20:06:00.000000000 +0000 +++ uvtool-0~bzr98/debian/control 2015-04-01 18:15:37.000000000 +0000 @@ -41,7 +41,7 @@ socat, ${misc:Depends}, ${python:Depends} -Recommends: qemu-kvm, libnss-mdns, cpu-checker +Recommends: qemu-kvm, cpu-checker Description: Library and tools for using Ubuntu Cloud Images with libvirt This package provides libvirt-specific tools for consuming Ubuntu Cloud images. Since it depends on libvirt-bin, installing this package will also diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0001-fix-second-disk-being-seen-as-iso.patch uvtool-0~bzr98/debian/patches/0001-fix-second-disk-being-seen-as-iso.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0001-fix-second-disk-being-seen-as-iso.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0001-fix-second-disk-being-seen-as-iso.patch 2015-04-01 18:15:42.000000000 +0000 @@ -0,0 +1,32 @@ +From 8b7545cdc434a90e90df71bc22be9a2a60993dc6 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Wed, 3 Dec 2014 15:17:05 -0500 +Subject: [PATCH 01/11] fix second-disk being seen as iso + +With libvirt 1.2.8, the second disk is being reported as type iso, which +confuses libvirt. As a temporary workaround, intercept that and call it +raw. Thanks to Serge Hallyn and Stefan Bader for the patch +(LP: #1372368). + +Bug-Ubuntu: https://launchpad.net/bugs/1372368 +Author: Serge Hallyn +--- + uvtool/libvirt/kvm.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/uvtool/libvirt/kvm.py b/uvtool/libvirt/kvm.py +index f6028c5..0e97ff0 100755 +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -284,6 +284,8 @@ def compose_domain_xml(name, volumes, cpu=1, memory=512, unsafe_caching=False, + find('format'). + get('type') + ) ++ if disk_format_type == "iso": ++ disk_format_type = "raw" + if unsafe_caching: + disk_driver = E.driver( + name='qemu', type=disk_format_type, cache='unsafe') +-- +2.1.4 + diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0002-Generate-ssh-keys-on-host-side.patch uvtool-0~bzr98/debian/patches/0002-Generate-ssh-keys-on-host-side.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0002-Generate-ssh-keys-on-host-side.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0002-Generate-ssh-keys-on-host-side.patch 2015-04-01 18:18:16.000000000 +0000 @@ -0,0 +1,102 @@ +From 71c95b60a2fd537f05eef0913c9fdfca37b0c9a1 Mon Sep 17 00:00:00 2001 +From: Robie Basak +Date: Wed, 3 Dec 2014 12:03:54 +0000 +Subject: [PATCH 02/11] Generate ssh keys on host side + +--- + debian/uvtool-libvirt.pyinstall | 1 + + uvtool/libvirt/kvm.py | 2 ++ + uvtool/ssh.py | 69 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 72 insertions(+) + create mode 100644 uvtool/ssh.py + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -41,6 +41,7 @@ from lxml.builder import E + + import uvtool.libvirt + import uvtool.libvirt.simplestreams ++import uvtool.ssh + import uvtool.wait + + DEFAULT_TEMPLATE = '/usr/share/uvtool/libvirt/template.xml' +@@ -154,6 +155,7 @@ def create_default_user_data(fobj, args) + data = { + b'hostname': args.hostname.encode('ascii'), + b'manage_etc_hosts': b'localhost', ++ b'ssh_keys': uvtool.ssh.generate_ssh_host_keys()[0], + } + + if ssh_authorized_keys: +--- /dev/null ++++ b/uvtool/ssh.py +@@ -0,0 +1,69 @@ ++#!/usr/bin/python ++ ++# Copyright (C) 2014 Canonical Ltd. ++# Author: Robie Basak ++# ++# This program is free software: you can redistribute it and/or modify ++# it under the terms of the GNU Affero General Public License as published by ++# the Free Software Foundation, either version 3 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 Affero General Public License for more details. ++# ++# You should have received a copy of the GNU Affero General Public License ++# along with this program. If not, see . ++ ++KEY_TYPES = ['rsa', 'dsa', 'ecdsa', 'ed25519'] ++ ++import os ++import shutil ++import subprocess ++import tempfile ++ ++ ++def _keygen(key_type, private_path): ++ subprocess.check_call([ ++ 'ssh-keygen', ++ '-q', ++ '-f', private_path, ++ '-N', '', ++ '-t', key_type, ++ '-C', 'root@localhost' ++ ]) ++ ++ ++def read_file(path): ++ with open(path, 'rb') as f: ++ return f.read() ++ ++ ++def generate_ssh_host_keys(): ++ cloud_init_result = {} ++ known_hosts_result = [] ++ tmp_dir = tempfile.mkdtemp(prefix='uvt-kvm.sshtmp') ++ try: ++ for key_type in KEY_TYPES: ++ private_path = os.path.join(tmp_dir, key_type) ++ _keygen(key_type, private_path) ++ ++ # ssh-keygen(1) defines that ".pub" is appended ++ public_path = private_path + ".pub" ++ ++ key_type_utf8 = key_type.encode('utf-8') ++ private_ci_key = key_type_utf8 + b'_private' ++ public_ci_key = key_type_utf8 + b'_public' ++ ++ private_key = read_file(private_path) ++ public_key = read_file(public_path) ++ ++ cloud_init_result[private_ci_key] = private_key ++ cloud_init_result[public_ci_key] = public_key ++ ++ known_hosts_result.append(public_key) ++ finally: ++ shutil.rmtree(tmp_dir) ++ ++ return cloud_init_result, b''.join(known_hosts_result) diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0003-Store-ssh-host-keys-in-libvirt-domain-xml.patch uvtool-0~bzr98/debian/patches/0003-Store-ssh-host-keys-in-libvirt-domain-xml.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0003-Store-ssh-host-keys-in-libvirt-domain-xml.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0003-Store-ssh-host-keys-in-libvirt-domain-xml.patch 2015-04-01 18:20:04.000000000 +0000 @@ -0,0 +1,132 @@ +From c5ea0e77cfa12b43e4a679d2d25d45ce05c0f467 Mon Sep 17 00:00:00 2001 +From: Robie Basak +Date: Wed, 3 Dec 2014 14:13:21 +0000 +Subject: [PATCH 03/11] Store ssh host keys in libvirt domain xml + +--- + uvtool/libvirt/__init__.py | 3 +++ + uvtool/libvirt/kvm.py | 35 ++++++++++++++++++++++++++++++----- + 2 files changed, 33 insertions(+), 5 deletions(-) + +--- a/uvtool/libvirt/__init__.py ++++ b/uvtool/libvirt/__init__.py +@@ -35,6 +35,9 @@ from lxml.builder import E + LIBVIRT_DNSMASQ_LEASE_FILE = '/var/lib/libvirt/dnsmasq/default.leases' + LIBVIRT_DNSMASQ_STATUS_FILE = '/var/lib/libvirt/dnsmasq/virbr0.status' + ++# The xmlns used for custom libvirt domain xml storage ++LIBVIRT_METADATA_XMLNS = 'https://launchpad.net/uvtool/libvirt/1' ++ + + def get_libvirt_pool_object(libvirt_conn, pool_name): + try: +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -24,6 +24,7 @@ from __future__ import unicode_literals + + import argparse + import errno ++import functools + import itertools + import os + import shutil +@@ -37,9 +38,10 @@ import yaml + + import libvirt + from lxml import etree +-from lxml.builder import E ++from lxml.builder import E, ElementMaker + + import uvtool.libvirt ++from uvtool.libvirt import LIBVIRT_METADATA_XMLNS + import uvtool.libvirt.simplestreams + import uvtool.ssh + import uvtool.wait +@@ -144,7 +146,7 @@ def get_ssh_authorized_keys(filename): + return [] + + +-def create_default_user_data(fobj, args): ++def create_default_user_data(fobj, args, ssh_host_keys=None): + """Write some sensible default cloud-init user-data to the given file + object. + +@@ -158,6 +160,9 @@ def create_default_user_data(fobj, args) + b'ssh_keys': uvtool.ssh.generate_ssh_host_keys()[0], + } + ++ if ssh_host_keys: ++ data[b'ssh_keys'] = ssh_host_keys ++ + if ssh_authorized_keys: + data[b'ssh_authorized_keys'] = ssh_authorized_keys + +@@ -259,7 +264,8 @@ def create_cow_volume_by_path(backing_vo + + + def compose_domain_xml(name, volumes, cpu=1, memory=512, unsafe_caching=False, +- template_path=DEFAULT_TEMPLATE, log_console_output=False, bridge=None): ++ template_path=DEFAULT_TEMPLATE, log_console_output=False, bridge=None, ++ ssh_known_hosts=None): + tree = etree.parse(template_path) + domain = tree.getroot() + assert domain.tag == 'domain' +@@ -321,6 +327,17 @@ def compose_domain_xml(name, volumes, cp + etree.strip_elements(devices, 'serial') + devices.append(E.serial(E.target(port='0'), type='stdio')) + ++ if ssh_known_hosts: ++ metadata = domain.find('metadata') ++ if metadata is None: ++ metadata = E.metadata() ++ domain.append(metadata) ++ EX = ElementMaker( ++ namespace=LIBVIRT_METADATA_XMLNS, ++ nsmap={'uvt': LIBVIRT_METADATA_XMLNS} ++ ) ++ metadata.append(EX.ssh_known_hosts(ssh_known_hosts)) ++ + return etree.tostring(tree) + + +@@ -337,7 +354,8 @@ def get_base_image(filters): + + def create(hostname, filters, user_data_fobj, meta_data_fobj, memory=512, + cpu=1, disk=2, unsafe_caching=False, template_path=DEFAULT_TEMPLATE, +- log_console_output=False, bridge=None, backing_image_file=None): ++ log_console_output=False, bridge=None, backing_image_file=None, ++ ssh_known_hosts=None): + if backing_image_file is None: + base_volume_name = get_base_image(filters) + undo_volume_creation = [] +@@ -369,6 +387,7 @@ def create(hostname, filters, user_data_ + memory=memory, + template_path=template_path, + unsafe_caching=unsafe_caching, ++ ssh_known_hosts=ssh_known_hosts, + ) + conn = libvirt.open('qemu:///system') + domain = conn.defineXML(xml) +@@ -523,8 +542,13 @@ def main_create(parser, args): + ) + return + ++ ssh_host_keys, ssh_known_hosts = uvtool.ssh.generate_ssh_host_keys() ++ + user_data_fobj = apply_default_fobj( +- args, 'user_data', create_default_user_data ++ args, 'user_data', functools.partial( ++ create_default_user_data, ++ ssh_host_keys=ssh_host_keys ++ ) + ) + meta_data_fobj = apply_default_fobj( + args, 'meta_data', create_default_meta_data +@@ -543,6 +567,7 @@ def main_create(parser, args): + memory=args.memory, + template_path=args.template, + unsafe_caching=args.unsafe_caching, ++ ssh_known_hosts=ssh_known_hosts, + ) + + diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0004-Move-ssh-insecure-testing-to-ssh-function.patch uvtool-0~bzr98/debian/patches/0004-Move-ssh-insecure-testing-to-ssh-function.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0004-Move-ssh-insecure-testing-to-ssh-function.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0004-Move-ssh-insecure-testing-to-ssh-function.patch 2015-04-01 18:20:04.000000000 +0000 @@ -0,0 +1,143 @@ +From 79e4d549cec319e16434282ba4a1168c7870aac2 Mon Sep 17 00:00:00 2001 +From: Robie Basak +Date: Wed, 3 Dec 2014 14:44:49 +0000 +Subject: [PATCH 04/11] Move ssh insecure testing to ssh function + +Move the insecure test back to the ssh function, so that the ssh +function may conditionally permit secure access if the guest's host key +is available. + +This adds an internal exception to pass back to the caller, so that the +caller may provide the user with a more helpful error message or +warning depending on the situation. + +Tests updated. Since the test doesn't really care about the "insecure" +testing functionality, it can safely just request insecure mode all the +time and verify that this is what was requested. Then it can be +independent of whether the guest's host key is available. +--- + uvtool/libvirt/kvm.py | 69 ++++++++++++++++++++++++++++-------------------- + uvtool/tests/test_kvm.py | 4 ++- + 2 files changed, 43 insertions(+), 30 deletions(-) + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -56,6 +56,12 @@ class CLIError(Exception): + pass + + ++class InsecureError(RuntimeError): ++ """An insecure operation is required and the user did not permit it by ++ using --insecure.""" ++ pass ++ ++ + # From: http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html + def subprocess_setup(): + # Python installs a SIGPIPE handler by default. This is usually not what +@@ -484,7 +490,10 @@ def name_to_ips(name): + + + def ssh(name, login_name, arguments, stdin=None, checked=False, sysexit=True, +- private_key_file=None): ++ private_key_file=None, insecure=False): ++ if not insecure: ++ raise InsecureError() ++ + ips = name_to_ips(name) + ip_count = len(ips) + if not ip_count: +@@ -598,11 +607,6 @@ def main_ip(parser, args): + + + def main_ssh(parser, args, default_login_name='ubuntu'): +- if not args.insecure: +- raise CLIError( +- "ssh access with host key verification is not implemented. " + +- "Use --insecure iff you trust your network path to the guest." +- ) + if args.login_name: + login_name = args.login_name + name = args.name +@@ -612,33 +616,40 @@ def main_ssh(parser, args, default_login + login_name = default_login_name + name = args.name + +- return ssh(name, login_name, args.ssh_arguments) ++ try: ++ return ssh( ++ name, login_name, args.ssh_arguments, insecure=args.insecure) ++ except InsecureError: ++ raise CLIError( ++ "ssh access with host key verification is not implemented. " + ++ "Use --insecure iff you trust your network path to the guest." ++ ) + + + def main_wait_remote(parser, args): +- if not args.insecure: +- print( +- "Warning: secure wait for boot-finished not yet implemented; " +- "use --insecure.", +- file=sys.stderr +- ) +- return +- + with open(args.remote_wait_script, 'rb') as wait_script: +- ssh( +- args.name, +- args.remote_wait_user, +- [ +- 'env', +- 'UVTOOL_WAIT_INTERVAL=%s' % args.interval, +- 'UVTOOL_WAIT_TIMEOUT=%s' % args.timeout, +- 'sh', +- '-' +- ], +- checked=True, +- stdin=wait_script, +- private_key_file=args.ssh_private_key_file, +- ) ++ try: ++ ssh( ++ args.name, ++ args.remote_wait_user, ++ [ ++ 'env', ++ 'UVTOOL_WAIT_INTERVAL=%s' % args.interval, ++ 'UVTOOL_WAIT_TIMEOUT=%s' % args.timeout, ++ 'sh', ++ '-' ++ ], ++ checked=True, ++ stdin=wait_script, ++ private_key_file=args.ssh_private_key_file, ++ insecure=args.insecure, ++ ) ++ except InsecureError: ++ print( ++ "Warning: secure wait for boot-finished not yet implemented; " ++ "use --insecure.", ++ file=sys.stderr ++ ) + + + def main_wait(parser, args): +--- a/uvtool/tests/test_kvm.py ++++ b/uvtool/tests/test_kvm.py +@@ -30,12 +30,14 @@ class TestKVM(unittest.TestCase): + args.login_name = args_login_name + args.name = args_hostname + args.ssh_arguments = mock.sentinel.ssh_arguments ++ args.insecure = True + with mock.patch('uvtool.libvirt.kvm.ssh') as ssh_mock: + main_ssh(parser, args) + ssh_mock.assert_called_with( + expected_hostname, + expected_login_name, +- mock.sentinel.ssh_arguments ++ mock.sentinel.ssh_arguments, ++ insecure=True, + ) + + def test_ssh_default(self): diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0005-Use-the-guest-s-ssh-public-host-key-when-available.patch uvtool-0~bzr98/debian/patches/0005-Use-the-guest-s-ssh-public-host-key-when-available.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0005-Use-the-guest-s-ssh-public-host-key-when-available.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0005-Use-the-guest-s-ssh-public-host-key-when-available.patch 2015-04-01 18:16:30.000000000 +0000 @@ -0,0 +1,148 @@ +From 0eec27b88583943727275402b8464873a607ab16 Mon Sep 17 00:00:00 2001 +From: Robie Basak +Date: Wed, 3 Dec 2014 15:16:01 +0000 +Subject: [PATCH 05/11] Use the guest's ssh public host key when available + +--- + uvtool/libvirt/__init__.py | 21 +++++++++++++ + uvtool/libvirt/kvm.py | 75 +++++++++++++++++++++++++++++----------------- + 2 files changed, 68 insertions(+), 28 deletions(-) + +--- a/uvtool/libvirt/__init__.py ++++ b/uvtool/libvirt/__init__.py +@@ -289,3 +289,24 @@ def mac_to_ip(mac): + dnsmasq_lease_file_mac_to_ip(lowercase_mac) or + libvirt_dnsmasq_status_file_mac_to_ip(lowercase_mac) + ) ++ ++ ++def get_domain_ssh_known_hosts(domain_name, conn=None, prefix=None): ++ if conn is None: ++ conn = libvirt.open('qemu:///system') ++ ++ domain = conn.lookupByName(domain_name) ++ xml = etree.fromstring(domain.XMLDesc(0)) ++ element = xml.xpath( ++ '/domain/metadata/uvt:ssh_known_hosts', ++ namespaces={'uvt': LIBVIRT_METADATA_XMLNS} ++ ) ++ if element: ++ if prefix: ++ return "\n".join( ++ [prefix + l for l in element[0].text.splitlines()] ++ ) ++ else: ++ return element[0].text ++ else: ++ return None +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -491,9 +491,6 @@ def name_to_ips(name): + + def ssh(name, login_name, arguments, stdin=None, checked=False, sysexit=True, + private_key_file=None, insecure=False): +- if not insecure: +- raise InsecureError() +- + ips = name_to_ips(name) + ip_count = len(ips) + if not ip_count: +@@ -508,29 +505,52 @@ def ssh(name, login_name, arguments, std + ) + ip = ips[0] + +- ssh_call = [ +- 'ssh', +- '-o', 'UserKnownHostsFile=/dev/null', +- '-o', 'StrictHostKeyChecking=no', +- '-o', 'CheckHostIP=no' +- ] +- if login_name: +- ssh_call.extend(['-l', login_name]) +- if private_key_file: +- ssh_call.extend(['-i', private_key_file]) +- ssh_call.append(ip) +- ssh_call.extend(arguments) +- +- call = subprocess.check_call if checked else subprocess.call +- +- result = call( +- ssh_call, preexec_fn=subprocess_setup, close_fds=True, stdin=stdin +- ) +- +- if sysexit: +- sys.exit(result) +- +- return result ++ objects_to_close = [] ++ try: ++ ssh_call = [ ++ 'ssh', ++ ] ++ ++ ssh_known_hosts = uvtool.libvirt.get_domain_ssh_known_hosts( ++ name, prefix=('%s ' % ip) ++ ) ++ if ssh_known_hosts: ++ ssh_known_hosts_file = tempfile.NamedTemporaryFile( ++ prefix='uvt-kvm.known_hoststmp') ++ objects_to_close.append(ssh_known_hosts_file) ++ ssh_known_hosts_file.write(ssh_known_hosts) ++ ssh_known_hosts_file.flush() ++ ssh_call.extend( ++ ['-o', 'UserKnownHostsFile=%s' % ssh_known_hosts_file.name] ++ ) ++ else: ++ if not insecure: ++ raise InsecureError() ++ ssh_call.extend([ ++ '-o', 'UserKnownHostsFile=/dev/null', ++ '-o', 'StrictHostKeyChecking=no', ++ '-o', 'CheckHostIP=no', ++ ]) ++ ++ if login_name: ++ ssh_call.extend(['-l', login_name]) ++ if private_key_file: ++ ssh_call.extend(['-i', private_key_file]) ++ ssh_call.append(ip) ++ ssh_call.extend(arguments) ++ ++ call = subprocess.check_call if checked else subprocess.call ++ ++ result = call( ++ ssh_call, preexec_fn=subprocess_setup, close_fds=True, stdin=stdin ++ ) ++ ++ if sysexit: ++ sys.exit(result) ++ ++ return result ++ finally: ++ [x.close() for x in objects_to_close] + + + def main_create(parser, args): +@@ -621,7 +641,7 @@ def main_ssh(parser, args, default_login + name, login_name, args.ssh_arguments, insecure=args.insecure) + except InsecureError: + raise CLIError( +- "ssh access with host key verification is not implemented. " + ++ "ssh public host key not found. " + + "Use --insecure iff you trust your network path to the guest." + ) + +@@ -645,10 +665,9 @@ def main_wait_remote(parser, args): + insecure=args.insecure, + ) + except InsecureError: +- print( +- "Warning: secure wait for boot-finished not yet implemented; " +- "use --insecure.", +- file=sys.stderr ++ raise CLIError( ++ "ssh public host key not found. Use " ++ "--insecure iff you trust your network path to the guest." + ) + + diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0006-uvt-kvm-create-add-wait-flag.patch uvtool-0~bzr98/debian/patches/0006-uvt-kvm-create-add-wait-flag.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0006-uvt-kvm-create-add-wait-flag.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0006-uvt-kvm-create-add-wait-flag.patch 2015-04-01 18:20:05.000000000 +0000 @@ -0,0 +1,83 @@ +From ff8e5904405f2f7b1609a484c085c71af3faa379 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Wed, 3 Dec 2014 16:28:02 -0500 +Subject: [PATCH 06/11] uvt-kvm create: add '--wait' flag + +this adds '--wait' and all the related flags to the create create +subcommand. so you can now: + uvt-kvm create --wait myhost release=vivid +--- + uvtool/libvirt/kvm.py | 31 +++++++++++++++++++++++-------- + 1 file changed, 23 insertions(+), 8 deletions(-) + mode change 100755 => 100644 uvtool/libvirt/kvm.py + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -599,6 +599,10 @@ def main_create(parser, args): + ssh_known_hosts=ssh_known_hosts, + ) + ++ if args.wait: ++ args.name = args.hostname ++ main_wait(parser, args) ++ + + def main_destroy(parser, args): + for h in args.hostname: +@@ -715,6 +719,17 @@ def main(args): + + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() ++ ++ wait_args = [ ++ (('--timeout',), {'type': float, 'default': 120.0}), ++ (('--interval',), {'type': float, 'default': 8.0}), ++ (('--remote-wait-script',), {'default': DEFAULT_REMOTE_WAIT_SCRIPT}), ++ (('--insecure',), {'action': 'store_true'}), ++ (('--remote-wait-user',), {'default': 'ubuntu'}), ++ (('--without-ssh',), {'action': 'store_true'}), ++ (('--ssh-private-key-file',), {}), ++ ] ++ + create_subparser = subparsers.add_parser('create') + create_subparser.set_defaults(func=main_create) + create_subparser.add_argument( +@@ -729,12 +744,16 @@ def main(args): + '--user-data', type=argparse.FileType('rb')) + create_subparser.add_argument( + '--meta-data', type=argparse.FileType('rb')) ++ for w_args, w_kwargs in wait_args: ++ create_subparser.add_argument(*w_args, **w_kwargs) ++ + create_subparser.add_argument('--password') + create_subparser.add_argument('--log-console-output', action='store_true') + create_subparser.add_argument('--backing-image-file') + create_subparser.add_argument('--run-script-once', action='append') + create_subparser.add_argument('--ssh-public-key-file') + create_subparser.add_argument('--packages', action='append') ++ create_subparser.add_argument('--wait', default=False, action='store_true') + create_subparser.add_argument('hostname') + create_subparser.add_argument( + 'filters', nargs='*', metavar='filter', +@@ -754,17 +773,13 @@ def main(args): + ssh_subparser.add_argument('--login-name', '-l') + ssh_subparser.add_argument('name') + ssh_subparser.add_argument('ssh_arguments', nargs='*') ++ + wait_subparser = subparsers.add_parser('wait') + wait_subparser.set_defaults(func=main_wait) +- wait_subparser.add_argument('--timeout', type=float, default=120.0) +- wait_subparser.add_argument('--interval', type=float, default=8.0) +- wait_subparser.add_argument('--remote-wait-script', +- default=DEFAULT_REMOTE_WAIT_SCRIPT) +- wait_subparser.add_argument('--insecure', action='store_true') +- wait_subparser.add_argument('--remote-wait-user', default='ubuntu') +- wait_subparser.add_argument('--without-ssh', action='store_true') +- wait_subparser.add_argument('--ssh-private-key-file') ++ for w_args, w_kwargs in wait_args: ++ wait_subparser.add_argument(*w_args, **w_kwargs) + wait_subparser.add_argument('name') ++ + args = parser.parse_args(args) + args.func(parser, args) + diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0007-sync-add-default-arch-filter-unless-one-is-provided.patch uvtool-0~bzr98/debian/patches/0007-sync-add-default-arch-filter-unless-one-is-provided.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0007-sync-add-default-arch-filter-unless-one-is-provided.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0007-sync-add-default-arch-filter-unless-one-is-provided.patch 2015-04-01 18:20:05.000000000 +0000 @@ -0,0 +1,47 @@ +From 7c420857f42a61e274f7df921352d879028118a3 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Fri, 5 Dec 2014 09:36:45 -0500 +Subject: [PATCH 07/11] sync: add default arch filter unless one is provided + +If the user does not provide a 'arch' filter, then provide one for them. +Previously the arch was added only if no other filters were provided. +--- + uvtool/libvirt/simplestreams.py | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +--- a/uvtool/libvirt/simplestreams.py ++++ b/uvtool/libvirt/simplestreams.py +@@ -249,6 +249,12 @@ class LibvirtMirror(simplestreams.mirror + + + def main_sync(args): ++ # add arch filter if there is none. ++ for swith in ('arch=', 'arch~', 'arch!'): ++ if any([f.startswith(swith) for f in args.filters]): ++ args.filters.append(system_arch_filter()) ++ break ++ + (mirror_url, initial_path) = simplestreams.util.path_from_mirror_url( + args.mirror_url, args.path) + +@@ -270,6 +276,11 @@ def main_sync(args): + clean_extraneous_images() + + ++def system_arch_filter(): ++ return 'arch=%s' % subprocess.check_output( ++ ['dpkg', '--print-architecture']).decode().strip() ++ ++ + def libvirt_pool_name_to_useful_description_string(libvirt_pool_name): + volume_metadata = pool_metadata[libvirt_pool_name] + filters = ' '.join('='.join((key, volume_metadata[key])) for key in USEFUL_FIELD_NAMES) +@@ -312,7 +323,7 @@ def main(argv=None): + default='https://cloud-images.ubuntu.com/releases/') + sync_subparser.add_argument('--no-authentication', action='store_true') + sync_subparser.add_argument('filters', nargs='*', metavar='filter', +- default=["arch=%s" % system_arch]) ++ default=[system_arch_filter()]) + + query_subparser = subparsers.add_parser('query') + query_subparser.set_defaults(func=main_query) diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0008-create-add-add-user-data.patch uvtool-0~bzr98/debian/patches/0008-create-add-add-user-data.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0008-create-add-add-user-data.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0008-create-add-add-user-data.patch 2015-04-01 18:20:05.000000000 +0000 @@ -0,0 +1,97 @@ +From 48efb0b4c6cf97b8e787716ab91e89f8a5fa1df3 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Fri, 5 Dec 2014 14:13:11 -0500 +Subject: [PATCH 08/11] create: add '--add-user-data' + +Add the ability for user to append user-data pieces. +The pieces are then combined into a 'cloud-config-archive' format. +Later we could combine this into mime-multipart if that was more +useful. + +cloud-config-archive should be read in cloud-init back to 12.04 version. + +If the user specifies '--user-data' and no '--add-user-data', then +their user-data will be presented verbatum. +--- + uvtool/libvirt/kvm.py | 40 ++++++++++++++++++++++++++++------------ + 1 file changed, 28 insertions(+), 12 deletions(-) + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -152,12 +152,28 @@ def get_ssh_authorized_keys(filename): + return [] + + +-def create_default_user_data(fobj, args, ssh_host_keys=None): +- """Write some sensible default cloud-init user-data to the given file +- object. ++def get_userdata_fobj(args, ssh_host_keys=None): ++ """create a file object that contains user-data based on input""" ++ if args.user_data and not args.add_user_data: ++ # user provided '--user-data', but no '--add-user-data' ++ return args.user_data ++ ++ fobjs = args.add_user_data ++ if args.user_data: ++ fobjs.insert(0, args.user_data) ++ archive = [] ++ else: ++ archive = get_internal_user_data(args, ssh_host_keys) + +- """ ++ for fobj in fobjs: ++ archive.append(fobj.read()) ++ ++ return StringIO.StringIO( ++ '#cloud-config-archive\n' + yaml.safe_dump(archive)) + ++ ++def get_internal_user_data(args, ssh_host_keys=None): ++ "return a string with a default cloud-init user-data blob" + ssh_authorized_keys = get_ssh_authorized_keys(args.ssh_public_key_file) + + data = { +@@ -186,8 +202,7 @@ def create_default_user_data(fobj, args, + for s in itertools.chain(*[p.split(',') for p in args.packages]) + ] + +- fobj.write("#cloud-config\n") +- fobj.write(yaml.dump(data)) ++ return ["#cloud-config\n" + yaml.safe_dump(data)] + + + def create_default_meta_data(fobj, args): +@@ -563,6 +578,9 @@ def main_create(parser, args): + file=sys.stderr + ) + ++ if args.user_data and args.add_user_data: ++ parser.error("--user-data and --add-user-data conflict") ++ + kvm_ok, is_kvm_ok_output = check_kvm_ok() + if not kvm_ok: + print( +@@ -573,12 +591,7 @@ def main_create(parser, args): + + ssh_host_keys, ssh_known_hosts = uvtool.ssh.generate_ssh_host_keys() + +- user_data_fobj = apply_default_fobj( +- args, 'user_data', functools.partial( +- create_default_user_data, +- ssh_host_keys=ssh_host_keys +- ) +- ) ++ user_data_fobj = get_userdata_fobj(args, ssh_host_keys=ssh_host_keys) + meta_data_fobj = apply_default_fobj( + args, 'meta_data', create_default_meta_data + ) +@@ -743,6 +756,9 @@ def main(args): + create_subparser.add_argument( + '--user-data', type=argparse.FileType('rb')) + create_subparser.add_argument( ++ '--add-user-data', type=argparse.FileType('rb'), action='append', ++ default=[], help='add additional user-data part') ++ create_subparser.add_argument( + '--meta-data', type=argparse.FileType('rb')) + for w_args, w_kwargs in wait_args: + create_subparser.add_argument(*w_args, **w_kwargs) diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0009-wait-change-default-interval-from-8.0-to-1.0.patch uvtool-0~bzr98/debian/patches/0009-wait-change-default-interval-from-8.0-to-1.0.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0009-wait-change-default-interval-from-8.0-to-1.0.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0009-wait-change-default-interval-from-8.0-to-1.0.patch 2015-04-01 18:20:05.000000000 +0000 @@ -0,0 +1,23 @@ +From a3ea9aacd6d5a95aa178c3a7abdac7ee414f7cf4 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Fri, 5 Dec 2014 14:35:23 -0500 +Subject: [PATCH 09/11] --wait: change default interval from 8.0 to 1.0 + +the interval here is used inside the host while its booting to poll +for existance of a file. Instead of sleeping 8 seconds (an eternity) +just sleep 1. +--- + uvtool/libvirt/kvm.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -735,7 +735,7 @@ def main(args): + + wait_args = [ + (('--timeout',), {'type': float, 'default': 120.0}), +- (('--interval',), {'type': float, 'default': 8.0}), ++ (('--interval',), {'type': float, 'default': 1.0}), + (('--remote-wait-script',), {'default': DEFAULT_REMOTE_WAIT_SCRIPT}), + (('--insecure',), {'action': 'store_true'}), + (('--remote-wait-user',), {'default': 'ubuntu'}), diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0010-enable-ssh-for-snappy-by-default.patch uvtool-0~bzr98/debian/patches/0010-enable-ssh-for-snappy-by-default.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0010-enable-ssh-for-snappy-by-default.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0010-enable-ssh-for-snappy-by-default.patch 2015-04-01 18:20:05.000000000 +0000 @@ -0,0 +1,19 @@ +From 6830fb7ca0a573859e3d881ee0fcf02253acc103 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Thu, 4 Dec 2014 12:19:00 -0500 +Subject: [PATCH 10/11] enable ssh for snappy by default. + +--- + uvtool/libvirt/kvm.py | 1 + + 1 file changed, 1 insertion(+) + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -180,6 +180,7 @@ def get_internal_user_data(args, ssh_hos + b'hostname': args.hostname.encode('ascii'), + b'manage_etc_hosts': b'localhost', + b'ssh_keys': uvtool.ssh.generate_ssh_host_keys()[0], ++ b'snappy': {b'ssh_enabled': True}, + } + + if ssh_host_keys: diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/0011-sync-add-snappy-url-for-shortcut.patch uvtool-0~bzr98/debian/patches/0011-sync-add-snappy-url-for-shortcut.patch --- uvtool-0~bzr92+0~snap141205.1/debian/patches/0011-sync-add-snappy-url-for-shortcut.patch 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/0011-sync-add-snappy-url-for-shortcut.patch 2015-04-02 15:34:25.000000000 +0000 @@ -0,0 +1,54 @@ +From fea53452cdd036f71031d1531e921a2afe721763 Mon Sep 17 00:00:00 2001 +From: Scott Moser +Date: Fri, 5 Dec 2014 09:59:29 -0500 +Subject: [PATCH 11/11] sync: add '--snappy' url for shortcut + +This adds '--snappy' as a flag to uvt-simplestreams-libvirt sync. +It is just a shortcut for: + --source=long-url-to-snappy-images. +--- + uvtool/libvirt/simplestreams.py | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/uvtool/libvirt/simplestreams.py ++++ b/uvtool/libvirt/simplestreams.py +@@ -47,6 +47,7 @@ LIBVIRT_POOL_NAME = 'uvtool' + IMAGE_DIR = '/var/lib/uvtool/libvirt/images/' # must end in '/'; see use + METADATA_DIR = '/var/lib/uvtool/libvirt/metadata' + USEFUL_FIELD_NAMES = ['release', 'arch', 'label'] ++SNAPPY_STREAM_URL = 'http://cloud-images.ubuntu.com/snappy/' + + + def mkdir_p(path): +@@ -255,6 +256,9 @@ def main_sync(args): + args.filters.append(system_arch_filter()) + break + ++ if args.snappy: ++ args.mirror_url = SNAPPY_STREAM_URL ++ + (mirror_url, initial_path) = simplestreams.util.path_from_mirror_url( + args.mirror_url, args.path) + +@@ -283,7 +287,11 @@ def system_arch_filter(): + + def libvirt_pool_name_to_useful_description_string(libvirt_pool_name): + volume_metadata = pool_metadata[libvirt_pool_name] +- filters = ' '.join('='.join((key, volume_metadata[key])) for key in USEFUL_FIELD_NAMES) ++ info = [] ++ for key in USEFUL_FIELD_NAMES: ++ if key in volume_metadata: ++ info.append('%s=%s' % (key, volume_metadata[key])) ++ filters = ' '.join(info) + return ' '.join([filters, '(%s)' % volume_metadata['version_name']]) + + +@@ -319,6 +327,8 @@ def main(argv=None): + help='keyring to be specified to gpg via --keyring', + default='/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg' + ) ++ sync_subparser.add_argument('--snappy', action='store_true', ++ help='set --source for snappy images') + sync_subparser.add_argument('--source', dest='mirror_url', + default='https://cloud-images.ubuntu.com/releases/') + sync_subparser.add_argument('--no-authentication', action='store_true') diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/fix-iso-format uvtool-0~bzr98/debian/patches/fix-iso-format --- uvtool-0~bzr92+0~snap141205.1/debian/patches/fix-iso-format 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/fix-iso-format 2015-04-01 18:20:03.000000000 +0000 @@ -0,0 +1,15 @@ +Bug-Ubuntu: https://launchpad.net/bugs/1372368 +Author: Serge Hallyn +Last-Update: 2014-09-25 + +--- a/uvtool/libvirt/kvm.py ++++ b/uvtool/libvirt/kvm.py +@@ -284,6 +284,8 @@ def compose_domain_xml(name, volumes, cp + find('format'). + get('type') + ) ++ if disk_format_type == "iso": ++ disk_format_type = "raw" + if unsafe_caching: + disk_driver = E.driver( + name='qemu', type=disk_format_type, cache='unsafe') diff -Nru uvtool-0~bzr92+0~snap141205.1/debian/patches/series uvtool-0~bzr98/debian/patches/series --- uvtool-0~bzr92+0~snap141205.1/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ uvtool-0~bzr98/debian/patches/series 2015-04-01 18:15:58.000000000 +0000 @@ -0,0 +1,11 @@ +fix-iso-format +0002-Generate-ssh-keys-on-host-side.patch +0003-Store-ssh-host-keys-in-libvirt-domain-xml.patch +0004-Move-ssh-insecure-testing-to-ssh-function.patch +0005-Use-the-guest-s-ssh-public-host-key-when-available.patch +0006-uvt-kvm-create-add-wait-flag.patch +0007-sync-add-default-arch-filter-unless-one-is-provided.patch +0008-create-add-add-user-data.patch +0009-wait-change-default-interval-from-8.0-to-1.0.patch +0010-enable-ssh-for-snappy-by-default.patch +0011-sync-add-snappy-url-for-shortcut.patch diff -Nru uvtool-0~bzr92+0~snap141205.1/man/uvt-kvm.1 uvtool-0~bzr98/man/uvt-kvm.1 --- uvtool-0~bzr92+0~snap141205.1/man/uvt-kvm.1 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/man/uvt-kvm.1 2015-04-01 17:46:51.000000000 +0000 @@ -115,6 +115,9 @@ corresponds to the current LTS release as returned by .BR distro-info (1). +Alternatively, see \fB--backing-image-file\fR under ADVANCED OVERRIDE OPTIONS +below to supply a backing image directly yourself. + This subcommand supports an extensive set of options to modify the definition and behavior of the VM. See LIBVIRT DOMAIN DEFINTION OPTIONS, CLOUD-INIT CONFIGURATION OPTIONS and ADVANCED OVERRIDE OPTIONS below. @@ -457,6 +460,12 @@ Default: minimal file with automatically generated instance-id. +.TP +.BI --backing-image-file\ image_file +Specify the name of a local file that will be used to create the VM instead of +relying on the volume storage pool. It must point to a qcow2 formatted file. +This option overrides any simplestreams filters provided. + .SH ADVANCED USAGE .B uvt-kvm @@ -583,7 +592,7 @@ using them. If you are using -.BR --userdata , +.BR --user-data , then .B --password will be overridden by it and you will need to modify your cloud-init userdata diff -Nru uvtool-0~bzr92+0~snap141205.1/remote-wait.sh uvtool-0~bzr98/remote-wait.sh --- uvtool-0~bzr92+0~snap141205.1/remote-wait.sh 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/remote-wait.sh 2015-04-01 17:46:51.000000000 +0000 @@ -1,11 +1,12 @@ #!/bin/sh set -e -## Not waiting for runlevel 2 as this seems to work still -## on trusty, and snappy shows runlevel 5. Admittedly, don't know what -## problem this solved. -# Wait for runlevel 2 -#while [ "$(runlevel|awk '{print $2}')" != 2 ]; do sleep $UVTOOL_WAIT_INTERVAL; done +# Wait for runlevel 2 (upstart) or 5 (systemd) +while :; do + runlevel=`runlevel|awk '{print $2}'` + [ "$runlevel" = 2 -o "$runlevel" = 5 ] && break + sleep $UVTOOL_WAIT_INTERVAL +done # Wait for cloud-init's signal while [ ! -e /var/lib/cloud/instance/boot-finished ]; do sleep $UVTOOL_WAIT_INTERVAL; done diff -Nru uvtool-0~bzr92+0~snap141205.1/uvtool/libvirt/__init__.py uvtool-0~bzr98/uvtool/libvirt/__init__.py --- uvtool-0~bzr92+0~snap141205.1/uvtool/libvirt/__init__.py 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/uvtool/libvirt/__init__.py 2015-04-01 17:46:51.000000000 +0000 @@ -20,7 +20,9 @@ import codecs import contextlib +import errno import itertools +import json import os import shutil import subprocess @@ -31,9 +33,7 @@ from lxml.builder import E LIBVIRT_DNSMASQ_LEASE_FILE = '/var/lib/libvirt/dnsmasq/default.leases' - -# The xmlns used for custom libvirt domain xml storage -LIBVIRT_METADATA_XMLNS = 'https://launchpad.net/uvtool/libvirt/1' +LIBVIRT_DNSMASQ_STATUS_FILE = '/var/lib/libvirt/dnsmasq/virbr0.status' def get_libvirt_pool_object(libvirt_conn, pool_name): @@ -239,32 +239,50 @@ yield mac.get('address') -def mac_to_ip(mac): - canonical_mac = mac.lower() - with codecs.open(LIBVIRT_DNSMASQ_LEASE_FILE, 'r') as f: +def dnsmasq_lease_file_mac_to_ip(lowercase_mac): + MAC_FIELD = 1 + IP_FIELD = 2 + MIN_FIELDS = max(MAC_FIELD, IP_FIELD) + + try: + f = codecs.open(LIBVIRT_DNSMASQ_LEASE_FILE, 'r') + except IOError as e: + if e.errno == errno.ENOENT: + return None + else: + raise + + with contextlib.closing(f): for line in f: fields = line.split() - if len(fields) > 1 and fields[1].lower() == canonical_mac: - return fields[2] + if (len(fields) >= MIN_FIELDS and + fields[MAC_FIELD].lower() == lowercase_mac): + return fields[IP_FIELD] return None -def get_domain_ssh_known_hosts(domain_name, conn=None, prefix=None): - if conn is None: - conn = libvirt.open('qemu:///system') +def libvirt_dnsmasq_status_file_mac_to_ip(lowercase_mac): + try: + f = codecs.open(LIBVIRT_DNSMASQ_STATUS_FILE , 'r') + except IOError as e: + if e.errno == errno.ENOENT: + return None + else: + raise - domain = conn.lookupByName(domain_name) - xml = etree.fromstring(domain.XMLDesc(0)) - element = xml.xpath( - '/domain/metadata/uvt:ssh_known_hosts', - namespaces={'uvt': LIBVIRT_METADATA_XMLNS} + with contextlib.closing(f): + j = json.load(f) + + for entry in j: + if entry.get('mac-address') == lowercase_mac: + return entry['ip-address'] + + return None + + +def mac_to_ip(mac): + lowercase_mac = mac.lower() + return ( + dnsmasq_lease_file_mac_to_ip(lowercase_mac) or + libvirt_dnsmasq_status_file_mac_to_ip(lowercase_mac) ) - if element: - if prefix: - return "\n".join( - [prefix + l for l in element[0].text.splitlines()] - ) - else: - return element[0].text - else: - return None diff -Nru uvtool-0~bzr92+0~snap141205.1/uvtool/libvirt/kvm.py uvtool-0~bzr98/uvtool/libvirt/kvm.py --- uvtool-0~bzr92+0~snap141205.1/uvtool/libvirt/kvm.py 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/uvtool/libvirt/kvm.py 2015-04-01 17:46:51.000000000 +0000 @@ -24,7 +24,6 @@ import argparse import errno -import functools import itertools import os import shutil @@ -38,12 +37,10 @@ import libvirt from lxml import etree -from lxml.builder import E, ElementMaker +from lxml.builder import E import uvtool.libvirt -from uvtool.libvirt import LIBVIRT_METADATA_XMLNS import uvtool.libvirt.simplestreams -import uvtool.ssh import uvtool.wait DEFAULT_TEMPLATE = '/usr/share/uvtool/libvirt/template.xml' @@ -56,12 +53,6 @@ pass -class InsecureError(RuntimeError): - """An insecure operation is required and the user did not permit it by - using --insecure.""" - pass - - # From: http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html def subprocess_setup(): # Python installs a SIGPIPE handler by default. This is usually not what @@ -152,40 +143,19 @@ return [] -def get_userdata_fobj(args, ssh_host_keys=None): - """create a file object that contains user-data based on input""" - if args.user_data and not args.add_user_data: - # user provided '--user-data', but no '--add-user-data' - return args.user_data - - fobjs = args.add_user_data - if args.user_data: - fobjs.insert(0, args.user_data) - archive = [] - else: - archive = get_internal_user_data(args, ssh_host_keys) - - for fobj in fobjs: - archive.append(fobj.read()) - - return StringIO.StringIO( - '#cloud-config-archive\n' + yaml.safe_dump(archive)) +def create_default_user_data(fobj, args): + """Write some sensible default cloud-init user-data to the given file + object. + """ -def get_internal_user_data(args, ssh_host_keys=None): - "return a string with a default cloud-init user-data blob" ssh_authorized_keys = get_ssh_authorized_keys(args.ssh_public_key_file) data = { b'hostname': args.hostname.encode('ascii'), b'manage_etc_hosts': b'localhost', - b'snappy': {b'ssh_enabled': True}, - b'ssh_keys': uvtool.ssh.generate_ssh_host_keys()[0], } - if ssh_host_keys: - data[b'ssh_keys'] = ssh_host_keys - if ssh_authorized_keys: data[b'ssh_authorized_keys'] = ssh_authorized_keys @@ -203,7 +173,8 @@ for s in itertools.chain(*[p.split(',') for p in args.packages]) ] - return ["#cloud-config\n" + yaml.safe_dump(data)] + fobj.write("#cloud-config\n") + fobj.write(yaml.dump(data)) def create_default_meta_data(fobj, args): @@ -286,8 +257,7 @@ def compose_domain_xml(name, volumes, cpu=1, memory=512, unsafe_caching=False, - template_path=DEFAULT_TEMPLATE, log_console_output=False, bridge=None, - ssh_known_hosts=None): + template_path=DEFAULT_TEMPLATE, log_console_output=False, bridge=None): tree = etree.parse(template_path) domain = tree.getroot() assert domain.tag == 'domain' @@ -314,8 +284,6 @@ find('format'). get('type') ) - if disk_format_type == "iso": - disk_format_type = "raw" if unsafe_caching: disk_driver = E.driver( name='qemu', type=disk_format_type, cache='unsafe') @@ -349,17 +317,6 @@ etree.strip_elements(devices, 'serial') devices.append(E.serial(E.target(port='0'), type='stdio')) - if ssh_known_hosts: - metadata = domain.find('metadata') - if metadata is None: - metadata = E.metadata() - domain.append(metadata) - EX = ElementMaker( - namespace=LIBVIRT_METADATA_XMLNS, - nsmap={'uvt': LIBVIRT_METADATA_XMLNS} - ) - metadata.append(EX.ssh_known_hosts(ssh_known_hosts)) - return etree.tostring(tree) @@ -376,8 +333,7 @@ def create(hostname, filters, user_data_fobj, meta_data_fobj, memory=512, cpu=1, disk=2, unsafe_caching=False, template_path=DEFAULT_TEMPLATE, - log_console_output=False, bridge=None, backing_image_file=None, - ssh_known_hosts=None): + log_console_output=False, bridge=None, backing_image_file=None): if backing_image_file is None: base_volume_name = get_base_image(filters) undo_volume_creation = [] @@ -409,7 +365,6 @@ memory=memory, template_path=template_path, unsafe_caching=unsafe_caching, - ssh_known_hosts=ssh_known_hosts, ) conn = libvirt.open('qemu:///system') domain = conn.defineXML(xml) @@ -506,61 +461,44 @@ def ssh(name, login_name, arguments, stdin=None, checked=False, sysexit=True, - private_key_file=None, insecure=False): + private_key_file=None): ips = name_to_ips(name) - if len(ips) > 1: + ip_count = len(ips) + if not ip_count: + raise CLIError( + "no IP address found for libvirt machine %s. " + "Has it had time to boot yet?\nTry: %s wait" % + (repr(name), sys.argv[0])) + elif ip_count > 1: raise CLIError( "multiple IPs detected for %s %s and are not supported." % (repr(name), repr(ips)) ) ip = ips[0] - objects_to_close = [] - try: - ssh_call = [ - 'ssh', - ] + ssh_call = [ + 'ssh', + '-o', 'UserKnownHostsFile=/dev/null', + '-o', 'StrictHostKeyChecking=no', + '-o', 'CheckHostIP=no' + ] + if login_name: + ssh_call.extend(['-l', login_name]) + if private_key_file: + ssh_call.extend(['-i', private_key_file]) + ssh_call.append(ip) + ssh_call.extend(arguments) - ssh_known_hosts = uvtool.libvirt.get_domain_ssh_known_hosts( - name, prefix=('%s ' % ip) - ) - if ssh_known_hosts: - ssh_known_hosts_file = tempfile.NamedTemporaryFile( - prefix='uvt-kvm.known_hoststmp') - objects_to_close.append(ssh_known_hosts_file) - ssh_known_hosts_file.write(ssh_known_hosts) - ssh_known_hosts_file.flush() - ssh_call.extend( - ['-o', 'UserKnownHostsFile=%s' % ssh_known_hosts_file.name] - ) - else: - if not insecure: - raise InsecureError() - ssh_call.extend([ - '-o', 'UserKnownHostsFile=/dev/null', - '-o', 'StrictHostKeyChecking=no', - '-o', 'CheckHostIP=no', - ]) - - if login_name: - ssh_call.extend(['-l', login_name]) - if private_key_file: - ssh_call.extend(['-i', private_key_file]) - ssh_call.append(ip) - ssh_call.extend(arguments) - - call = subprocess.check_call if checked else subprocess.call + call = subprocess.check_call if checked else subprocess.call - result = call( - ssh_call, preexec_fn=subprocess_setup, close_fds=True, stdin=stdin - ) + result = call( + ssh_call, preexec_fn=subprocess_setup, close_fds=True, stdin=stdin + ) - if sysexit: - sys.exit(result) + if sysexit: + sys.exit(result) - return result - finally: - [x.close() for x in objects_to_close] + return result def main_create(parser, args): @@ -573,9 +511,6 @@ file=sys.stderr ) - if args.user_data and args.add_user_data: - parser.error("--user-data and --add-user-data conflict") - kvm_ok, is_kvm_ok_output = check_kvm_ok() if not kvm_ok: print( @@ -584,9 +519,9 @@ ) return - ssh_host_keys, ssh_known_hosts = uvtool.ssh.generate_ssh_host_keys() - - user_data_fobj = get_userdata_fobj(args, ssh_host_keys=ssh_host_keys) + user_data_fobj = apply_default_fobj( + args, 'user_data', create_default_user_data + ) meta_data_fobj = apply_default_fobj( args, 'meta_data', create_default_meta_data ) @@ -604,13 +539,8 @@ memory=args.memory, template_path=args.template, unsafe_caching=args.unsafe_caching, - ssh_known_hosts=ssh_known_hosts, ) - if args.wait: - args.name = args.hostname - main_wait(parser, args) - def main_destroy(parser, args): for h in args.hostname: @@ -639,6 +569,11 @@ def main_ssh(parser, args, default_login_name='ubuntu'): + if not args.insecure: + raise CLIError( + "ssh access with host key verification is not implemented. " + + "Use --insecure iff you trust your network path to the guest." + ) if args.login_name: login_name = args.login_name name = args.name @@ -648,39 +583,33 @@ login_name = default_login_name name = args.name - try: - return ssh( - name, login_name, args.ssh_arguments, insecure=args.insecure) - except InsecureError: - raise CLIError( - "ssh public host key not found. " + - "Use --insecure iff you trust your network path to the guest." - ) + return ssh(name, login_name, args.ssh_arguments) def main_wait_remote(parser, args): + if not args.insecure: + print( + "Warning: secure wait for boot-finished not yet implemented; " + "use --insecure.", + file=sys.stderr + ) + return + with open(args.remote_wait_script, 'rb') as wait_script: - try: - ssh( - args.name, - args.remote_wait_user, - [ - 'env', - 'UVTOOL_WAIT_INTERVAL=%s' % args.interval, - 'UVTOOL_WAIT_TIMEOUT=%s' % args.timeout, - 'sh', - '-' - ], - checked=True, - stdin=wait_script, - private_key_file=args.ssh_private_key_file, - insecure=args.insecure, - ) - except InsecureError: - raise CLIError( - "ssh public host key not found. Use " - "--insecure iff you trust your network path to the guest." - ) + ssh( + args.name, + args.remote_wait_user, + [ + 'env', + 'UVTOOL_WAIT_INTERVAL=%s' % args.interval, + 'UVTOOL_WAIT_TIMEOUT=%s' % args.timeout, + 'sh', + '-' + ], + checked=True, + stdin=wait_script, + private_key_file=args.ssh_private_key_file, + ) def main_wait(parser, args): @@ -727,17 +656,6 @@ parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() - - wait_args = [ - (('--timeout',), {'type': float, 'default': 120.0}), - (('--interval',), {'type': float, 'default': 1.0}), - (('--remote-wait-script',), {'default': DEFAULT_REMOTE_WAIT_SCRIPT}), - (('--insecure',), {'action': 'store_true'}), - (('--remote-wait-user',), {'default': 'ubuntu'}), - (('--without-ssh',), {'action': 'store_true'}), - (('--ssh-private-key-file',), {}), - ] - create_subparser = subparsers.add_parser('create') create_subparser.set_defaults(func=main_create) create_subparser.add_argument( @@ -745,30 +663,19 @@ create_subparser.add_argument('--template', default=DEFAULT_TEMPLATE) create_subparser.add_argument('--memory', default=512, type=int) create_subparser.add_argument('--cpu', default=1, type=int) - # TODO: reset this to 8GB after snappy images are smaller - # also TODO, fail sanely if you try to create smaller than the - # source volume. - create_subparser.add_argument('--disk', default=10, type=int, - help='size of disk in GB') + create_subparser.add_argument('--disk', default=8, type=int) create_subparser.add_argument('--bridge') create_subparser.add_argument('--unsafe-caching', action='store_true') create_subparser.add_argument( '--user-data', type=argparse.FileType('rb')) create_subparser.add_argument( - '--add-user-data', type=argparse.FileType('rb'), action='append', - default=[], help='add additional user-data part') - create_subparser.add_argument( '--meta-data', type=argparse.FileType('rb')) - for w_args, w_kwargs in wait_args: - create_subparser.add_argument(*w_args, **w_kwargs) - create_subparser.add_argument('--password') create_subparser.add_argument('--log-console-output', action='store_true') create_subparser.add_argument('--backing-image-file') create_subparser.add_argument('--run-script-once', action='append') create_subparser.add_argument('--ssh-public-key-file') create_subparser.add_argument('--packages', action='append') - create_subparser.add_argument('--wait', default=False, action='store_true') create_subparser.add_argument('hostname') create_subparser.add_argument( 'filters', nargs='*', metavar='filter', @@ -788,13 +695,17 @@ ssh_subparser.add_argument('--login-name', '-l') ssh_subparser.add_argument('name') ssh_subparser.add_argument('ssh_arguments', nargs='*') - wait_subparser = subparsers.add_parser('wait') wait_subparser.set_defaults(func=main_wait) - for w_args, w_kwargs in wait_args: - wait_subparser.add_argument(*w_args, **w_kwargs) + wait_subparser.add_argument('--timeout', type=float, default=120.0) + wait_subparser.add_argument('--interval', type=float, default=8.0) + wait_subparser.add_argument('--remote-wait-script', + default=DEFAULT_REMOTE_WAIT_SCRIPT) + wait_subparser.add_argument('--insecure', action='store_true') + wait_subparser.add_argument('--remote-wait-user', default='ubuntu') + wait_subparser.add_argument('--without-ssh', action='store_true') + wait_subparser.add_argument('--ssh-private-key-file') wait_subparser.add_argument('name') - args = parser.parse_args(args) args.func(parser, args) diff -Nru uvtool-0~bzr92+0~snap141205.1/uvtool/libvirt/simplestreams.py uvtool-0~bzr98/uvtool/libvirt/simplestreams.py --- uvtool-0~bzr92+0~snap141205.1/uvtool/libvirt/simplestreams.py 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/uvtool/libvirt/simplestreams.py 2015-04-01 17:46:51.000000000 +0000 @@ -47,7 +47,6 @@ IMAGE_DIR = '/var/lib/uvtool/libvirt/images/' # must end in '/'; see use METADATA_DIR = '/var/lib/uvtool/libvirt/metadata' USEFUL_FIELD_NAMES = ['release', 'arch', 'label'] -SNAPPY_STREAM_URL = 'http://cloud-images.ubuntu.com/snappy/' def mkdir_p(path): @@ -250,12 +249,6 @@ def main_sync(args): - # add arch filter if there is none. - for swith in ('arch=', 'arch~', 'arch!'): - if any([f.startswith(swith) for f in args.filters]): - args.filters.append(system_arch_filter()) - break - (mirror_url, initial_path) = simplestreams.util.path_from_mirror_url( args.mirror_url, args.path) @@ -277,11 +270,6 @@ clean_extraneous_images() -def system_arch_filter(): - return 'arch=%s' % subprocess.check_output( - ['dpkg', '--print-architecture']).decode().strip() - - def libvirt_pool_name_to_useful_description_string(libvirt_pool_name): volume_metadata = pool_metadata[libvirt_pool_name] filters = ' '.join('='.join((key, volume_metadata[key])) for key in USEFUL_FIELD_NAMES) @@ -320,14 +308,11 @@ help='keyring to be specified to gpg via --keyring', default='/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg' ) - sync_subparser.add_argument('--snappy', dest='mirror_url', - action='store_const', const=SNAPPY_STREAM_URL, - help='set --source for snappy images') sync_subparser.add_argument('--source', dest='mirror_url', default='https://cloud-images.ubuntu.com/releases/') sync_subparser.add_argument('--no-authentication', action='store_true') sync_subparser.add_argument('filters', nargs='*', metavar='filter', - default=[system_arch_filter()]) + default=["arch=%s" % system_arch]) query_subparser = subparsers.add_parser('query') query_subparser.set_defaults(func=main_query) diff -Nru uvtool-0~bzr92+0~snap141205.1/uvtool/ssh.py uvtool-0~bzr98/uvtool/ssh.py --- uvtool-0~bzr92+0~snap141205.1/uvtool/ssh.py 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/uvtool/ssh.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,69 +0,0 @@ -#!/usr/bin/python - -# Copyright (C) 2014 Canonical Ltd. -# Author: Robie Basak -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -KEY_TYPES = ['rsa', 'dsa', 'ecdsa', 'ed25519'] - -import os -import shutil -import subprocess -import tempfile - - -def _keygen(key_type, private_path): - subprocess.check_call([ - 'ssh-keygen', - '-q', - '-f', private_path, - '-N', '', - '-t', key_type, - '-C', 'root@localhost' - ]) - - -def read_file(path): - with open(path, 'rb') as f: - return f.read() - - -def generate_ssh_host_keys(): - cloud_init_result = {} - known_hosts_result = [] - tmp_dir = tempfile.mkdtemp(prefix='uvt-kvm.sshtmp') - try: - for key_type in KEY_TYPES: - private_path = os.path.join(tmp_dir, key_type) - _keygen(key_type, private_path) - - # ssh-keygen(1) defines that ".pub" is appended - public_path = private_path + ".pub" - - key_type_utf8 = key_type.encode('utf-8') - private_ci_key = key_type_utf8 + b'_private' - public_ci_key = key_type_utf8 + b'_public' - - private_key = read_file(private_path) - public_key = read_file(public_path) - - cloud_init_result[private_ci_key] = private_key - cloud_init_result[public_ci_key] = public_key - - known_hosts_result.append(public_key) - finally: - shutil.rmtree(tmp_dir) - - return cloud_init_result, b''.join(known_hosts_result) diff -Nru uvtool-0~bzr92+0~snap141205.1/uvtool/tests/test_kvm.py uvtool-0~bzr98/uvtool/tests/test_kvm.py --- uvtool-0~bzr92+0~snap141205.1/uvtool/tests/test_kvm.py 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/uvtool/tests/test_kvm.py 2015-04-01 17:46:51.000000000 +0000 @@ -30,14 +30,12 @@ args.login_name = args_login_name args.name = args_hostname args.ssh_arguments = mock.sentinel.ssh_arguments - args.insecure = True with mock.patch('uvtool.libvirt.kvm.ssh') as ssh_mock: main_ssh(parser, args) ssh_mock.assert_called_with( expected_hostname, expected_login_name, - mock.sentinel.ssh_arguments, - insecure=True, + mock.sentinel.ssh_arguments ) def test_ssh_default(self): diff -Nru uvtool-0~bzr92+0~snap141205.1/uvtool/wait.py uvtool-0~bzr98/uvtool/wait.py --- uvtool-0~bzr92+0~snap141205.1/uvtool/wait.py 2014-12-06 03:36:37.000000000 +0000 +++ uvtool-0~bzr98/uvtool/wait.py 2015-04-01 17:46:51.000000000 +0000 @@ -20,6 +20,7 @@ import argparse import contextlib import functools +import os import socket import sys import time @@ -27,6 +28,10 @@ import pyinotify import uvtool.libvirt +from uvtool.libvirt import ( + LIBVIRT_DNSMASQ_LEASE_FILE, + LIBVIRT_DNSMASQ_STATUS_FILE +) SSH_PORT = 22 @@ -37,8 +42,9 @@ self.notifier = pyinotify.Notifier(self.wm, pyinotify.ProcessEvent()) def start_watching(self): - self.wdd = self.wm.add_watch( - uvtool.libvirt.LIBVIRT_DNSMASQ_LEASE_FILE, pyinotify.IN_MODIFY) + for f in [LIBVIRT_DNSMASQ_LEASE_FILE, LIBVIRT_DNSMASQ_STATUS_FILE]: + if os.path.exists(f): + self.wm.add_watch(f, pyinotify.IN_MODIFY) def wait(self, timeout): if self.notifier.check_events(timeout=(timeout*1000)):