qvm-check: refactor check mechanism and add filter for checking netvm

Fix QubesOS/qubes-issues#3496
This commit is contained in:
Frédéric Pierret (fepitre) 2019-07-31 10:37:15 +02:00
parent 7bca004532
commit 7d93377b78
No known key found for this signature in database
GPG Key ID: 484010B5CDC576E2
3 changed files with 166 additions and 106 deletions

View File

@ -15,7 +15,7 @@
Synopsis Synopsis
-------- --------
:command:`qvm-check` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--running] [--paused] [--template] [*VMNAME* [*VMNAME* ...]] :command:`qvm-check` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--running] [--paused] [--template] [--networked] [*VMNAME* [*VMNAME* ...]]
Options Options
------- -------
@ -52,6 +52,10 @@ Options
Determine if (any of given) VM is a template Determine if (any of given) VM is a template
.. option:: --networked
Determine if (any of given) VM can reach network
Authors Authors
------- -------
@ -59,5 +63,6 @@ Authors
| Rafal Wojtczuk <rafal at invisiblethingslab dot com> | Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com> | Marek Marczykowski <marmarek at invisiblethingslab dot com>
| Wojtek Porczyk <woju at invisiblethingslab dot com> | Wojtek Porczyk <woju at invisiblethingslab dot com>
| Frédéric Pierret <frederic dot pierret at qubes dash os dot com>
.. vim: ts=3 sw=3 et tw=80 .. vim: ts=3 sw=3 et tw=80

View File

@ -21,14 +21,14 @@
import qubesadmin.tests import qubesadmin.tests
import qubesadmin.tools.qvm_check import qubesadmin.tools.qvm_check
class TC_00_qvm_check(qubesadmin.tests.QubesTestCase): class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
def test_000_exists(self): def test_000_exists(self):
self.app.expected_calls[ self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \ ('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' b'0\x00some-vm class=AppVM state=Running\n'
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['some-vm'], app=self.app), qubesadmin.tools.qvm_check.main(['some-vm'], app=self.app), 0)
0)
self.assertAllCalled() self.assertAllCalled()
def test_001_exists_multi(self): def test_001_exists_multi(self):
@ -38,20 +38,17 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
b'other-vm class=AppVM state=Running\n' b'other-vm class=AppVM state=Running\n'
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['some-vm', 'other-vm'], qubesadmin.tools.qvm_check.main(['some-vm', 'other-vm'],
app=self.app), app=self.app), 0)
0)
self.assertAllCalled() self.assertAllCalled()
def test_002_exists_verbose(self): def test_002_exists_verbose(self):
self.app.expected_calls[ self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \ ('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' b'0\x00some-vm class=AppVM state=Running\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['some-vm'], app=self.app), qubesadmin.tools.qvm_check.main(['some-vm'], app=self.app), 0)
0) self.assertEqual(logger.output, ['INFO:qvm-check:some-vm: exists'])
self.assertEqual(stdout.getvalue(),
'VM some-vm exists\n')
self.assertAllCalled() self.assertAllCalled()
def test_003_exists_multi_verbose(self): def test_003_exists_multi_verbose(self):
@ -59,13 +56,12 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
('dom0', 'admin.vm.List', None, None)] = \ ('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' \ b'0\x00some-vm class=AppVM state=Running\n' \
b'other-vm class=AppVM state=Running\n' b'other-vm class=AppVM state=Running\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['some-vm', 'other-vm'], qubesadmin.tools.qvm_check.main(['some-vm', 'other-vm'],
app=self.app), app=self.app), 0)
0) self.assertEqual(logger.output, ['INFO:qvm-check:other-vm: exists',
self.assertEqual(stdout.getvalue(), 'INFO:qvm-check:some-vm: exists'])
'VMs other-vm, some-vm exist\n')
self.assertAllCalled() self.assertAllCalled()
def test_004_running_verbose(self): def test_004_running_verbose(self):
@ -77,13 +73,11 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
self.app.expected_calls[ self.app.expected_calls[
('some-vm', 'admin.vm.List', None, None)] = \ ('some-vm', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' b'0\x00some-vm class=AppVM state=Running\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['--running', qubesadmin.tools.qvm_check.main(['--running', 'some-vm'],
'some-vm'], app=self.app), app=self.app), 0)
0) self.assertEqual(logger.output, ['INFO:qvm-check:some-vm: running'])
self.assertEqual(stdout.getvalue(),
'VM some-vm is running\n')
self.assertAllCalled() self.assertAllCalled()
def test_005_running_multi_verbose(self): def test_005_running_multi_verbose(self):
@ -98,14 +92,12 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
self.app.expected_calls[ self.app.expected_calls[
('some-vm2', 'admin.vm.List', None, None)] = \ ('some-vm2', 'admin.vm.List', None, None)] = \
b'0\x00some-vm2 class=AppVM state=Running\n' b'0\x00some-vm2 class=AppVM state=Running\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(qubesadmin.tools.qvm_check.main(
qubesadmin.tools.qvm_check.main(['--running', ['--running', 'some-vm', 'some-vm2'], app=self.app), 0)
'some-vm', 'some-vm2'], self.assertEqual(logger.output, ['INFO:qvm-check:some-vm: running',
app=self.app), 'INFO:qvm-check:some-vm2: running']
0) )
self.assertEqual(stdout.getvalue(),
'VMs some-vm, some-vm2 are running\n')
self.assertAllCalled() self.assertAllCalled()
def test_006_running_multi_verbose2(self): def test_006_running_multi_verbose2(self):
@ -123,14 +115,13 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
self.app.expected_calls[ self.app.expected_calls[
('some-vm3', 'admin.vm.List', None, None)] = \ ('some-vm3', 'admin.vm.List', None, None)] = \
b'0\x00some-vm3 class=AppVM state=Halted\n' b'0\x00some-vm3 class=AppVM state=Halted\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['--running', qubesadmin.tools.qvm_check.main(['--running', '--all'],
'--all'], app=self.app), 3)
app=self.app), self.assertEqual(logger.output, ['INFO:qvm-check:some-vm: running',
0) 'INFO:qvm-check:some-vm2: running']
self.assertEqual(stdout.getvalue(), )
'VMs some-vm, some-vm2 are running\n')
self.assertAllCalled() self.assertAllCalled()
def test_007_not_running_verbose(self): def test_007_not_running_verbose(self):
@ -142,14 +133,12 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
self.app.expected_calls[ self.app.expected_calls[
('some-vm3', 'admin.vm.List', None, None)] = \ ('some-vm3', 'admin.vm.List', None, None)] = \
b'0\x00some-vm3 class=AppVM state=Halted\n' b'0\x00some-vm3 class=AppVM state=Halted\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['--running', qubesadmin.tools.qvm_check.main(['--running', 'some-vm3'],
'some-vm3'], app=self.app), 1)
app=self.app), self.assertEqual(logger.output,
1) ['INFO:qvm-check:None of qubes: running'])
self.assertEqual(stdout.getvalue(),
'None of given VM is running\n')
self.assertAllCalled() self.assertAllCalled()
def test_008_paused(self): def test_008_paused(self):
@ -161,14 +150,11 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
self.app.expected_calls[ self.app.expected_calls[
('some-vm2', 'admin.vm.List', None, None)] = \ ('some-vm2', 'admin.vm.List', None, None)] = \
b'0\x00some-vm2 class=AppVM state=Paused\n' b'0\x00some-vm2 class=AppVM state=Paused\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['--paused', qubesadmin.tools.qvm_check.main(['--paused', 'some-vm2'],
'some-vm2'], app=self.app), 0)
app=self.app), self.assertEqual(logger.output, ['INFO:qvm-check:some-vm2: paused'])
0)
self.assertEqual(stdout.getvalue(),
'VM some-vm2 is paused\n')
self.assertAllCalled() self.assertAllCalled()
def test_009_paused_multi(self): def test_009_paused_multi(self):
@ -183,31 +169,24 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
self.app.expected_calls[ self.app.expected_calls[
('some-vm', 'admin.vm.List', None, None)] = \ ('some-vm', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' b'0\x00some-vm class=AppVM state=Running\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(qubesadmin.tools.qvm_check.main(
qubesadmin.tools.qvm_check.main(['--paused', ['--paused', 'some-vm2', 'some-vm'], app=self.app), 3)
'some-vm2', 'some-vm'], self.assertEqual(logger.output, ['INFO:qvm-check:some-vm2: paused'])
app=self.app),
0)
self.assertEqual(stdout.getvalue(),
'VM some-vm2 is paused\n')
self.assertAllCalled() self.assertAllCalled()
def test_010_template(self): def test_010_template(self):
self.app.expected_calls[ self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \ ('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' \ b'0\x00some-vm class=AppVM state=Running\n' \
b'some-vm2 class=AppVM state=Paused\n' \ b'some-vm2 class=AppVM state=Paused\n' \
b'some-vm3 class=TemplateVM state=Halted\n' b'some-vm3 class=TemplateVM state=Halted\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(
qubesadmin.tools.qvm_check.main(['--template', qubesadmin.tools.qvm_check.main(['--template', 'some-vm3'],
'some-vm3'], app=self.app), 0)
app=self.app), self.assertEqual(logger.output,
0) ['INFO:qvm-check:some-vm3: template'])
self.assertEqual(stdout.getvalue(),
'VM some-vm3 is a template\n')
self.assertAllCalled() self.assertAllCalled()
def test_011_template_multi(self): def test_011_template_multi(self):
@ -216,13 +195,53 @@ class TC_00_qvm_check(qubesadmin.tests.QubesTestCase):
b'0\x00some-vm class=AppVM state=Running\n' \ b'0\x00some-vm class=AppVM state=Running\n' \
b'some-vm2 class=AppVM state=Paused\n' \ b'some-vm2 class=AppVM state=Paused\n' \
b'some-vm3 class=TemplateVM state=Halted\n' b'some-vm3 class=TemplateVM state=Halted\n'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with self.assertLogs() as logger:
self.assertEqual( self.assertEqual(qubesadmin.tools.qvm_check.main(
qubesadmin.tools.qvm_check.main(['--template', ['--template', 'some-vm2', 'some-vm3'], app=self.app), 3)
'some-vm2', 'some-vm3'], self.assertEqual(logger.output,
app=self.app), ['INFO:qvm-check:some-vm3: template'])
0)
self.assertEqual(stdout.getvalue(),
'VM some-vm3 is a template\n')
self.assertAllCalled() self.assertAllCalled()
def test_012_networked(self):
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' \
b'some-vm2 class=AppVM state=Running\n'
self.app.expected_calls[
('some-vm2', 'admin.vm.property.Get', 'provides_network', None)] = \
b'0\x00default=false type=bool false'
self.app.expected_calls[
('some-vm2', 'admin.vm.property.Get', 'netvm', None)] = \
b'0\x00default=false type=vm some-vm'
with self.assertLogs() as logger:
self.assertEqual(
qubesadmin.tools.qvm_check.main(['--networked', 'some-vm2'],
app=self.app), 0)
self.assertEqual(logger.output,
['INFO:qvm-check:some-vm2: networked'])
self.assertAllCalled()
def test_013_networked_multi(self):
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=AppVM state=Running\n' \
b'some-vm2 class=AppVM state=Running\n' \
b'some-vm3 class=TemplateVM state=Halted\n'
self.app.expected_calls[
('some-vm2', 'admin.vm.property.Get', 'provides_network', None)] = \
b'0\x00default=false type=bool false'
self.app.expected_calls[
('some-vm3', 'admin.vm.property.Get', 'provides_network', None)] = \
b'0\x00default=false type=bool false'
self.app.expected_calls[
('some-vm2', 'admin.vm.property.Get', 'netvm', None)] = \
b'0\x00default=false type=vm some-vm'
self.app.expected_calls[
('some-vm3', 'admin.vm.property.Get', 'netvm', None)] = \
b"0\x00default=false type=vm "
with self.assertLogs() as logger:
self.assertEqual(qubesadmin.tools.qvm_check.main(
['--networked', 'some-vm2', 'some-vm3'], app=self.app), 3)
self.assertEqual(logger.output,
['INFO:qvm-check:some-vm2: networked'])
self.assertAllCalled()

View File

@ -4,6 +4,7 @@
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
# #
# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> # Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
# Copyright (C) 2019 Frédéric Pierret <frederic.pierret@qubes-os.org>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by # it under the terms of the GNU Lesser General Public License as published by
@ -19,9 +20,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
''' Exits sucessfull if the provided domains exists, else returns failure ''' """ Exits sucessfull if the provided domains exists, else returns failure """
from __future__ import print_function
import sys import sys
@ -31,46 +30,83 @@ import qubesadmin.vm
parser = qubesadmin.tools.QubesArgumentParser(description=__doc__, parser = qubesadmin.tools.QubesArgumentParser(description=__doc__,
vmname_nargs='+') vmname_nargs='+')
parser.add_argument("--running", action="store_true", dest="running", parser.add_argument("--running", action="store_true", dest="running",
default=False, help="Determine if (any of given) VM is running") default=False,
help="Determine if (any of given) VM is running")
parser.add_argument("--paused", action="store_true", dest="paused", parser.add_argument("--paused", action="store_true", dest="paused",
default=False, help="Determine if (any of given) VM is paused") default=False,
help="Determine if (any of given) VM is paused")
parser.add_argument("--template", action="store_true", dest="template", parser.add_argument("--template", action="store_true", dest="template",
default=False, help="Determine if (any of given) VM is a template") default=False,
help="Determine if (any of given) VM is a template")
parser.add_argument("--networked", action="store_true", dest="networked",
default=False,
help="Determine if (any of given) VM can reach network")
def print_msg(domains, what_single, what_plural): def print_msg(log, domains, status):
'''Print message in appropriate form about given domain(s)''' """Print message in appropriate form about given domain(s)"""
if not domains: if not domains:
print("None of given VM {!s}".format(what_single)) log.info("None of qubes: {!s}".format(', '.join(status)))
elif len(domains) == 1:
print("VM {!s} {!s}".format(domains[0], what_single))
else: else:
txt = ", ".join([vm.name for vm in sorted(domains)]) for vm in sorted(domains):
print("VMs {!s} {!s}".format(txt, what_plural)) log.info("{!s}: {!s}".format(vm.name, ', '.join(status)))
def get_filters(args):
"""Get status and check functions"""
filters = []
if args.running:
filters.append({'status': 'running', 'check': lambda x: x.is_running()})
if args.paused:
filters.append({'status': 'paused', 'check': lambda x: x.is_paused()})
if args.template:
filters.append(
{'status': 'template', 'check': lambda x: x.klass == 'TemplateVM'})
if args.networked:
filters.append(
{'status': 'networked', 'check': lambda x: x.is_networked()})
return filters
def main(args=None, app=None): def main(args=None, app=None):
'''Main function of qvm-check tool''' """Main function of qvm-check tool"""
args = parser.parse_args(args, app=app) args = parser.parse_args(args, app=app)
domains = args.domains domains = args.domains
if args.running: return_code = 0
running = [vm for vm in domains if vm.is_running()]
log = args.app.log
log.name = "qvm-check"
status = []
filters = get_filters(args)
filtered_domains = set(domains)
if filters:
for filt in filters:
status.append(filt['status'])
check = filt['check']
filtered_domains = filtered_domains.intersection(
[vm for vm in domains if check(vm)])
filtered_domains = list(filtered_domains)
if set(domains) & set(filtered_domains) != set(domains):
if not filtered_domains:
return_code = 1
else:
return_code = 3
if args.verbose: if args.verbose:
print_msg(running, "is running", "are running") print_msg(log, filtered_domains, status)
return 0 if running else 1 else:
if args.paused: if not domains:
paused = [vm for vm in domains if vm.is_paused()] return_code = 1
if args.verbose: elif args.verbose:
print_msg(paused, "is paused", "are paused") print_msg(log, domains, ["exists"])
return 0 if paused else 1
if args.template: return return_code
template = [vm for vm in domains if vm.klass == 'TemplateVM']
if args.verbose:
print_msg(template, "is a template", "are templates")
return 0 if template else 1
if args.verbose:
print_msg(domains, "exists", "exist")
return 0 if domains else 1
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())