__init__.py 9.8 KB

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