__init__.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU Lesser General Public License as published by
  10. # the Free Software Foundation; either version 2.1 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with this program; if not, see <http://www.gnu.org/licenses/>.
  20. '''Qubes VM objects.'''
  21. import logging
  22. import qubesadmin.base
  23. import qubesadmin.exc
  24. import qubesadmin.storage
  25. import qubesadmin.features
  26. import qubesadmin.devices
  27. import qubesadmin.firewall
  28. class QubesVM(qubesadmin.base.PropertyHolder):
  29. '''Qubes domain.'''
  30. log = None
  31. features = None
  32. devices = None
  33. firewall = None
  34. def __init__(self, app, name):
  35. super(QubesVM, self).__init__(app, 'mgmt.vm.property.', name)
  36. self._volumes = None
  37. self.log = logging.getLogger(name)
  38. self.features = qubesadmin.features.Features(self)
  39. self.devices = qubesadmin.devices.DeviceManager(self)
  40. self.firewall = qubesadmin.firewall.Firewall(self)
  41. @property
  42. def name(self):
  43. '''Domain name'''
  44. return self._method_dest
  45. @name.setter
  46. def name(self, new_value):
  47. self.qubesd_call(
  48. self._method_dest,
  49. self._method_prefix + 'Set',
  50. 'name',
  51. str(new_value).encode('utf-8'))
  52. self._method_dest = new_value
  53. self._volumes = None
  54. self.app.domains.clear_cache()
  55. def __str__(self):
  56. return self._method_dest
  57. def __lt__(self, other):
  58. if isinstance(other, QubesVM):
  59. return self.name < other.name
  60. return NotImplemented
  61. def start(self):
  62. '''
  63. Start domain.
  64. :return:
  65. '''
  66. self.qubesd_call(self._method_dest, 'mgmt.vm.Start')
  67. def shutdown(self, force=False):
  68. '''
  69. Shutdown domain.
  70. :return:
  71. '''
  72. # TODO: force parameter
  73. # TODO: wait parameter (using event?)
  74. if force:
  75. raise NotImplementedError
  76. self.qubesd_call(self._method_dest, 'mgmt.vm.Shutdown')
  77. def kill(self):
  78. '''
  79. Kill domain (forcefuly shutdown).
  80. :return:
  81. '''
  82. self.qubesd_call(self._method_dest, 'mgmt.vm.Kill')
  83. def pause(self):
  84. '''
  85. Pause domain.
  86. Pause its execution without any prior notification.
  87. :return:
  88. '''
  89. self.qubesd_call(self._method_dest, 'mgmt.vm.Pause')
  90. def unpause(self):
  91. '''
  92. Unpause domain.
  93. Opposite to :py:meth:`pause`.
  94. :return:
  95. '''
  96. self.qubesd_call(self._method_dest, 'mgmt.vm.Unpause')
  97. def suspend(self):
  98. '''
  99. Suspend domain.
  100. Give domain a chance to prepare for suspend - for example suspend
  101. used PCI devices.
  102. :return:
  103. '''
  104. raise NotImplementedError
  105. #self.qubesd_call(self._method_dest, 'mgmt.vm.Suspend')
  106. def resume(self):
  107. '''
  108. Resume domain.
  109. Opposite to :py:meth:`suspend`.
  110. :return:
  111. '''
  112. raise NotImplementedError
  113. #self.qubesd_call(self._method_dest, 'mgmt.vm.Resume')
  114. def get_power_state(self):
  115. '''Return power state description string.
  116. Return value may be one of those:
  117. =============== ========================================================
  118. return value meaning
  119. =============== ========================================================
  120. ``'Halted'`` Machine is not active.
  121. ``'Transient'`` Machine is running, but does not have :program:`guid`
  122. or :program:`qrexec` available.
  123. ``'Running'`` Machine is ready and running.
  124. ``'Paused'`` Machine is paused.
  125. ``'Suspended'`` Machine is S3-suspended.
  126. ``'Halting'`` Machine is in process of shutting down (OS shutdown).
  127. ``'Dying'`` Machine is in process of shutting down (cleanup).
  128. ``'Crashed'`` Machine crashed and is unusable.
  129. ``'NA'`` Machine is in unknown state.
  130. =============== ========================================================
  131. .. seealso::
  132. http://wiki.libvirt.org/page/VM_lifecycle
  133. Description of VM life cycle from the point of view of libvirt.
  134. https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainState
  135. Libvirt's enum describing precise state of a domain.
  136. '''
  137. vm_list_info = self.qubesd_call(
  138. self._method_dest, 'mgmt.vm.List', None, None).decode('ascii')
  139. # name class=... state=... other=...
  140. vm_state = vm_list_info.strip().partition('state=')[2].split(' ')[0]
  141. return vm_state
  142. def is_halted(self):
  143. ''' Check whether this domain's state is 'Halted'
  144. :returns: :py:obj:`True` if this domain is halted, \
  145. :py:obj:`False` otherwise.
  146. :rtype: bool
  147. '''
  148. return self.get_power_state() == 'Halted'
  149. def is_paused(self):
  150. '''Check whether this domain is paused.
  151. :returns: :py:obj:`True` if this domain is paused, \
  152. :py:obj:`False` otherwise.
  153. :rtype: bool
  154. '''
  155. return self.get_power_state() == 'Paused'
  156. def is_running(self):
  157. '''Check whether this domain is running.
  158. :returns: :py:obj:`True` if this domain is started, \
  159. :py:obj:`False` otherwise.
  160. :rtype: bool
  161. '''
  162. return self.get_power_state() != 'Halted'
  163. @property
  164. def volumes(self):
  165. '''VM disk volumes'''
  166. if self._volumes is None:
  167. volumes_list = self.qubesd_call(
  168. self._method_dest, 'mgmt.vm.volume.List')
  169. self._volumes = {}
  170. for volname in volumes_list.decode('ascii').splitlines():
  171. if not volname:
  172. continue
  173. self._volumes[volname] = qubesadmin.storage.Volume(self.app,
  174. vm=self.name, vm_name=volname)
  175. return self._volumes
  176. def run_service(self, service, **kwargs):
  177. '''Run service on this VM
  178. :param str service: service name
  179. :rtype: subprocess.Popen
  180. '''
  181. return self.app.run_service(self._method_dest, service, **kwargs)
  182. def run_service_for_stdio(self, service, input=None, **kwargs):
  183. '''Run a service, pass an optional input and return (stdout, stderr).
  184. Raises an exception if return code != 0.
  185. *args* and *kwargs* are passed verbatim to :py:meth:`run_service`.
  186. .. warning::
  187. There are some combinations if stdio-related *kwargs*, which are
  188. not filtered for problems originating between the keyboard and the
  189. chair.
  190. ''' # pylint: disable=redefined-builtin
  191. p = self.run_service(service, **kwargs)
  192. # this one is actually a tuple, but there is no need to unpack it
  193. stdouterr = p.communicate(input=input)
  194. if p.returncode:
  195. raise qubesadmin.exc.QubesVMError(self,
  196. 'service {!r} failed with retcode {!r}; '
  197. 'stdout={!r} stderr={!r}'.format(
  198. service, p.returncode, *stdouterr))
  199. return stdouterr
  200. @staticmethod
  201. def prepare_input_for_vmshell(command, input=None):
  202. '''Prepare shell input for the given command and optional (real) input
  203. ''' # pylint: disable=redefined-builtin
  204. if input is None:
  205. input = b''
  206. return b''.join((command.rstrip('\n').encode('utf-8'), b'\n', input))
  207. def run(self, command, input=None, **kwargs):
  208. '''Run a shell command inside the domain using qubes.VMShell qrexec.
  209. ''' # pylint: disable=redefined-builtin
  210. return self.run_service_for_stdio('qubes.VMShell',
  211. input=self.prepare_input_for_vmshell(command, input), **kwargs)
  212. # pylint: disable=abstract-method
  213. class AdminVM(QubesVM):
  214. '''Dom0'''
  215. pass
  216. class AppVM(QubesVM):
  217. '''Application VM'''
  218. pass
  219. class StandaloneVM(QubesVM):
  220. '''Standalone Application VM'''
  221. pass
  222. class TemplateVM(QubesVM):
  223. '''Template for AppVM'''
  224. pass
  225. class DispVM(QubesVM):
  226. '''Disposable VM'''
  227. pass