diff --git a/doc/manpages/qvm-pool.rst b/doc/manpages/qvm-pool.rst index 509332a..524bdfb 100644 --- a/doc/manpages/qvm-pool.rst +++ b/doc/manpages/qvm-pool.rst @@ -12,6 +12,17 @@ Synopsis | :command:`qvm-pool` {remove,rm,i} [*options*] <*pool_name*> ... | :command:`qvm-pool` {set,s} [*options*] <*pool_name*> +Legacy Mode +^^^^^^^^^^^ +| :command:`qvm-pool` [*options*] {-a, --add} <*pool_name*> <*driver*> +| :command:`qvm-pool` [*options*] {-i, --info} <*pool_name*> +| :command:`qvm-pool` [*options*] {-l, --list} +| :command:`qvm-pool` [*options*] {-r, --remove} <*pool_name*> +| :command:`qvm-pool` [*options*] {-s, --set} <*pool_name*> +| :command:`qvm-pool` [*options*] --help-drivers + +.. deprecated:: 4.0.18 + Options ------- @@ -39,11 +50,14 @@ Add a new pool. .. option:: --option, -o Set option for the driver in `name=value` format. You can specify this - option multiple times. For supported drivers and their options, - see ``drivers``. + option multiple times. + + .. seealso:: The `drivers` command for supported drivers and their options. aliases: a +Legacy mode: :command:`qvm-pool` [-h] [--verbose] [--quiet] --add *POOL_NAME* *DRIVER* -o *OPTIONS* + drivers ^^^^^^^ | :command:`qvm-pool drivers` [-h] [--verbose] [--quiet] @@ -53,6 +67,8 @@ The listed driver options can be used with the ``-o options`` switch. aliases: d +Legacy mode: :command:`qvm-pool` [-h] [--verbose] [--quiet] --help-drivers + info ^^^^ | :command:`qvm-pool info` [-h] [--verbose] [--quiet] *POOL_NAME* @@ -61,6 +77,8 @@ Print info about a specified pool aliases: i +Legacy mode: :command:`qvm-pool` [-h] [--verbose] [--quiet] --info *POOL_NAME* + list ^^^^ | :command:`qvm-pool list` [-h] [--verbose] [--quiet] @@ -69,6 +87,8 @@ List all available pools. aliases: l, ls +Legacy mode: :command:`qvm-pool` [-h] [--verbose] [--quiet] --list + remove ^^^^^^ | :command:`qvm-pool remove` [-h] [--verbose] [--quiet] *POOL_NAME* [*POOL_NAME* ...] @@ -79,6 +99,8 @@ all pool drivers?). aliases: r, rm +Legacy mode: :command:`qvm-pool` [-h] [--verbose] [--quiet] --remove *POOL_NAME* [*POOL_NAME* ...] + set ^^^ | :command:`qvm-pool set` [-h] [--verbose] [--quiet] *POOL_NAME* @@ -88,11 +110,14 @@ Modify driver options for a pool. .. option:: --option, -o Set option for the driver in `name=value` format. You can specify this - option multiple times. For supported drivers and their options, - see ``drivers``. + option multiple times. + + .. seealso:: The `drivers` command for supported drivers and their options. aliases: s +Legacy mode: :command:`qvm-pool` [-h] [--verbose] [--quiet] --set *POOL_NAME* -o *OPTIONS* + Examples -------- diff --git a/qubesadmin/tests/tools/qvm_pool.py b/qubesadmin/tests/tools/qvm_pool.py index f3b115a..bce2491 100644 --- a/qubesadmin/tests/tools/qvm_pool.py +++ b/qubesadmin/tests/tools/qvm_pool.py @@ -67,6 +67,17 @@ class TC_00_qvm_pool(qubesadmin.tests.QubesTestCase): app=self.app)) self.assertAllCalled() + def test_021_add_multiple(self): + self.app.expected_calls[ + ('dom0', 'admin.pool.Add', 'file', + b'name=test-pool\ndir_path=/some/path\nrw=True\n')] = b'0\x00' + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main( + ['add', 'test-pool', 'file', '-o', 'dir_path=/some/path', + '-o', 'rw=True'], + app=self.app)) + self.assertAllCalled() + def test_030_remove(self): self.app.expected_calls[ ('dom0', 'admin.pool.Remove', 'test-pool', None)] = b'0\x00' diff --git a/qubesadmin/tests/tools/qvm_pool_legacy.py b/qubesadmin/tests/tools/qvm_pool_legacy.py new file mode 100644 index 0000000..19fa24a --- /dev/null +++ b/qubesadmin/tests/tools/qvm_pool_legacy.py @@ -0,0 +1,116 @@ +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# 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 +# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . + +import qubesadmin.tests +import qubesadmin.tests.tools +import qubesadmin.tools.qvm_pool + + +class TC_00_qvm_pool_legacy(qubesadmin.tests.QubesTestCase): + def test_000_list(self): + self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \ + b'0\x00pool-file\npool-lvm\n' + self.app.expected_calls[ + ('dom0', 'admin.pool.Info', 'pool-file', None)] = \ + b'0\x00driver=file\ndir_path=/var/lib/qubes\n' + self.app.expected_calls[ + ('dom0', 'admin.pool.Info', 'pool-lvm', None)] = \ + b'0\x00driver=lvm\nvolume_group=qubes_dom0\nthin_pool=pool00\n' + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main(['-l'], app=self.app)) + self.assertEqual(stdout.getvalue(), + 'NAME DRIVER\n' + 'pool-file file\n' + 'pool-lvm lvm\n') + self.assertAllCalled() + + def test_010_list_drivers(self): + self.app.expected_calls[ + ('dom0', 'admin.pool.ListDrivers', None, None)] = \ + b'0\x00file dir_path revisions_to_keep\n' \ + b'lvm volume_group thin_pool revisions_to_keep\n' + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main(['--help-drivers'], app=self.app)) + self.assertEqual(stdout.getvalue(), + 'DRIVER OPTIONS\n' + 'file dir_path, revisions_to_keep\n' + 'lvm volume_group, thin_pool, revisions_to_keep\n' + ) + self.assertAllCalled() + + def test_020_add(self): + self.app.expected_calls[ + ('dom0', 'admin.pool.Add', 'file', + b'name=test-pool\ndir_path=/some/path\n')] = b'0\x00' + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main( + ['--add', 'test-pool', 'file', '-o', 'dir_path=/some/path'], + app=self.app)) + self.assertAllCalled() + + def test_030_remove(self): + self.app.expected_calls[ + ('dom0', 'admin.pool.Remove', 'test-pool', None)] = b'0\x00' + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main(['--remove', 'test-pool'], + app=self.app)) + self.assertAllCalled() + + def test_040_info(self): + self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \ + b'0\x00pool-file\npool-lvm\n' + self.app.expected_calls[ + ('dom0', 'admin.pool.Info', 'pool-lvm', None)] = \ + b'0\x00driver=lvm\nvolume_group=qubes_dom0\nthin_pool=pool00\n' + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main(['-i', 'pool-lvm'], + app=self.app)) + self.assertEqual(stdout.getvalue(), + 'name pool-lvm\n' + 'driver lvm\n' + 'thin_pool pool00\n' + 'volume_group qubes_dom0\n' + ) + self.assertAllCalled() + + def test_050_set(self): + self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \ + b'0\x00pool-file\npool-lvm\n' + self.app.expected_calls[ + ('dom0', 'admin.pool.Set.revisions_to_keep', 'pool-lvm', b'2')] = \ + b'0\x00' + self.assertEqual(0, + qubesadmin.tools.qvm_pool.main(['-s', 'pool-lvm', '-o', + 'revisions_to_keep=2'], + app=self.app)) + self.assertAllCalled() + + def test_051_set_invalid(self): + self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \ + b'0\x00pool-file\npool-lvm\n' + with self.assertRaises(SystemExit) as e: + qubesadmin.tools.qvm_pool.main( + ['-s', 'pool-lvm', '-o', 'prop=1'], + app=self.app) + self.assertEqual(e.exception.code, 2) + self.assertAllCalled() diff --git a/qubesadmin/tools/qvm_pool.py b/qubesadmin/tools/qvm_pool.py index 37f0052..3eb870c 100644 --- a/qubesadmin/tools/qvm_pool.py +++ b/qubesadmin/tools/qvm_pool.py @@ -22,12 +22,13 @@ from __future__ import print_function +import argparse import sys import qubesadmin import qubesadmin.exc -import qubesadmin.storage import qubesadmin.tools +import qubesadmin.tools.qvm_pool_legacy def list_drivers(args): @@ -103,14 +104,14 @@ def set_pool(args): def init_list_parser(sub_parsers): - ''' Add 'list' action related options ''' + ''' Adds 'list' action related options ''' l_parser = sub_parsers.add_parser( 'list', aliases=('l', 'ls'), help='List all available pools') l_parser.set_defaults(func=list_pools) def init_info_parser(sub_parsers): - ''' Add 'info' action related options ''' + ''' Adds 'info' action related options ''' i_parser = sub_parsers.add_parser( 'info', aliases=('i',), help='Print info about the specified pools') i_parser.add_argument(metavar='POOL_NAME', dest='pools', @@ -119,7 +120,7 @@ def init_info_parser(sub_parsers): def init_add_parser(sub_parsers): - ''' Add 'add' action related options ''' + ''' Adds 'add' action related options ''' a_parser = sub_parsers.add_parser( 'add', aliases=('a',), help='Add a new pool') a_parser.add_argument(metavar='POOL_NAME', dest='pool_name') @@ -132,7 +133,7 @@ def init_add_parser(sub_parsers): def init_remove_parser(sub_parsers): - ''' Add 'remove' action related options ''' + ''' Adds 'remove' action related options ''' r_parser = sub_parsers.add_parser( 'remove', aliases=('r', 'rm'), help='Remove the specified pools') r_parser.add_argument(metavar='POOL_NAME', dest='pool_names', nargs='+') @@ -140,7 +141,7 @@ def init_remove_parser(sub_parsers): def init_set_parser(sub_parsers): - ''' Add 'set' action related options ''' + ''' Adds 'set' action related options ''' s_parser = sub_parsers.add_parser( 'set', aliases=('s',), help='Modify driver options for a pool') s_parser.add_argument(metavar='POOL_NAME', dest='pool_name') @@ -152,8 +153,8 @@ def init_set_parser(sub_parsers): def get_parser(): - '''Create :py:class:`argparse.ArgumentParser` suitable for - :program:`qvm-pool`. + ''' Creates :py:class:`argparse.ArgumentParser` suitable for + :program:`qvm-pool`. ''' parser = qubesadmin.tools.QubesArgumentParser(description=__doc__, want_app=True) @@ -162,7 +163,7 @@ def get_parser(): sub_parsers = parser.add_subparsers( title='commands', dest='command', - description="For more information see qvm-pool command -h") + description="For more information see qvm-pool -h") d_parser = sub_parsers.add_parser( 'drivers', aliases=('d',), help='List all drivers with their options') @@ -180,8 +181,41 @@ def get_parser(): return parser +def uses_legacy_options(args, app): + ''' Checks if legacy options and used, and invokes the legacy tool ''' + parser = argparse.ArgumentParser(description=__doc__, + usage=argparse.SUPPRESS) + + parser.add_argument('-a', '--add', + dest='has_legacy_options', action='store_true', + help=argparse.SUPPRESS) + parser.add_argument('-i', '--info', + dest='has_legacy_options', action='store_true', + help=argparse.SUPPRESS) + parser.add_argument('-l', '--list', + dest='has_legacy_options', action='store_true', + help=argparse.SUPPRESS) + parser.add_argument('-r', '--remove', + dest='has_legacy_options', action='store_true', + help=argparse.SUPPRESS) + parser.add_argument('-s', '--set', + dest='has_legacy_options', action='store_true', + help=argparse.SUPPRESS) + parser.add_argument('--help-drivers', + dest='has_legacy_options', action='store_true', + help=argparse.SUPPRESS) + + parsed_args, _ = parser.parse_known_args(args) + if parsed_args.has_legacy_options: + qubesadmin.tools.qvm_pool_legacy.main(args, app) + return True + return False + + def main(args=None, app=None): '''Main routine of :program:`qvm-pool`.''' + if uses_legacy_options(args, app): + return 0 parser = get_parser() args = parser.parse_args(args, app=app) @@ -191,7 +225,6 @@ def main(args=None, app=None): except qubesadmin.exc.QubesException as e: parser.error_runtime(str(e)) return 1 - return 0 diff --git a/qubesadmin/tools/qvm_pool_legacy.py b/qubesadmin/tools/qvm_pool_legacy.py new file mode 100644 index 0000000..6b90b0c --- /dev/null +++ b/qubesadmin/tools/qvm_pool_legacy.py @@ -0,0 +1,208 @@ +# pylint: disable=too-few-public-methods + +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov +# +# 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 +# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . + +'''Manages Qubes pools and their options''' + +from __future__ import print_function + +import argparse +import sys + +import qubesadmin +import qubesadmin.exc +import qubesadmin.tools + + +class _Info(qubesadmin.tools.PoolsAction): + ''' Action for argument parser that displays pool info and exits. ''' + + def __init__(self, option_strings, help='print pool info and exit', + **kwargs): + # pylint: disable=redefined-builtin + super(_Info, self).__init__(option_strings, help=help, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, 'command', 'info') + super(_Info, self).__call__(parser, namespace, values, option_string) + + +def pool_info(pool): + ''' Prints out pool name and config ''' + data = [("name", pool.name)] + data += [i for i in sorted(pool.config.items()) if i[0] != 'name'] + qubesadmin.tools.print_table(data) + + +def list_pools(app): + ''' Prints out all known pools and their drivers ''' + result = [('NAME', 'DRIVER')] + for pool in app.pools.values(): + result += [(pool.name, pool.driver)] + qubesadmin.tools.print_table(result) + + +class _Remove(argparse.Action): + ''' Action for argument parser that removes a pool ''' + + def __init__(self, option_strings, dest=None, default=None, metavar=None): + super(_Remove, self).__init__(option_strings=option_strings, + dest=dest, + metavar=metavar, + default=default, + help='remove pool') + + def __call__(self, parser, namespace, name, option_string=None): + setattr(namespace, 'command', 'remove') + setattr(namespace, 'name', name) + + +class _Add(argparse.Action): + ''' Action for argument parser that adds a pool. ''' + + def __init__(self, option_strings, dest=None, default=None, metavar=None): + super(_Add, self).__init__(option_strings=option_strings, + dest=dest, + metavar=metavar, + default=default, + nargs=2, + help='add pool') + + def __call__(self, parser, namespace, values, option_string=None): + name, driver = values + setattr(namespace, 'command', 'add') + setattr(namespace, 'name', name) + setattr(namespace, 'driver', driver) + + +class _Set(qubesadmin.tools.PoolsAction): + ''' Action for argument parser that sets pool options. ''' + + def __init__(self, option_strings, dest=None, default=None, metavar=None): + super(_Set, self).__init__(option_strings=option_strings, + dest=dest, + metavar=metavar, + default=default, + help='modify pool (use -o to specify ' + 'modifications)') + + def __call__(self, parser, namespace, name, option_string=None): + setattr(namespace, 'command', 'set') + super(_Set, self).__call__(parser, namespace, name, option_string) + + +class _Options(argparse.Action): + ''' Action for argument parser that parsers options. ''' + + def __init__(self, option_strings, dest, default, metavar='options'): + super(_Options, self).__init__( + option_strings=option_strings, + dest=dest, + metavar=metavar, + default=default, + help='comma-separated list of driver options') + + def __call__(self, parser, namespace, options, option_string=None): + setattr(namespace, 'options', + dict([option.split('=', 1) for option in options.split(',')])) + + +def get_parser(): + ''' Parses the provided args ''' + parser = qubesadmin.tools.QubesArgumentParser(description=__doc__) + parser.add_argument('-o', action=_Options, dest='options', default={}) + group = parser.add_mutually_exclusive_group() + group.add_argument('-l', + '--list', + dest='command', + const='list', + action='store_const', + help='list all pools and exit (default action)') + group.add_argument('-i', '--info', metavar='POOLNAME', dest='pools', + action=_Info, default=[]) + group.add_argument('-a', + '--add', + action=_Add, + dest='command', + metavar=('NAME', 'DRIVER')) + group.add_argument('-r', '--remove', metavar='NAME', action=_Remove) + group.add_argument('-s', '--set', metavar='POOLNAME', dest='pool', + action=_Set, default=[]) + group.add_argument('--help-drivers', + dest='command', + const='list-drivers', + action='store_const', + help='list all drivers with their options and exit') + return parser + + +def main(args=None, app=None): + '''Main routine of :program:`qvm-pools`. + + :param list args: Optional arguments to override those delivered from \ + command line. + ''' + parser = get_parser() + try: + args = parser.parse_args(args, app=app) + except qubesadmin.exc.QubesException as e: + parser.print_error(str(e)) + return 1 + + if args.command is None or args.command == 'list': + list_pools(args.app) + elif args.command == 'list-drivers': + result = [('DRIVER', 'OPTIONS')] + for driver in sorted(args.app.pool_drivers): + params = args.app.pool_driver_parameters(driver) + driver_options = ', '.join(params) + result += [(driver, driver_options)] + qubesadmin.tools.print_table(result) + elif args.command == 'add': + try: + args.app.add_pool(name=args.name, driver=args.driver, + **args.options) + except qubesadmin.exc.QubesException as e: + parser.error('failed to add pool %s: %s\n' % (args.name, str(e))) + elif args.command == 'remove': + try: + args.app.remove_pool(args.name) + except KeyError: + parser.print_error('no such pool %s\n' % args.name) + except qubesadmin.exc.QubesException as e: + parser.error('failed to remove pool %s: %s\n' % (args.name, str(e))) + elif args.command == 'info': + for pool in args.pools: + pool_info(pool) + elif args.command == 'set': + pool = args.pool[0] + for opt, value in args.options.items(): + if not hasattr(type(pool), opt): + parser.error('setting pool option %s is not supported' % ( + pool.name)) + try: + setattr(pool, opt, value) + except qubesadmin.exc.QubesException as e: + parser.error('failed to set pool %s option %s: %s\n' % ( + pool.name, opt, str(e))) + return 0 + + +if __name__ == '__main__': + sys.exit(main())