From 759fafea6344f4909e7f7562bce638445048753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Thu, 18 Oct 2018 02:52:10 +0200 Subject: [PATCH] tools/qvm-create: properly create template-based StandaloneVM By definition StandaloneVM is not linked to the template. Creating one from a template is a clone operation. It's already possible using qvm-clone tool, but it's logical to do that using qvm-create tool too. This was the case in R3.2 too. While adding this special case, skip cloning private volume, to preserve behaviour of TemplateBaseVMs which do not inherit private volume either. Fixes QubesOS/qubes-issues#3793 --- qubesadmin/tests/tools/qvm_create.py | 102 +++++++++++++++++++++++++++ qubesadmin/tools/qvm_create.py | 22 ++++-- 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/qubesadmin/tests/tools/qvm_create.py b/qubesadmin/tests/tools/qvm_create.py index f142f52..7200c67 100644 --- a/qubesadmin/tests/tools/qvm_create.py +++ b/qubesadmin/tests/tools/qvm_create.py @@ -210,3 +210,105 @@ class TC_00_qvm_create(qubesadmin.tests.QubesTestCase): self.assertAllCalled() self.assertTrue(os.path.exists(root_file.name)) + def test_011_standalonevm(self): + self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \ + b'0\x00red\nblue\n' + self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \ + b'0\x00template class=TemplateVM state=Halted\n' \ + b'new-vm class=StandaloneVM state=Halted\n' + self.app.expected_calls[ + ('template', 'admin.vm.property.Get', 'label', None)] = \ + b'0\x00default=False type=label blue' + self.app.expected_calls[ + ('template', 'admin.vm.property.Get', 'vcpus', None)] = \ + b'0\x00default=False type=int 2' + self.app.expected_calls[ + ('template', 'admin.vm.property.Get', 'kernel', None)] = \ + b'0\x00default=True type=str kernel-version' + self.app.expected_calls[ + ('template', 'admin.vm.property.Get', 'memory', None)] = \ + b'0\x00default=True type=int 400' + self.app.expected_calls[ + ('template', 'admin.vm.property.Get', 'template', None)] = \ + b'2\x00QubesNoSuchPropertyError\x00\x00No such property\x00' + self.app.expected_calls[ + ('template', 'admin.vm.property.List', None, None)] = \ + b'0\x00name\n' \ + b'label\n' \ + b'vcpus\n' \ + b'kernel\n' \ + b'memory\n' + self.app.expected_calls[ + ('template', 'admin.vm.tag.List', None, None)] = \ + b'0\x00' + self.app.expected_calls[ + ('template', 'admin.vm.feature.List', None, None)] = \ + b'0\x00' + self.app.expected_calls[ + ('template', 'admin.vm.firewall.Get', None, None)] = \ + b'0\x00' + self.app.expected_calls[('dom0', 'admin.vm.Create.StandaloneVM', None, + b'name=new-vm label=blue')] = b'0\x00' + # TODO this is weird... + self.app.expected_calls[ + ('new-vm', 'admin.vm.property.Set', 'label', b'red')] = \ + b'0\x00' + self.app.expected_calls[ + ('new-vm', 'admin.vm.property.Set', 'vcpus', b'2')] = \ + b'0\x00' + self.app.expected_calls[ + ('new-vm', 'admin.vm.firewall.Set', None, b'')] = \ + b'0\x00' + self.app.expected_calls[ + ('template', 'admin.vm.volume.List', None, None)] = \ + b'0\x00root\nprivate\nvolatile\nkernel\n' + self.app.expected_calls[ + ('new-vm', 'admin.vm.volume.List', None, None)] = \ + b'0\x00root\nprivate\nvolatile\nkernel\n' + self.app.expected_calls[ + ('new-vm', 'admin.vm.volume.Info', 'root', None)] = \ + b'0\x00' \ + b'snap_on_start=False\n' \ + b'save_on_stop=True\n' \ + b'pool=other-pool\n' \ + b'vid=new-vm-root\n' \ + b'rw=True\n' \ + b'size=2\n' + self.app.expected_calls[ + ('new-vm', 'admin.vm.volume.Info', 'private', None)] = \ + b'0\x00' \ + b'snap_on_start=False\n' \ + b'save_on_stop=True\n' \ + b'pool=other-pool\n' \ + b'vid=new-vm-private\n' \ + b'rw=True\n' \ + b'size=2\n' + self.app.expected_calls[ + ('new-vm', 'admin.vm.volume.Info', 'volatile', None)] = \ + b'0\x00' \ + b'snap_on_start=False\n' \ + b'save_on_stop=False\n' \ + b'pool=other-pool\n' \ + b'vid=new-vm-volatile\n' \ + b'rw=True\n' \ + b'size=2\n' + self.app.expected_calls[ + ('new-vm', 'admin.vm.volume.Info', 'kernel', None)] = \ + b'0\x00' \ + b'snap_on_start=False\n' \ + b'save_on_stop=False\n' \ + b'pool=linux-kernel\n' \ + b'vid=kernel-version\n' \ + b'rw=False\n' \ + b'size=2\n' + self.app.expected_calls[ + ('template', 'admin.vm.volume.CloneFrom', 'root', None)] = \ + b'0\0clone-cookie' + self.app.expected_calls[ + ('new-vm', 'admin.vm.volume.CloneTo', 'root', b'clone-cookie')] = \ + b'0\0' + qubesadmin.tools.qvm_create.main(['-C', 'StandaloneVM', + '-t', 'template', '-l', 'red', 'new-vm'], + app=self.app) + self.assertAllCalled() + diff --git a/qubesadmin/tools/qvm_create.py b/qubesadmin/tools/qvm_create.py index c4a5c40..6aa2b71 100644 --- a/qubesadmin/tools/qvm_create.py +++ b/qubesadmin/tools/qvm_create.py @@ -137,12 +137,22 @@ def main(args=None, app=None): parser.error('no such domain class: {!r}'.format(args.cls)) try: - vm = args.app.add_new_vm(args.cls, - name=args.properties.pop('name'), - label=args.properties.pop('label'), - template=args.properties.pop('template', None), - pool=pool, - pools=pools) + if args.cls == 'StandaloneVM' and 'template' in args.properties: + # "template-based" StandaloneVM is special, as it's a clone of + # the template + vm = args.app.clone_vm(args.properties.pop('template'), + args.properties.pop('name'), + new_cls=args.cls, + pool=pool, + pools=pools, + ignore_volumes=('private',)) + else: + vm = args.app.add_new_vm(args.cls, + name=args.properties.pop('name'), + label=args.properties.pop('label'), + template=args.properties.pop('template', None), + pool=pool, + pools=pools) except qubesadmin.exc.QubesException as e: args.app.log.error('Error creating VM: {!s}'.format(e)) return 1