tools: remove qvm-ls tool and related integration in qubes.property
qvm-ls tool (as all other tools) will be accessing properties through API, so no need (nor sense) for this tool-specific attributes in qubes.property. The only somehow used was ls_width, and in fact it made the output unnecessary wide. The tool itself is already moved to core-mgmt-client repository. QubesOS/qubes-issues#853
This commit is contained in:
parent
654e64c4a6
commit
2aa0de3d5b
@ -132,7 +132,7 @@ class Label(object):
|
||||
self.icon_dispvm) + ".png"
|
||||
|
||||
|
||||
class property(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
class property(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
'''Qubes property.
|
||||
|
||||
This class holds one property that can be saved to and loaded from
|
||||
@ -157,8 +157,6 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
:param int order: order of evaluation (bigger order values are later)
|
||||
:param bool clone: :py:meth:`PropertyHolder.clone_properties` will not \
|
||||
include this property by default if :py:obj:`False`
|
||||
:param str ls_head: column head for :program:`qvm-ls`
|
||||
:param int ls_width: column width in :program:`qvm-ls`
|
||||
:param str doc: docstring; this should be one paragraph of plain RST, no \
|
||||
sphinx-specific features
|
||||
|
||||
@ -192,7 +190,7 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
def __init__(self, name, setter=None, saver=None, type=None,
|
||||
default=_NO_DEFAULT, write_once=False, load_stage=2, order=0,
|
||||
save_via_ref=False, clone=True,
|
||||
ls_head=None, ls_width=None, doc=None):
|
||||
doc=None):
|
||||
# pylint: disable=redefined-builtin
|
||||
self.__name__ = name
|
||||
self._setter = setter
|
||||
@ -208,11 +206,6 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
self.__doc__ = doc
|
||||
self._attr_name = '_qubesprop_' + name
|
||||
|
||||
if ls_head is not None or ls_width is not None:
|
||||
self.ls_head = ls_head or self.__name__.replace('_', '-').upper()
|
||||
self.ls_width = max(ls_width or 0, len(self.ls_head) + 1)
|
||||
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
|
@ -908,7 +908,6 @@ def load_tests(loader, tests, pattern): # pylint: disable=unused-argument
|
||||
'qubes.tests.mgmt',
|
||||
'qubes.tests.tools.qvm_device',
|
||||
'qubes.tests.tools.qvm_firewall',
|
||||
'qubes.tests.tools.qvm_ls',
|
||||
'qubespolicy.tests',
|
||||
):
|
||||
tests.addTests(loader.loadTestsFromName(modname))
|
||||
|
@ -1,76 +0,0 @@
|
||||
# pylint: disable=protected-access,pointless-statement
|
||||
|
||||
#
|
||||
# The Qubes OS Project, https://www.qubes-os.org/
|
||||
#
|
||||
# Copyright (C) 2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import qubes
|
||||
import qubes.vm.adminvm
|
||||
import qubes.tools.qvm_ls
|
||||
|
||||
import qubes.tests
|
||||
import qubes.tests.vm
|
||||
|
||||
class TC_00_Column(qubes.tests.QubesTestCase):
|
||||
def test_000_collected(self):
|
||||
self.assertIn('NAME', qubes.tools.qvm_ls.Column.columns)
|
||||
|
||||
def test_100_init(self):
|
||||
try:
|
||||
testcolumn = qubes.tools.qvm_ls.Column('TESTCOLUMN', width=50)
|
||||
self.assertEqual(testcolumn.ls_head, 'TESTCOLUMN')
|
||||
self.assertEqual(testcolumn.ls_width, 50)
|
||||
finally:
|
||||
try:
|
||||
qubes.tools.qvm_ls.Column.columns['TESTCOLUMN']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def test_101_fix_width(self):
|
||||
try:
|
||||
testcolumn = qubes.tools.qvm_ls.Column('TESTCOLUMN', width=2)
|
||||
self.assertGreater(testcolumn.ls_width, len('TESTCOLUMN'))
|
||||
finally:
|
||||
try:
|
||||
qubes.tools.qvm_ls.Column.columns['TESTCOLUMN']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
class TC_90_globals(qubes.tests.QubesTestCase):
|
||||
# @qubes.tests.skipUnlessDom0
|
||||
def test_100_simple_flag(self):
|
||||
flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'internal')
|
||||
|
||||
# TODO after serious testing of QubesVM and Qubes app, this should be
|
||||
# using normal components
|
||||
app = qubes.tests.vm.TestApp()
|
||||
vm = qubes.vm.adminvm.AdminVM(app, None,
|
||||
qid=0, name='dom0', internal='False')
|
||||
|
||||
self.assertFalse(flag(None, vm))
|
||||
vm.internal = True
|
||||
self.assertTrue(flag(None, vm))
|
||||
|
||||
|
||||
def test_900_formats_columns(self):
|
||||
for fmt in qubes.tools.qvm_ls.formats:
|
||||
for col in qubes.tools.qvm_ls.formats[fmt]:
|
||||
self.assertIn(col.upper(), qubes.tools.qvm_ls.Column.columns)
|
@ -1,634 +0,0 @@
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
#
|
||||
# The Qubes OS Project, https://www.qubes-os.org/
|
||||
#
|
||||
# Copyright (C) 2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
'''qvm-ls - List available domains'''
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import qubes
|
||||
import qubes.config
|
||||
import qubes.tools
|
||||
import qubes.utils
|
||||
|
||||
|
||||
#
|
||||
# columns
|
||||
#
|
||||
|
||||
class Column(object):
|
||||
'''A column in qvm-ls output characterised by its head, a width and a way
|
||||
to fetch a parameter describing the domain.
|
||||
|
||||
:param str head: Column head (usually uppercase).
|
||||
:param int width: Column width.
|
||||
:param str attr: Attribute, possibly complex (containing ``.``). This may \
|
||||
also be a callable that gets as its only argument the domain.
|
||||
:param str doc: Description of column (will be visible in --help-columns).
|
||||
'''
|
||||
|
||||
#: collection of all columns
|
||||
columns = {}
|
||||
|
||||
def __init__(self, head, width=0, attr=None, doc=None):
|
||||
self.ls_head = head
|
||||
self.ls_width = max(width, len(self.ls_head) + 1)
|
||||
self.__doc__ = doc if doc is None else qubes.utils.format_doc(doc)
|
||||
|
||||
# intentionally not always do set self._attr,
|
||||
# to cause AttributeError in self.format()
|
||||
if attr is not None:
|
||||
self._attr = attr
|
||||
|
||||
self.__class__.columns[self.ls_head] = self
|
||||
|
||||
|
||||
def cell(self, vm):
|
||||
'''Format one cell.
|
||||
|
||||
.. note::
|
||||
|
||||
This is only for technical formatting (filling with space). If you
|
||||
want to subclass the :py:class:`Column` class, you should override
|
||||
:py:meth:`Column.format` method instead.
|
||||
|
||||
:param qubes.vm.qubesvm.QubesVM: Domain to get a value from.
|
||||
:returns: string that is at least as wide as needed to fill table row.
|
||||
:rtype: str
|
||||
'''
|
||||
|
||||
value = self.format(vm) or '-'
|
||||
return value.ljust(self.ls_width)
|
||||
|
||||
|
||||
def format(self, vm):
|
||||
'''Format one cell value.
|
||||
|
||||
Return value to put in a table cell.
|
||||
|
||||
:param qubes.vm.qubesvm.QubesVM: Domain to get a value from.
|
||||
:returns: Value to put, or :py:obj:`None` if no value.
|
||||
:rtype: str or None
|
||||
'''
|
||||
|
||||
ret = None
|
||||
try:
|
||||
if isinstance(self._attr, str):
|
||||
ret = vm
|
||||
for attrseg in self._attr.split('.'):
|
||||
ret = getattr(ret, attrseg)
|
||||
elif isinstance(self._attr, collections.Callable):
|
||||
ret = self._attr(vm)
|
||||
|
||||
except (AttributeError, ZeroDivisionError):
|
||||
# division by 0 may be caused by arithmetic in callable attr
|
||||
return None
|
||||
|
||||
if ret is None:
|
||||
return None
|
||||
|
||||
# late import to avoid circular import
|
||||
# pylint: disable=redefined-outer-name
|
||||
import qubes.vm
|
||||
if isinstance(ret, (qubes.vm.BaseVM, qubes.Label)):
|
||||
return ret.name
|
||||
|
||||
if isinstance(ret, int):
|
||||
return str(ret)
|
||||
|
||||
return ret
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(head={!r}, width={!r})'.format(self.__class__.__name__,
|
||||
self.ls_head, self.ls_width)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.ls_head == other.ls_head
|
||||
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.ls_head < other.ls_head
|
||||
|
||||
|
||||
def column(width=0, head=None):
|
||||
'''Mark function or plain property as valid column in :program:`qvm-ls`.
|
||||
|
||||
By default all instances of :py:class:`qubes.property` are valid.
|
||||
|
||||
:param int width: Column width
|
||||
:param str head: Column head (default: take property's name)
|
||||
'''
|
||||
|
||||
def decorator(obj):
|
||||
# pylint: disable=missing-docstring
|
||||
# we keep hints on fget, so the order of decorators does not matter
|
||||
holder = obj.fget if isinstance(obj, property) else obj
|
||||
|
||||
try:
|
||||
holder.ls_head = head or holder.__name__.replace('_', '-').upper()
|
||||
except AttributeError:
|
||||
raise TypeError('Cannot find default column name '
|
||||
'for a strange object {!r}'.format(obj))
|
||||
|
||||
holder.ls_width = max(width, len(holder.ls_head) + 1)
|
||||
|
||||
return obj
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class PropertyColumn(Column):
|
||||
'''Column that displays value from property (:py:class:`property` or
|
||||
:py:class:`qubes.property`) of domain.
|
||||
|
||||
You shouldn't use this class directly, see :py:func:`column` decorator.
|
||||
|
||||
:param holder: Holder of magic attributes.
|
||||
'''
|
||||
|
||||
def __init__(self, holder):
|
||||
super(PropertyColumn, self).__init__(
|
||||
head=holder.ls_head,
|
||||
width=holder.ls_width,
|
||||
attr=holder.__name__,
|
||||
doc=holder.__doc__)
|
||||
self.holder = holder
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(head={!r}, width={!r} holder={!r})'.format(
|
||||
self.__class__.__name__,
|
||||
self.ls_head,
|
||||
self.ls_width,
|
||||
self.holder)
|
||||
|
||||
|
||||
def process_class(cls):
|
||||
'''Process class after definition to find all listable properties.
|
||||
|
||||
It is used in metaclass of the domain.
|
||||
|
||||
:param qubes.vm.BaseVMMeta cls: Class to round up.
|
||||
'''
|
||||
|
||||
for klass in cls.__mro__:
|
||||
for prop in klass.__dict__.values():
|
||||
holder = prop.fget \
|
||||
if isinstance(prop, property) \
|
||||
else prop
|
||||
if not hasattr(holder, 'ls_head') or holder.ls_head is None:
|
||||
continue
|
||||
|
||||
for col in Column.columns.values():
|
||||
if not isinstance(col, PropertyColumn):
|
||||
continue
|
||||
|
||||
if col.holder.__name__ != holder.__name__:
|
||||
continue
|
||||
|
||||
if col.ls_head != holder.ls_head:
|
||||
raise TypeError('Found column head mismatch in class {!r} '
|
||||
'({!r} != {!r})'.format(cls.__name__,
|
||||
holder.ls_head, col.ls_head))
|
||||
|
||||
if col.ls_width != holder.ls_width:
|
||||
raise TypeError('Found column width mismatch in class {!r} '
|
||||
'({!r} != {!r})'.format(cls.__name__,
|
||||
holder.ls_width, col.ls_width))
|
||||
|
||||
PropertyColumn(holder)
|
||||
|
||||
|
||||
def flag(field):
|
||||
'''Mark method as flag field.
|
||||
|
||||
:param int field: Which field to fill (counted from 1)
|
||||
'''
|
||||
|
||||
def decorator(obj):
|
||||
# pylint: disable=missing-docstring
|
||||
obj.field = field
|
||||
return obj
|
||||
return decorator
|
||||
|
||||
|
||||
def simple_flag(field, letter, attr, doc=None):
|
||||
'''Create simple, binary flag.
|
||||
|
||||
:param str attr: Attribute name to check. If result is true, flag is fired.
|
||||
:param str letter: The letter to show.
|
||||
'''
|
||||
|
||||
def helper(self, vm):
|
||||
# pylint: disable=missing-docstring,unused-argument
|
||||
try:
|
||||
value = getattr(vm, attr)
|
||||
except AttributeError:
|
||||
value = False
|
||||
|
||||
if value:
|
||||
return letter[0]
|
||||
|
||||
helper.__doc__ = doc
|
||||
helper.field = field
|
||||
return helper
|
||||
|
||||
|
||||
class StatusColumn(Column):
|
||||
'''Some fancy flags that describe general status of the domain.'''
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
def __init__(self):
|
||||
super(StatusColumn, self).__init__(
|
||||
head='STATUS',
|
||||
width=len(self.get_flags()) + 1,
|
||||
doc=self.__class__.__doc__)
|
||||
|
||||
|
||||
@flag(1)
|
||||
def type(self, vm):
|
||||
'''Type of domain.
|
||||
|
||||
0 AdminVM (AKA Dom0)
|
||||
aA AppVM
|
||||
dD DisposableVM
|
||||
sS StandaloneVM
|
||||
tT TemplateVM
|
||||
|
||||
When it is HVM (optimised VM), the letter is capital.
|
||||
'''
|
||||
|
||||
# late import because of circular dependency
|
||||
# pylint: disable=redefined-outer-name
|
||||
import qubes.vm
|
||||
import qubes.vm.adminvm
|
||||
import qubes.vm.appvm
|
||||
import qubes.vm.dispvm
|
||||
import qubes.vm.qubesvm
|
||||
import qubes.vm.templatevm
|
||||
|
||||
if isinstance(vm, qubes.vm.adminvm.AdminVM):
|
||||
return '0'
|
||||
|
||||
ret = None
|
||||
# TODO right order, depending on inheritance
|
||||
if isinstance(vm, qubes.vm.templatevm.TemplateVM):
|
||||
ret = 't'
|
||||
if isinstance(vm, qubes.vm.appvm.AppVM):
|
||||
ret = 'a'
|
||||
# if isinstance(vm, qubes.vm.standalonevm.StandaloneVM):
|
||||
# ret = 's'
|
||||
if isinstance(vm, qubes.vm.dispvm.DispVM):
|
||||
ret = 'd'
|
||||
|
||||
if ret is not None:
|
||||
if getattr(vm, 'hvm', False):
|
||||
return ret.upper()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@flag(2)
|
||||
def power(self, vm):
|
||||
'''Current power state.
|
||||
|
||||
r running
|
||||
t transient
|
||||
p paused
|
||||
s suspended
|
||||
h halting
|
||||
d dying
|
||||
c crashed
|
||||
? unknown
|
||||
'''
|
||||
|
||||
state = vm.get_power_state().lower()
|
||||
if state == 'unknown':
|
||||
return '?'
|
||||
elif state in ('running', 'transient', 'paused', 'suspended',
|
||||
'halting', 'dying', 'crashed'):
|
||||
return state[0]
|
||||
|
||||
|
||||
updateable = simple_flag(3, 'U', 'updateable',
|
||||
doc='If the domain is updateable.')
|
||||
|
||||
provides_network = simple_flag(4, 'N', 'provides_network',
|
||||
doc='If the domain provides network.')
|
||||
|
||||
installed_by_rpm = simple_flag(5, 'R', 'installed_by_rpm',
|
||||
doc='If the domain is installed by RPM.')
|
||||
|
||||
internal = simple_flag(6, 'i', 'internal',
|
||||
doc='If the domain is internal (not normally shown, no appmenus).')
|
||||
|
||||
debug = simple_flag(7, 'D', 'debug',
|
||||
doc='If the domain is being debugged.')
|
||||
|
||||
autostart = simple_flag(8, 'A', 'autostart',
|
||||
doc='If the domain is marked for autostart.')
|
||||
|
||||
# TODO (not sure if really):
|
||||
# include in backups
|
||||
# uses_custom_config
|
||||
|
||||
def _no_flag(self, vm):
|
||||
'''Reserved for future use.'''
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_flags(cls):
|
||||
'''Get all flags as list.
|
||||
|
||||
Holes between flags are filled with :py:meth:`_no_flag`.
|
||||
|
||||
:rtype: list
|
||||
'''
|
||||
|
||||
flags = {}
|
||||
for mycls in cls.__mro__:
|
||||
for attr in mycls.__dict__.values():
|
||||
if not hasattr(attr, 'field'):
|
||||
continue
|
||||
if attr.field in flags:
|
||||
continue
|
||||
flags[attr.field] = attr
|
||||
|
||||
return [(flags[i] if i in flags else cls._no_flag)
|
||||
for i in range(1, max(flags) + 1)]
|
||||
|
||||
|
||||
def format(self, vm):
|
||||
return ''.join((flag(self, vm) or '-') for flag in self.get_flags())
|
||||
|
||||
|
||||
def calc_size(vm, volume_name):
|
||||
''' Calculates the volume size in MB '''
|
||||
try:
|
||||
return vm.volumes[volume_name].size // 1024 // 1024
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
def calc_usage(vm, volume_name):
|
||||
''' Calculates the volume usage in MB '''
|
||||
try:
|
||||
return vm.volumes[volume_name].usage // 1024 // 1024
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
def calc_used(vm, volume_name):
|
||||
''' Calculates the volume usage in percent '''
|
||||
size = calc_size(vm, volume_name)
|
||||
if size == 0:
|
||||
return 0
|
||||
usage = calc_usage(vm, volume_name)
|
||||
return usage * 100 // size
|
||||
|
||||
|
||||
# todo maxmem
|
||||
|
||||
Column('GATEWAY', width=15,
|
||||
attr='netvm.gateway',
|
||||
doc='Network gateway.')
|
||||
|
||||
Column('MEMORY', width=5,
|
||||
attr=(lambda vm: vm.get_mem() / 1024 if vm.is_running() else None),
|
||||
doc='Memory currently used by VM')
|
||||
|
||||
Column('DISK', width=5,
|
||||
attr=(lambda vm: vm.storage.get_disk_utilization() / 1024 / 1024),
|
||||
doc='Total disk utilisation.')
|
||||
|
||||
|
||||
Column('PRIV-CURR', width=5,
|
||||
attr=(lambda vm: calc_usage(vm, 'private')),
|
||||
doc='Disk utilisation by private image (/home, /usr/local).')
|
||||
|
||||
Column('PRIV-MAX', width=5,
|
||||
attr=(lambda vm: calc_size(vm, 'private')),
|
||||
doc='Maximum available space for private image.')
|
||||
|
||||
Column('PRIV-USED', width=5,
|
||||
attr=(lambda vm: calc_used(vm, 'private')),
|
||||
doc='Disk utilisation by private image as a percentage of available space.')
|
||||
|
||||
|
||||
Column('ROOT-CURR', width=5,
|
||||
attr=(lambda vm: calc_usage(vm, 'root')),
|
||||
doc='Disk utilisation by root image (/usr, /lib, /etc, ...).')
|
||||
|
||||
Column('ROOT-MAX', width=5,
|
||||
attr=(lambda vm: calc_size(vm, 'root')),
|
||||
doc='Maximum available space for root image.')
|
||||
|
||||
Column('ROOT-USED', width=5,
|
||||
attr=(lambda vm: calc_used(vm, 'root')),
|
||||
doc='Disk utilisation by root image as a percentage of available space.')
|
||||
|
||||
|
||||
StatusColumn()
|
||||
|
||||
|
||||
class Table(object):
|
||||
'''Table that is displayed to the user.
|
||||
|
||||
:param qubes.Qubes app: Qubes application object.
|
||||
:param list colnames: Names of the columns (need not to be uppercase).
|
||||
'''
|
||||
def __init__(self, app, colnames, raw_data=False):
|
||||
self.app = app
|
||||
self.columns = tuple(Column.columns[col.upper()] for col in colnames)
|
||||
self.raw_data = raw_data
|
||||
|
||||
|
||||
def format_head(self):
|
||||
'''Format table head (all column heads).'''
|
||||
return ''.join('{head:{width}s}'.format(
|
||||
head=col.ls_head, width=col.ls_width)
|
||||
for col in self.columns[:-1]) + \
|
||||
self.columns[-1].ls_head
|
||||
|
||||
|
||||
def format_row(self, vm):
|
||||
'''Format single table row (all columns for one domain).'''
|
||||
if self.raw_data:
|
||||
return '|'.join(col.format(vm) for col in self.columns)
|
||||
|
||||
return ''.join(col.cell(vm) for col in self.columns)
|
||||
|
||||
|
||||
def write_table(self, stream=sys.stdout):
|
||||
'''Write whole table to file-like object.
|
||||
|
||||
:param file stream: Stream to write the table to.
|
||||
'''
|
||||
|
||||
if not self.raw_data:
|
||||
stream.write(self.format_head() + '\n')
|
||||
for vm in self.app.domains:
|
||||
stream.write(self.format_row(vm) + '\n')
|
||||
|
||||
|
||||
#: Available formats. Feel free to plug your own one.
|
||||
formats = {
|
||||
'simple': ('name', 'status', 'label', 'template', 'netvm'),
|
||||
'network': ('name', 'status', 'netvm', 'ip', 'ipback', 'gateway'),
|
||||
'full': ('name', 'status', 'label', 'qid', 'xid', 'uuid'),
|
||||
# 'perf': ('name', 'status', 'cpu', 'memory'),
|
||||
'disk': ('name', 'status', 'disk',
|
||||
'priv-curr', 'priv-max', 'priv-used',
|
||||
'root-curr', 'root-max', 'root-used'),
|
||||
}
|
||||
|
||||
|
||||
class _HelpColumnsAction(argparse.Action):
|
||||
'''Action for argument parser that displays all columns and exits.'''
|
||||
# pylint: disable=redefined-builtin
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest=argparse.SUPPRESS,
|
||||
default=argparse.SUPPRESS,
|
||||
help='list all available columns with short descriptions and exit'):
|
||||
super(_HelpColumnsAction, self).__init__(
|
||||
option_strings=option_strings,
|
||||
dest=dest,
|
||||
default=default,
|
||||
nargs=0,
|
||||
help=help)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
width = max(len(column.ls_head) for column in Column.columns.values())
|
||||
wrapper = textwrap.TextWrapper(width=80,
|
||||
initial_indent=' ', subsequent_indent=' ' * (width + 6))
|
||||
|
||||
text = 'Available columns:\n' + '\n'.join(
|
||||
wrapper.fill('{head:{width}s} {doc}'.format(
|
||||
head=column.ls_head,
|
||||
doc=column.__doc__ or '',
|
||||
width=width))
|
||||
for column in sorted(Column.columns.values()))
|
||||
parser.exit(message=text + '\n')
|
||||
|
||||
class _HelpFormatsAction(argparse.Action):
|
||||
'''Action for argument parser that displays all formats and exits.'''
|
||||
# pylint: disable=redefined-builtin
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest=argparse.SUPPRESS,
|
||||
default=argparse.SUPPRESS,
|
||||
help='list all available formats with their definitions and exit'):
|
||||
super(_HelpFormatsAction, self).__init__(
|
||||
option_strings=option_strings,
|
||||
dest=dest,
|
||||
default=default,
|
||||
nargs=0,
|
||||
help=help)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
width = max(len(fmt) for fmt in formats)
|
||||
text = 'Available formats:\n' + ''.join(
|
||||
' {fmt:{width}s} {columns}\n'.format(
|
||||
fmt=fmt, columns=','.join(formats[fmt]).upper(), width=width)
|
||||
for fmt in sorted(formats))
|
||||
parser.exit(message=text)
|
||||
|
||||
|
||||
def get_parser():
|
||||
'''Create :py:class:`argparse.ArgumentParser` suitable for
|
||||
:program:`qvm-ls`.
|
||||
'''
|
||||
# parser creation is delayed to get all the columns that are scattered
|
||||
# thorough the modules
|
||||
|
||||
wrapper = textwrap.TextWrapper(width=80, break_on_hyphens=False,
|
||||
initial_indent=' ', subsequent_indent=' ')
|
||||
|
||||
parser = qubes.tools.QubesArgumentParser(
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
description='List Qubes domains and their parametres.',
|
||||
epilog='available formats (see --help-formats):\n{}\n\n'
|
||||
'available columns (see --help-columns):\n{}'.format(
|
||||
wrapper.fill(', '.join(sorted(formats.keys()))),
|
||||
wrapper.fill(', '.join(sorted(sorted(Column.columns.keys()))))))
|
||||
|
||||
parser.add_argument('--help-columns', action=_HelpColumnsAction)
|
||||
parser.add_argument('--help-formats', action=_HelpFormatsAction)
|
||||
|
||||
|
||||
parser_formats = parser.add_mutually_exclusive_group()
|
||||
|
||||
parser_formats.add_argument('--format', '-o', metavar='FORMAT',
|
||||
action='store', choices=formats.keys(), default='simple',
|
||||
help='preset format')
|
||||
|
||||
parser_formats.add_argument('--fields', '-O', metavar='FIELD,...',
|
||||
action='store',
|
||||
help='user specified format (see available columns below)')
|
||||
|
||||
|
||||
parser.add_argument('--raw-data', action='store_true',
|
||||
help='Display specify data of specified VMs. Intended for '
|
||||
'bash-parsing.')
|
||||
|
||||
# parser.add_argument('--conf', '-c',
|
||||
# action='store', metavar='CFGFILE',
|
||||
# help='Qubes config file')
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(args=None):
|
||||
'''Main routine of :program:`qvm-ls`.
|
||||
|
||||
:param list args: Optional arguments to override those delivered from \
|
||||
command line.
|
||||
'''
|
||||
|
||||
parser = get_parser()
|
||||
try:
|
||||
args = parser.parse_args(args)
|
||||
except qubes.exc.QubesException as e:
|
||||
parser.print_error(str(e))
|
||||
return 1
|
||||
|
||||
if args.fields:
|
||||
columns = [col.strip() for col in args.fields.split(',')]
|
||||
for col in columns:
|
||||
if col.upper() not in Column.columns:
|
||||
parser.error('no such column: {!r}'.format(col))
|
||||
else:
|
||||
columns = formats[args.format]
|
||||
|
||||
table = Table(args.app, columns)
|
||||
table.write_table(sys.stdout)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -38,7 +38,6 @@ import qubes
|
||||
import qubes.devices
|
||||
import qubes.events
|
||||
import qubes.log
|
||||
import qubes.tools.qvm_ls
|
||||
|
||||
VM_ENTRY_POINT = 'qubes.vm'
|
||||
|
||||
@ -164,7 +163,6 @@ class BaseVMMeta(qubes.events.EmitterMeta):
|
||||
'''Metaclass for :py:class:`.BaseVM`'''
|
||||
def __init__(cls, name, bases, dict_):
|
||||
super(BaseVMMeta, cls).__init__(name, bases, dict_)
|
||||
qubes.tools.qvm_ls.process_class(cls)
|
||||
|
||||
|
||||
class BaseVM(qubes.PropertyHolder, metaclass=BaseVMMeta):
|
||||
|
@ -34,7 +34,6 @@ class AppVM(qubes.vm.qubesvm.QubesVM):
|
||||
template = qubes.VMProperty('template',
|
||||
load_stage=4,
|
||||
vmclass=qubes.vm.templatevm.TemplateVM,
|
||||
ls_width=31,
|
||||
doc='Template, on which this AppVM is based.')
|
||||
|
||||
dispvm_allowed = qubes.property('dispvm_allowed',
|
||||
|
@ -33,12 +33,10 @@ class DispVM(qubes.vm.qubesvm.QubesVM):
|
||||
template = qubes.VMProperty('template',
|
||||
load_stage=4,
|
||||
vmclass=qubes.vm.appvm.AppVM,
|
||||
ls_width=31,
|
||||
doc='AppVM, on which this DispVM is based.')
|
||||
|
||||
dispid = qubes.property('dispid', type=int, write_once=True,
|
||||
clone=False,
|
||||
ls_width=3,
|
||||
doc='''Internal, persistent identifier of particular DispVM.''')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -68,20 +68,17 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
mac = qubes.property('mac', type=str,
|
||||
default='00:16:3E:5E:6C:00',
|
||||
setter=_setter_mac,
|
||||
ls_width=17,
|
||||
doc='MAC address of the NIC emulated inside VM')
|
||||
|
||||
ip = qubes.property('ip', type=str,
|
||||
default=_default_ip,
|
||||
setter=_setter_ip,
|
||||
ls_width=15,
|
||||
doc='IP address of this domain.')
|
||||
|
||||
# CORE2: swallowed uses_default_netvm
|
||||
netvm = qubes.VMProperty('netvm', load_stage=4, allow_none=True,
|
||||
default=(lambda self: self.app.default_fw_netvm if self.provides_network
|
||||
else self.app.default_netvm),
|
||||
ls_width=31,
|
||||
doc='''VM that provides network connection to this domain. When
|
||||
`None`, machine is disconnected. When absent, domain uses default
|
||||
NetVM.''')
|
||||
@ -101,21 +98,18 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
#
|
||||
|
||||
|
||||
@qubes.tools.qvm_ls.column(width=15)
|
||||
@property
|
||||
def visible_ip(self):
|
||||
'''IP address of this domain as seen by the domain.'''
|
||||
return self.features.check_with_template('net/fake-ip', None) or \
|
||||
self.ip
|
||||
|
||||
@qubes.tools.qvm_ls.column(width=15)
|
||||
@property
|
||||
def visible_gateway(self):
|
||||
'''Default gateway of this domain as seen by the domain.'''
|
||||
return self.features.check_with_template('net/fake-gateway', None) or \
|
||||
self.netvm.gateway
|
||||
|
||||
@qubes.tools.qvm_ls.column(width=15)
|
||||
@property
|
||||
def visible_netmask(self):
|
||||
'''Netmask as seen by the domain.'''
|
||||
@ -139,13 +133,11 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
# does not happen, because qid < 253, but may happen in the future.
|
||||
return '10.137.{}.{}'.format((vm.qid >> 8) & 7, vm.qid & 7)
|
||||
|
||||
@qubes.tools.qvm_ls.column(head='IPBACK', width=15)
|
||||
@property
|
||||
def gateway(self):
|
||||
'''Gateway for other domains that use this domain as netvm.'''
|
||||
return self.visible_ip if self.provides_network else None
|
||||
|
||||
@qubes.tools.qvm_ls.column(width=15)
|
||||
@property
|
||||
def netmask(self):
|
||||
'''Netmask for gateway address.'''
|
||||
@ -164,7 +156,6 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
# used in both
|
||||
#
|
||||
|
||||
@qubes.tools.qvm_ls.column(width=15)
|
||||
@property
|
||||
def dns(self):
|
||||
'''Secondary DNS server set up for this domain.'''
|
||||
|
@ -47,7 +47,6 @@ import qubes.exc
|
||||
import qubes.storage
|
||||
import qubes.storage.domain
|
||||
import qubes.storage.file
|
||||
import qubes.tools.qvm_ls
|
||||
import qubes.utils
|
||||
import qubes.vm
|
||||
import qubes.vm.mix.net
|
||||
@ -357,7 +356,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
label = qubes.property('label',
|
||||
setter=_setter_label,
|
||||
saver=(lambda self, prop, value: 'label-{}'.format(value.index)),
|
||||
ls_width=14,
|
||||
doc='''Colourful label assigned to VM. This is where the colour of the
|
||||
padlock is set.''')
|
||||
|
||||
@ -368,18 +366,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
qid = qubes.property('qid', type=int, write_once=True,
|
||||
setter=_setter_qid,
|
||||
clone=False,
|
||||
ls_width=3,
|
||||
doc='''Internal, persistent identificator of particular domain. Note
|
||||
this is different from Xen domid.''')
|
||||
|
||||
name = qubes.property('name', type=str,
|
||||
clone=False,
|
||||
ls_width=31,
|
||||
doc='User-specified name of the domain.')
|
||||
|
||||
uuid = qubes.property('uuid', type=uuid.UUID, write_once=True,
|
||||
clone=False,
|
||||
ls_width=36,
|
||||
doc='UUID from libvirt.')
|
||||
|
||||
hvm = qubes.property('hvm',
|
||||
@ -416,14 +411,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
type=int,
|
||||
setter=_setter_positive_int,
|
||||
default=(lambda self: self.app.host.no_cpus),
|
||||
ls_width=2,
|
||||
doc='FIXME')
|
||||
|
||||
# CORE2: swallowed uses_default_kernel
|
||||
kernel = qubes.property('kernel', type=str,
|
||||
setter=_setter_kernel,
|
||||
default=(lambda self: self.app.default_kernel),
|
||||
ls_width=12,
|
||||
doc='Kernel used by this domain.')
|
||||
|
||||
# CORE2: swallowed uses_default_kernelopts
|
||||
@ -434,7 +427,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
if list(self.devices['pci'].persistent())
|
||||
else self.template.kernelopts if hasattr(self, 'template')
|
||||
else qubes.config.defaults['kernelopts']),
|
||||
ls_width=30,
|
||||
doc='Kernel command line passed to domain.')
|
||||
|
||||
debug = qubes.property('debug', type=bool, default=False,
|
||||
@ -449,7 +441,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
default=(lambda self: self.template.default_user
|
||||
if hasattr(self, 'template') else 'user'),
|
||||
setter=_setter_default_user,
|
||||
ls_width=12,
|
||||
doc='FIXME')
|
||||
|
||||
# pylint: enable=no-member
|
||||
@ -463,7 +454,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
|
||||
qrexec_timeout = qubes.property('qrexec_timeout', type=int, default=60,
|
||||
setter=_setter_positive_int,
|
||||
ls_width=3,
|
||||
doc='''Time in seconds after which qrexec connection attempt is deemed
|
||||
failed. Operating system inside VM should be able to boot in this
|
||||
time.''')
|
||||
@ -512,7 +502,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
|
||||
# VMM-related
|
||||
|
||||
@qubes.tools.qvm_ls.column(width=3)
|
||||
@property
|
||||
def xid(self):
|
||||
'''Xen ID.
|
||||
|
@ -288,7 +288,6 @@ fi
|
||||
%{python3_sitelib}/qubes/tools/qvm_check.py
|
||||
%{python3_sitelib}/qubes/tools/qvm_clone.py
|
||||
%{python3_sitelib}/qubes/tools/qvm_kill.py
|
||||
%{python3_sitelib}/qubes/tools/qvm_ls.py
|
||||
%{python3_sitelib}/qubes/tools/qvm_pause.py
|
||||
%{python3_sitelib}/qubes/tools/qvm_pool.py
|
||||
%{python3_sitelib}/qubes/tools/qvm_prefs.py
|
||||
@ -349,7 +348,6 @@ fi
|
||||
%{python3_sitelib}/qubes/tests/tools/init.py
|
||||
%{python3_sitelib}/qubes/tests/tools/qvm_device.py
|
||||
%{python3_sitelib}/qubes/tests/tools/qvm_firewall.py
|
||||
%{python3_sitelib}/qubes/tests/tools/qvm_ls.py
|
||||
|
||||
%dir %{python3_sitelib}/qubes/tests/integ
|
||||
%dir %{python3_sitelib}/qubes/tests/integ/__pycache__
|
||||
|
Loading…
Reference in New Issue
Block a user