__init__.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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, 'admin.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 __eq__(self, other):
  62. if isinstance(other, QubesVM):
  63. return self.name == other.name
  64. elif isinstance(other, str):
  65. return self.name == other
  66. return NotImplemented
  67. def start(self):
  68. '''
  69. Start domain.
  70. :return:
  71. '''
  72. self.qubesd_call(self._method_dest, 'admin.vm.Start')
  73. def shutdown(self, force=False):
  74. '''
  75. Shutdown domain.
  76. :return:
  77. '''
  78. # TODO: force parameter
  79. # TODO: wait parameter (using event?)
  80. if force:
  81. raise NotImplementedError
  82. self.qubesd_call(self._method_dest, 'admin.vm.Shutdown')
  83. def kill(self):
  84. '''
  85. Kill domain (forcefuly shutdown).
  86. :return:
  87. '''
  88. self.qubesd_call(self._method_dest, 'admin.vm.Kill')
  89. def pause(self):
  90. '''
  91. Pause domain.
  92. Pause its execution without any prior notification.
  93. :return:
  94. '''
  95. self.qubesd_call(self._method_dest, 'admin.vm.Pause')
  96. def unpause(self):
  97. '''
  98. Unpause domain.
  99. Opposite to :py:meth:`pause`.
  100. :return:
  101. '''
  102. self.qubesd_call(self._method_dest, 'admin.vm.Unpause')
  103. def suspend(self):
  104. '''
  105. Suspend domain.
  106. Give domain a chance to prepare for suspend - for example suspend
  107. used PCI devices.
  108. :return:
  109. '''
  110. raise NotImplementedError
  111. #self.qubesd_call(self._method_dest, 'admin.vm.Suspend')
  112. def resume(self):
  113. '''
  114. Resume domain.
  115. Opposite to :py:meth:`suspend`.
  116. :return:
  117. '''
  118. raise NotImplementedError
  119. #self.qubesd_call(self._method_dest, 'admin.vm.Resume')
  120. def get_power_state(self):
  121. '''Return power state description string.
  122. Return value may be one of those:
  123. =============== ========================================================
  124. return value meaning
  125. =============== ========================================================
  126. ``'Halted'`` Machine is not active.
  127. ``'Transient'`` Machine is running, but does not have :program:`guid`
  128. or :program:`qrexec` available.
  129. ``'Running'`` Machine is ready and running.
  130. ``'Paused'`` Machine is paused.
  131. ``'Suspended'`` Machine is S3-suspended.
  132. ``'Halting'`` Machine is in process of shutting down (OS shutdown).
  133. ``'Dying'`` Machine is in process of shutting down (cleanup).
  134. ``'Crashed'`` Machine crashed and is unusable.
  135. ``'NA'`` Machine is in unknown state.
  136. =============== ========================================================
  137. .. seealso::
  138. http://wiki.libvirt.org/page/VM_lifecycle
  139. Description of VM life cycle from the point of view of libvirt.
  140. https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainState
  141. Libvirt's enum describing precise state of a domain.
  142. '''
  143. vm_list_info = self.qubesd_call(
  144. self._method_dest, 'admin.vm.List', None, None).decode('ascii')
  145. # name class=... state=... other=...
  146. vm_state = vm_list_info.strip().partition('state=')[2].split(' ')[0]
  147. return vm_state
  148. def is_halted(self):
  149. ''' Check whether this domain's state is 'Halted'
  150. :returns: :py:obj:`True` if this domain is halted, \
  151. :py:obj:`False` otherwise.
  152. :rtype: bool
  153. '''
  154. return self.get_power_state() == 'Halted'
  155. def is_paused(self):
  156. '''Check whether this domain is paused.
  157. :returns: :py:obj:`True` if this domain is paused, \
  158. :py:obj:`False` otherwise.
  159. :rtype: bool
  160. '''
  161. return self.get_power_state() == 'Paused'
  162. def is_running(self):
  163. '''Check whether this domain is running.
  164. :returns: :py:obj:`True` if this domain is started, \
  165. :py:obj:`False` otherwise.
  166. :rtype: bool
  167. '''
  168. return self.get_power_state() != 'Halted'
  169. def is_networked(self):
  170. '''Check whether this VM can reach network (firewall notwithstanding).
  171. :returns: :py:obj:`True` if is machine can reach network, \
  172. :py:obj:`False` otherwise.
  173. :rtype: bool
  174. '''
  175. if self.provides_network:
  176. return True
  177. return self.netvm is not None
  178. @property
  179. def volumes(self):
  180. '''VM disk volumes'''
  181. if self._volumes is None:
  182. volumes_list = self.qubesd_call(
  183. self._method_dest, 'admin.vm.volume.List')
  184. self._volumes = {}
  185. for volname in volumes_list.decode('ascii').splitlines():
  186. if not volname:
  187. continue
  188. self._volumes[volname] = qubesadmin.storage.Volume(self.app,
  189. vm=self.name, vm_name=volname)
  190. return self._volumes
  191. def run_service(self, service, **kwargs):
  192. '''Run service on this VM
  193. :param str service: service name
  194. :rtype: subprocess.Popen
  195. '''
  196. return self.app.run_service(self._method_dest, service, **kwargs)
  197. def run_service_for_stdio(self, service, input=None, **kwargs):
  198. '''Run a service, pass an optional input and return (stdout, stderr).
  199. Raises an exception if return code != 0.
  200. *args* and *kwargs* are passed verbatim to :py:meth:`run_service`.
  201. .. warning::
  202. There are some combinations if stdio-related *kwargs*, which are
  203. not filtered for problems originating between the keyboard and the
  204. chair.
  205. ''' # pylint: disable=redefined-builtin
  206. p = self.run_service(service, **kwargs)
  207. # this one is actually a tuple, but there is no need to unpack it
  208. stdouterr = p.communicate(input=input)
  209. if p.returncode:
  210. raise qubesadmin.exc.QubesVMError(
  211. 'VM {}: service {!r} failed with retcode {!r}; '
  212. 'stdout={!r} stderr={!r}'.format(self,
  213. service, p.returncode, *stdouterr))
  214. return stdouterr
  215. @staticmethod
  216. def prepare_input_for_vmshell(command, input=None):
  217. '''Prepare shell input for the given command and optional (real) input
  218. ''' # pylint: disable=redefined-builtin
  219. if input is None:
  220. input = b''
  221. return b''.join((command.rstrip('\n').encode('utf-8'),
  222. b'; exit\n', input))
  223. def run(self, command, input=None, **kwargs):
  224. '''Run a shell command inside the domain using qubes.VMShell qrexec.
  225. ''' # pylint: disable=redefined-builtin
  226. return self.run_service_for_stdio('qubes.VMShell',
  227. input=self.prepare_input_for_vmshell(command, input), **kwargs)
  228. # pylint: disable=abstract-method
  229. class AdminVM(QubesVM):
  230. '''Dom0'''
  231. pass
  232. class AppVM(QubesVM):
  233. '''Application VM'''
  234. pass
  235. class StandaloneVM(QubesVM):
  236. '''Standalone Application VM'''
  237. pass
  238. class TemplateVM(QubesVM):
  239. '''Template for AppVM'''
  240. @property
  241. def appvms(self):
  242. ''' Returns a generator containing all domains based on the current
  243. TemplateVM.
  244. '''
  245. for vm in self.app.domains:
  246. try:
  247. if vm.template == self:
  248. yield vm
  249. except AttributeError:
  250. pass
  251. class DispVM(QubesVM):
  252. '''Disposable VM'''
  253. pass