qvm_volume.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. # encoding=utf-8
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
  6. # Copyright (C) 2017 Marek Marczykowski-Górecki
  7. # <marmarek@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU Lesser General Public License as published by
  11. # the Free Software Foundation; either version 2.1 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU Lesser General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public License along
  20. # with this program; if not, see <http://www.gnu.org/licenses/>.
  21. '''Qubes volume management'''
  22. from __future__ import print_function
  23. import sys
  24. import qubesadmin
  25. import qubesadmin.exc
  26. import qubesadmin.tools
  27. import qubesadmin.utils
  28. def prepare_table(vd_list, full=False):
  29. ''' Converts a list of :py:class:`VolumeData` objects to a list of tupples
  30. for the :py:func:`qubes.tools.print_table`.
  31. If :program:`qvm-volume` is running in a TTY, it will ommit duplicate
  32. data.
  33. :param list vd_list: List of :py:class:`VolumeData` objects.
  34. :param bool full: If set to true duplicate data is printed even when
  35. running from TTY.
  36. :returns: list of tupples
  37. '''
  38. output = []
  39. output += [('POOL:VOLUME', 'VMNAME', 'VOLUME_NAME', 'REVERT_POSSIBLE')]
  40. for volume in sorted(vd_list):
  41. if volume.domains:
  42. vmname, volume_name = volume.domains.pop()
  43. output += [(str(volume), vmname, volume_name, volume.revisions)]
  44. for tupple in volume.domains:
  45. vmname, volume_name = tupple
  46. if full or not sys.stdout.isatty():
  47. output += [(str(volume), vmname, volume_name,
  48. volume.revisions)]
  49. else:
  50. output += [('', vmname, volume_name, volume.revisions)]
  51. else:
  52. output += [(str(volume), "")]
  53. return output
  54. class VolumeData(object):
  55. ''' Wrapper object around :py:class:`qubes.storage.Volume`, mainly to track
  56. the domains a volume is attached to.
  57. '''
  58. # pylint: disable=too-few-public-methods
  59. def __init__(self, volume):
  60. self.pool = volume.pool
  61. self.vid = volume.vid
  62. if volume.revisions:
  63. self.revisions = 'Yes'
  64. else:
  65. self.revisions = 'No'
  66. self.domains = []
  67. def __lt__(self, other):
  68. return (self.pool, self.vid) < (other.pool, other.vid)
  69. def __str__(self):
  70. return "{!s}:{!s}".format(self.pool, self.vid)
  71. def list_volumes(args):
  72. ''' Called by the parser to execute the qvm-volume list subcommand. '''
  73. app = args.app
  74. if hasattr(args, 'domains') and args.domains:
  75. domains = args.domains
  76. else:
  77. domains = app.domains
  78. volumes = [v for vm in domains for v in vm.volumes.values()]
  79. if getattr(args, 'pools', None):
  80. # only specified pools
  81. volumes = [v for v in volumes if v.pool in args.pools]
  82. vd_dict = {}
  83. for volume in volumes:
  84. volume_data = VolumeData(volume)
  85. try:
  86. vd_dict[volume.pool][volume.vid] = volume_data
  87. except KeyError:
  88. vd_dict[volume.pool] = {volume.vid: volume_data}
  89. for domain in domains: # gather the domain names
  90. try:
  91. for name, volume in domain.volumes.items():
  92. try:
  93. volume_data = vd_dict[volume.pool][volume.vid]
  94. volume_data.domains += [(domain.name, name)]
  95. except KeyError:
  96. # Skipping volume
  97. continue
  98. except AttributeError:
  99. # Skipping domain without volumes
  100. continue
  101. if hasattr(args, 'domains') and args.domains:
  102. result = [x # reduce to only VolumeData with assigned domains
  103. for p in vd_dict.values() for x in p.values()
  104. if x.domains]
  105. else:
  106. result = [x for p in vd_dict.values() for x in p.values()]
  107. qubesadmin.tools.print_table(
  108. prepare_table(result, full=getattr(args, 'full', False)))
  109. def revert_volume(args):
  110. ''' Revert volume to previous state '''
  111. volume = args.volume
  112. if args.revision:
  113. revision = args.revision
  114. else:
  115. revisions = volume.revisions
  116. if not revisions:
  117. raise qubesadmin.exc.StoragePoolException(
  118. 'No snapshots available')
  119. revision = volume.revisions[-1]
  120. volume.revert(revision)
  121. def extend_volumes(args):
  122. ''' Called by the parser to execute the :program:`qvm-block extend`
  123. subcommand
  124. '''
  125. volume = args.volume
  126. size = qubesadmin.utils.parse_size(args.size)
  127. volume.resize(size)
  128. def init_list_parser(sub_parsers):
  129. ''' Configures the parser for the :program:`qvm-block list` subcommand '''
  130. # pylint: disable=protected-access
  131. list_parser = sub_parsers.add_parser('list', aliases=('ls', 'l'),
  132. help='list storage volumes')
  133. list_parser.add_argument('-p', '--pool', dest='pools',
  134. action=qubesadmin.tools.PoolsAction)
  135. list_parser.add_argument(
  136. '--full', action='store_true',
  137. help='print full line for each POOL_NAME:VOLUME_ID & vm combination')
  138. vm_name_group = qubesadmin.tools.VmNameGroup(
  139. list_parser, required=False, vm_action=qubesadmin.tools.VmNameAction,
  140. help='list volumes from specified domain(s)')
  141. list_parser._mutually_exclusive_groups.append(vm_name_group)
  142. list_parser.set_defaults(func=list_volumes)
  143. def init_revert_parser(sub_parsers):
  144. ''' Add 'revert' action related options '''
  145. revert_parser = sub_parsers.add_parser(
  146. 'revert', aliases=('rv', 'r'),
  147. help='revert volume to previous revision')
  148. revert_parser.add_argument(metavar='VM:VOLUME', dest='volume',
  149. action=qubesadmin.tools.VMVolumeAction)
  150. revert_parser.add_argument(metavar='REVISION', dest='revision',
  151. help='Optional revision to revert to;'
  152. 'if not specified, latest one is assumed',
  153. action='store', nargs='?')
  154. revert_parser.set_defaults(func=revert_volume)
  155. def init_extend_parser(sub_parsers):
  156. ''' Add 'extend' action related options '''
  157. extend_parser = sub_parsers.add_parser(
  158. "extend", help="extend volume from domain")
  159. extend_parser.add_argument(metavar='VM:VOLUME', dest='volume',
  160. action=qubesadmin.tools.VMVolumeAction)
  161. extend_parser.add_argument('size', help='New size in bytes')
  162. extend_parser.set_defaults(func=extend_volumes)
  163. def get_parser():
  164. '''Create :py:class:`argparse.ArgumentParser` suitable for
  165. :program:`qvm-block`.
  166. '''
  167. parser = qubesadmin.tools.QubesArgumentParser(description=__doc__,
  168. want_app=True)
  169. parser.register('action', 'parsers',
  170. qubesadmin.tools.AliasedSubParsersAction)
  171. sub_parsers = parser.add_subparsers(
  172. title='commands',
  173. description="For more information see qvm-block command -h",
  174. dest='command')
  175. init_extend_parser(sub_parsers)
  176. init_list_parser(sub_parsers)
  177. init_revert_parser(sub_parsers)
  178. # default action
  179. parser.set_defaults(func=list_volumes)
  180. return parser
  181. def main(args=None, app=None):
  182. '''Main routine of :program:`qvm-block`.'''
  183. parser = get_parser()
  184. try:
  185. args = parser.parse_args(args, app=app)
  186. args.func(args)
  187. except qubesadmin.exc.QubesException as e:
  188. parser.print_error(str(e))
  189. return 1
  190. return 0
  191. if __name__ == '__main__':
  192. sys.exit(main())