__init__.py 8.5 KB

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