qmemmand.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. #!/usr/bin/python2
  2. # -*- coding: utf-8 -*-
  3. # pylint: skip-file
  4. #
  5. # The Qubes OS Project, http://www.qubes-os.org
  6. #
  7. # Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. #
  23. #
  24. import ConfigParser
  25. import SocketServer
  26. import logging
  27. import logging.handlers
  28. import os
  29. import socket
  30. import sys
  31. import thread
  32. import xen.lowlevel.xs
  33. import qubes.qmemman
  34. import qubes.qmemman.algo
  35. import qubes.utils
  36. SOCK_PATH = '/var/run/qubes/qmemman.sock'
  37. LOG_PATH = '/var/log/qubes/qmemman.log'
  38. system_state = qubes.qmemman.SystemState()
  39. global_lock = thread.allocate_lock()
  40. # If XS_Watcher will
  41. # handle meminfo event before @introduceDomain, it will use
  42. # incomplete domain list for that and may redistribute memory
  43. # allocated to some VM, but not yet used (see #1389).
  44. # To fix that, system_state should be updated (refresh domain
  45. # list) before processing other changes, every time some process requested
  46. # memory for a new VM, before releasing the lock. Then XS_Watcher will check
  47. # this flag before processing other event.
  48. force_refresh_domain_list = False
  49. def only_in_first_list(l1, l2):
  50. ret = []
  51. for i in l1:
  52. if not i in l2:
  53. ret.append(i)
  54. return ret
  55. def get_domain_meminfo_key(domain_id):
  56. return '/local/domain/'+domain_id+'/memory/meminfo'
  57. class WatchType(object):
  58. def __init__(self, fn, param):
  59. self.fn = fn
  60. self.param = param
  61. class XS_Watcher(object):
  62. def __init__(self):
  63. self.log = logging.getLogger('qmemman.daemon.xswatcher')
  64. self.log.debug('XS_Watcher()')
  65. self.handle = xen.lowlevel.xs.xs()
  66. self.handle.watch('@introduceDomain', WatchType(
  67. XS_Watcher.domain_list_changed, False))
  68. self.handle.watch('@releaseDomain', WatchType(
  69. XS_Watcher.domain_list_changed, False))
  70. self.watch_token_dict = {}
  71. def domain_list_changed(self, refresh_only=False):
  72. """
  73. Check if any domain was created/destroyed. If it was, update
  74. appropriate list. Then redistribute memory.
  75. :param refresh_only If True, only refresh domain list, do not
  76. redistribute memory. In this mode, caller must already hold
  77. global_lock.
  78. """
  79. self.log.debug('domain_list_changed(only_refresh={!r})'.format(
  80. refresh_only))
  81. got_lock = False
  82. if not refresh_only:
  83. self.log.debug('acquiring global_lock')
  84. global_lock.acquire()
  85. got_lock = True
  86. self.log.debug('global_lock acquired')
  87. try:
  88. curr = self.handle.ls('', '/local/domain')
  89. if curr is None:
  90. return
  91. # check if domain is really there, it may happen that some empty
  92. # directories are left in xenstore
  93. curr = filter(
  94. lambda x:
  95. self.handle.read('',
  96. '/local/domain/{}/domid'.format(x)
  97. ) is not None,
  98. curr
  99. )
  100. self.log.debug('curr={!r}'.format(curr))
  101. for i in only_in_first_list(curr, self.watch_token_dict.keys()):
  102. # new domain has been created
  103. watch = WatchType(XS_Watcher.meminfo_changed, i)
  104. self.watch_token_dict[i] = watch
  105. self.handle.watch(get_domain_meminfo_key(i), watch)
  106. system_state.add_domain(i)
  107. for i in only_in_first_list(self.watch_token_dict.keys(), curr):
  108. # domain destroyed
  109. self.handle.unwatch(get_domain_meminfo_key(i), self.watch_token_dict[i])
  110. self.watch_token_dict.pop(i)
  111. system_state.del_domain(i)
  112. finally:
  113. if got_lock:
  114. global_lock.release()
  115. self.log.debug('global_lock released')
  116. if not refresh_only:
  117. system_state.do_balance()
  118. def meminfo_changed(self, domain_id):
  119. self.log.debug('meminfo_changed(domain_id={!r})'.format(domain_id))
  120. untrusted_meminfo_key = self.handle.read(
  121. '', get_domain_meminfo_key(domain_id))
  122. if untrusted_meminfo_key == None or untrusted_meminfo_key == '':
  123. return
  124. self.log.debug('acquiring global_lock')
  125. global_lock.acquire()
  126. self.log.debug('global_lock acquired')
  127. if force_refresh_domain_list:
  128. self.domain_list_changed(refresh_only=True)
  129. system_state.refresh_meminfo(domain_id, untrusted_meminfo_key)
  130. global_lock.release()
  131. self.log.debug('global_lock released')
  132. def watch_loop(self):
  133. self.log.debug('watch_loop()')
  134. while True:
  135. result = self.handle.read_watch()
  136. self.log.debug('watch_loop result={!r}'.format(result))
  137. token = result[1]
  138. token.fn(self, token.param)
  139. class QMemmanReqHandler(SocketServer.BaseRequestHandler):
  140. """
  141. The RequestHandler class for our server.
  142. It is instantiated once per connection to the server, and must
  143. override the handle() method to implement communication to the
  144. client.
  145. """
  146. def handle(self):
  147. self.log = logging.getLogger('qmemman.daemon.reqhandler')
  148. got_lock = False
  149. # self.request is the TCP socket connected to the client
  150. while True:
  151. self.data = self.request.recv(1024).strip()
  152. self.log.debug('data={!r}'.format(self.data))
  153. if len(self.data) == 0:
  154. self.log.info('EOF')
  155. if got_lock:
  156. global force_refresh_domain_list
  157. force_refresh_domain_list = True
  158. global_lock.release()
  159. self.log.debug('global_lock released')
  160. return
  161. # XXX something is wrong here: return without release?
  162. if got_lock:
  163. self.log.warning('Second request over qmemman.sock?')
  164. return
  165. self.log.debug('acquiring global_lock')
  166. global_lock.acquire()
  167. self.log.debug('global_lock acquired')
  168. got_lock = True
  169. if system_state.do_balloon(int(self.data)):
  170. resp = "OK\n"
  171. else:
  172. resp = "FAIL\n"
  173. self.log.debug('resp={!r}'.format(resp))
  174. self.request.send(resp)
  175. # XXX no release of lock?
  176. parser = qubes.tools.QubesArgumentParser(want_app=False)
  177. parser.add_argument('--config', '-c', metavar='FILE',
  178. action='store', default='/etc/qubes/qmemman.conf',
  179. help='qmemman config file')
  180. parser.add_argument('--foreground',
  181. action='store_true', default=False,
  182. help='do not close stdio')
  183. def main():
  184. args = parser.parse_args()
  185. # setup logging
  186. ha_syslog = logging.handlers.SysLogHandler('/dev/log')
  187. ha_syslog.setFormatter(
  188. logging.Formatter('%(name)s[%(process)d]: %(message)s'))
  189. logging.root.addHandler(ha_syslog)
  190. # leave log for backwards compatibility
  191. ha_file = logging.FileHandler(LOG_PATH)
  192. ha_file.setFormatter(
  193. logging.Formatter('%(asctime)s %(name)s[%(process)d]: %(message)s'))
  194. logging.root.addHandler(ha_file)
  195. if args.foreground:
  196. ha_stderr = logging.StreamHandler(sys.stderr)
  197. ha_file.setFormatter(
  198. logging.Formatter('%(asctime)s %(name)s[%(process)d]: %(message)s'))
  199. logging.root.addHandler(ha_stderr)
  200. else:
  201. # close io
  202. sys.stdout.close()
  203. sys.stderr.close()
  204. sys.stdin.close()
  205. logging.root.setLevel(parser.get_loglevel_from_verbosity(args))
  206. log = logging.getLogger('qmemman.daemon')
  207. config = ConfigParser.SafeConfigParser({
  208. 'vm-min-mem': str(qubes.qmemman.algo.MIN_PREFMEM),
  209. 'dom0-mem-boost': str(qubes.qmemman.algo.DOM0_MEM_BOOST),
  210. 'cache-margin-factor': str(qubes.qmemman.algo.CACHE_FACTOR)
  211. })
  212. config.read(args.config)
  213. if config.has_section('global'):
  214. qubes.qmemman.algo.MIN_PREFMEM = \
  215. qubes.utils.parse_size(config.get('global', 'vm-min-mem'))
  216. qubes.qmemman.algo.DOM0_MEM_BOOST = \
  217. qubes.utils.parse_size(config.get('global', 'dom0-mem-boost'))
  218. qubes.qmemman.algo.CACHE_FACTOR = \
  219. config.getfloat('global', 'cache-margin-factor')
  220. log.info('MIN_PREFMEM={algo.MIN_PREFMEM}'
  221. ' DOM0_MEM_BOOST={algo.DOM0_MEM_BOOST}'
  222. ' CACHE_FACTOR={algo.CACHE_FACTOR}'.format(
  223. algo=qubes.qmemman.algo))
  224. try:
  225. os.unlink(SOCK_PATH)
  226. except:
  227. pass
  228. log.debug('instantiating server')
  229. os.umask(0)
  230. server = SocketServer.UnixStreamServer(SOCK_PATH, QMemmanReqHandler)
  231. os.umask(077)
  232. # notify systemd
  233. nofity_socket = os.getenv('NOTIFY_SOCKET')
  234. if nofity_socket:
  235. log.debug('notifying systemd')
  236. s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
  237. if nofity_socket.startswith('@'):
  238. nofity_socket = '\0%s' % nofity_socket[1:]
  239. s.connect(nofity_socket)
  240. s.sendall("READY=1")
  241. s.close()
  242. thread.start_new_thread(server.serve_forever, ())
  243. XS_Watcher().watch_loop()