__init__.py 10 KB

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