Explorar o código

Add qvm-clone(1)

Bahtiar `kalkin-` Gadimov %!s(int64=8) %!d(string=hai) anos
pai
achega
bcf1cfcb1f
Modificáronse 4 ficheiros con 147 adicións e 10 borrados
  1. 16 7
      doc/manpages/qvm-clone.rst
  2. 73 0
      qubes/tools/qvm_clone.py
  3. 57 3
      qubes/vm/qubesvm.py
  4. 1 0
      rpm_spec/core-dom0.spec

+ 16 - 7
doc/manpages/qvm-clone.rst

@@ -1,30 +1,39 @@
 .. program:: qvm-clone
 
-===========================================================================
 :program:`qvm-clone` -- Clones an existing VM by copying all its disk files
 ===========================================================================
 
 Synopsis
-========
-:command:`qvm-clone` [*options*] <*src-name*> <*new-name*>
+--------
+:command:`qvm-clone` [-h] [--verbose] [--quiet] [-p *POOL:VOLUME* | -P POOL] *VMNAME* *NEWVM*
 
 Options
-=======
+-------
 
 .. option:: --help, -h
 
     Show this help message and exit
 
+.. option:: -P POOL
+
+    Pool to use for the new domain. All volumes besides snapshots volumes are
+    imported in to the specified POOL. ~HIS IS WHAT YOU WANT TO USE NORMALLY.
+
+.. option:: --pool=POOL:VOLUME, -p POOL:VOLUME
+
+    Specify the pool to use for the specific volume
+
 .. option:: --quiet, -q
 
     Be quiet
 
-.. option:: --path=DIR_PATH, -p DIR_PATH
+.. option:: --verbose, -v
 
-    Specify path to the template directory
+    Increase verbosity
 
 Authors
-=======
+-------
 | Joanna Rutkowska <joanna at invisiblethingslab dot com>
 | Rafal Wojtczuk <rafal at invisiblethingslab dot com>
 | Marek Marczykowski <marmarek at invisiblethingslab dot com>
+| Bahtiar `kalkin-` Gadimov <bahtiar at gadimov dot de> 

+ 73 - 0
qubes/tools/qvm_clone.py

@@ -0,0 +1,73 @@
+#!/usr/bin/python2
+# -*- encoding: utf8 -*-
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
+#
+# 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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+''' Clone a domain '''
+
+import sys
+
+from qubes.tools import QubesArgumentParser, SinglePropertyAction
+
+parser = QubesArgumentParser(description=__doc__, vmname_nargs=1)
+parser.add_argument('new_name',
+                    metavar='NEWVM',
+                    action=SinglePropertyAction,
+                    help='name of the domain to create')
+
+group = parser.add_mutually_exclusive_group()
+group.add_argument('-P',
+                    metavar='POOL',
+                    dest='one_pool',
+                    default='',
+                    help='pool to use for the new domain')
+
+group.add_argument('-p',
+                    '--pool',
+                    action='append',
+                    metavar='POOL:VOLUME',
+                    help='specify the pool to use for the specific volume')
+
+
+def main(args=None):
+    ''' Clones an existing VM by copying all its disk files '''
+    args = parser.parse_args(args)
+    app = args.app
+    src_vm = args.domains[0]
+    new_name = args.properties['new_name']
+    dst_vm = app.add_new_vm(src_vm.__class__, name=new_name)
+    dst_vm.clone_properties(src_vm)
+
+    if args.one_pool:
+        dst_vm.clone_disk_files(src_vm, pool=args.one_pool)
+    elif hasattr(args, 'pools') and args.pools:
+        dst_vm.clone_disk_files(src_vm, pools=args.pools)
+    else:
+        dst_vm.clone_disk_files(src_vm)
+
+#   try:
+    app.save()  # HACK remove_from_disk on exception hangs for some reason
+#   except Exception as e:  # pylint: disable=broad-except
+#       dst_vm.remove_from_disk()
+#       parser.print_error(e)
+#   return 0
+
+if __name__ == '__main__':
+    sys.exit(main())

+ 57 - 3
qubes/vm/qubesvm.py

@@ -25,6 +25,7 @@
 
 from __future__ import absolute_import
 
+import copy
 import base64
 import datetime
 import itertools
@@ -1074,6 +1075,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
         os.makedirs(self.dir_path, mode=0o775)
 
         if pool or pools:
+            # pylint: disable=attribute-defined-outside-init
             self.volume_config = _patch_volume_config(self.volume_config, pool,
                                                       pools)
             self.storage = qubes.storage.Storage(self)
@@ -1096,7 +1098,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
         shutil.rmtree(self.dir_path)
         self.storage.remove()
 
-    def clone_disk_files(self, src):
+    def clone_disk_files(self, src, pool=None, pools=None, ):
         '''Clone files from other vm.
 
         :param qubes.vm.qubesvm.QubesVM src: source VM
@@ -1110,9 +1112,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
             raise qubes.exc.QubesVMNotHaltedError(
                 self, 'Cannot clone a running domain {!r}'.format(self.name))
 
-        if hasattr(src, 'volume_config'):
+        if pool or pools:
             # pylint: disable=attribute-defined-outside-init
-            self.volume_config = src.volume_config
+            self.volume_config = _patch_volume_config(self.volume_config, pool,
+                                                      pools)
+
         self.storage = qubes.storage.Storage(self)
         self.storage.clone(src)
         self.storage.verify()
@@ -1589,3 +1593,53 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
         domain.memory_maximum = self.get_mem_static_max() * 1024
 
         return qubes.qmemman.algo.prefmem(domain) / 1024
+
+
+def _clean_volume_config(config):
+    common_attributes = ['name', 'pool', 'size', 'internal', 'removable',
+                            'revisions_to_keep', 'rw', 'snap_on_start',
+                            'save_on_stop', 'source']
+    config_copy = copy.deepcopy(config)
+    return {k: v for k, v in config_copy.items() if k in common_attributes}
+
+
+def _patch_pool_config(config, pool=None, pools=None):
+    assert pool is not None or pools is not None
+    is_saveable = 'save_on_stop' in config and config['save_on_stop']
+    is_resetable = not ('snap_on_start' in config and  # volatile
+                        config['snap_on_start'] and not is_saveable)
+
+    is_exportable = is_saveable or is_resetable
+
+    name = config['name']
+
+    if pool and is_exportable:
+        config['pool'] = str(pool)
+    elif pool and not is_exportable:
+        pass
+    elif pools and name in pools.keys():
+        if is_exportable:
+            config['pool'] = str(pools[name])
+        else:
+            msg = "Can't clone a snapshot volume {!s} to pool {!s} " \
+                .format(name, pools[name])
+            raise qubes.exc.QubesException(msg)
+    return config
+
+def _patch_volume_config(volume_config, pool=None, pools=None):
+    assert not (pool and pools), \
+        'You can not pass pool & pools parameter at same time'
+    assert pool or pools
+
+    result = {}
+
+    for name, config in volume_config.items():
+        # copy only the subset of volume_config key/values
+        dst_config = _clean_volume_config(config)
+
+        if pool is not None or pools is not None:
+            dst_config = _patch_pool_config(dst_config, pool, pools)
+
+        result[name] = dst_config
+
+    return result

+ 1 - 0
rpm_spec/core-dom0.spec

@@ -247,6 +247,7 @@ fi
 %{python_sitelib}/qubes/tools/qvm_block.py*
 %{python_sitelib}/qubes/tools/qvm_create.py*
 %{python_sitelib}/qubes/tools/qvm_features.py*
+%{python_sitelib}/qubes/tools/qvm_clone.py*
 %{python_sitelib}/qubes/tools/qvm_kill.py*
 %{python_sitelib}/qubes/tools/qvm_ls.py*
 %{python_sitelib}/qubes/tools/qvm_pause.py*