qmemmand.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. def only_in_first_list(l1, l2):
  41. ret = []
  42. for i in l1:
  43. if not i in l2:
  44. ret.append(i)
  45. return ret
  46. def get_domain_meminfo_key(domain_id):
  47. return '/local/domain/'+domain_id+'/memory/meminfo'
  48. class WatchType(object):
  49. def __init__(self, fn, param):
  50. self.fn = fn
  51. self.param = param
  52. class XS_Watcher(object):
  53. def __init__(self):
  54. self.log = logging.getLogger('qmemman.daemon.xswatcher')
  55. self.log.debug('XS_Watcher()')
  56. self.handle = xen.lowlevel.xs.xs()
  57. self.handle.watch('@introduceDomain', WatchType(XS_Watcher.domain_list_changed, None))
  58. self.handle.watch('@releaseDomain', WatchType(XS_Watcher.domain_list_changed, None))
  59. self.watch_token_dict = {}
  60. def domain_list_changed(self, param):
  61. self.log.debug('domain_list_changed(param={!r})'.format(param))
  62. curr = self.handle.ls('', '/local/domain')
  63. self.log.debug('curr={!r}'.format(curr))
  64. if curr == None:
  65. return
  66. self.log.debug('acquiring global_lock')
  67. global_lock.acquire()
  68. self.log.debug('global_lock acquired')
  69. for i in only_in_first_list(curr, self.watch_token_dict.keys()):
  70. #new domain has been created
  71. watch = WatchType(XS_Watcher.meminfo_changed, i)
  72. self.watch_token_dict[i] = watch
  73. self.handle.watch(get_domain_meminfo_key(i), watch)
  74. system_state.add_domain(i)
  75. for i in only_in_first_list(self.watch_token_dict.keys(), curr):
  76. #domain destroyed
  77. self.handle.unwatch(get_domain_meminfo_key(i), self.watch_token_dict[i])
  78. self.watch_token_dict.pop(i)
  79. system_state.del_domain(i)
  80. global_lock.release()
  81. self.log.debug('global_lock released')
  82. system_state.do_balance()
  83. def meminfo_changed(self, domain_id):
  84. self.log.debug('meminfo_changed(domain_id={!r})'.format(domain_id))
  85. untrusted_meminfo_key = self.handle.read(
  86. '', get_domain_meminfo_key(domain_id))
  87. if untrusted_meminfo_key == None or untrusted_meminfo_key == '':
  88. return
  89. self.log.debug('acquiring global_lock')
  90. global_lock.acquire()
  91. self.log.debug('global_lock acquired')
  92. system_state.refresh_meminfo(domain_id, untrusted_meminfo_key)
  93. global_lock.release()
  94. self.log.debug('global_lock released')
  95. def watch_loop(self):
  96. self.log.debug('watch_loop()')
  97. while True:
  98. result = self.handle.read_watch()
  99. self.log.debug('watch_loop result={!r}'.format(result))
  100. token = result[1]
  101. token.fn(self, token.param)
  102. class QMemmanReqHandler(SocketServer.BaseRequestHandler):
  103. """
  104. The RequestHandler class for our server.
  105. It is instantiated once per connection to the server, and must
  106. override the handle() method to implement communication to the
  107. client.
  108. """
  109. def handle(self):
  110. self.log = logging.getLogger('qmemman.daemon.reqhandler')
  111. got_lock = False
  112. # self.request is the TCP socket connected to the client
  113. while True:
  114. self.data = self.request.recv(1024).strip()
  115. self.log.debug('data={!r}'.format(self.data))
  116. if len(self.data) == 0:
  117. self.log.info('EOF')
  118. if got_lock:
  119. global_lock.release()
  120. self.log.debug('global_lock released')
  121. return
  122. # XXX something is wrong here: return without release?
  123. if got_lock:
  124. self.log.warning('Second request over qmemman.sock?')
  125. return
  126. self.log.debug('acquiring global_lock')
  127. global_lock.acquire()
  128. self.log.debug('global_lock acquired')
  129. got_lock = True
  130. if system_state.do_balloon(int(self.data)):
  131. resp = "OK\n"
  132. else:
  133. resp = "FAIL\n"
  134. self.log.debug('resp={!r}'.format(resp))
  135. self.request.send(resp)
  136. # XXX no release of lock?
  137. parser = qubes.tools.QubesArgumentParser(want_app=False)
  138. parser.add_argument('--config', '-c', metavar='FILE',
  139. action='store', default='/etc/qubes/qmemman.conf',
  140. help='qmemman config file')
  141. parser.add_argument('--foreground',
  142. action='store_true', default=False,
  143. help='do not close stdio')
  144. def main():
  145. args = parser.parse_args()
  146. # setup logging
  147. ha_syslog = logging.handlers.SysLogHandler('/dev/log')
  148. ha_syslog.setFormatter(
  149. logging.Formatter('%(name)s[%(process)d]: %(message)s'))
  150. logging.root.addHandler(ha_syslog)
  151. # leave log for backwards compatibility
  152. ha_file = logging.FileHandler(LOG_PATH)
  153. ha_file.setFormatter(
  154. logging.Formatter('%(asctime)s %(name)s[%(process)d]: %(message)s'))
  155. logging.root.addHandler(ha_file)
  156. if args.foreground:
  157. ha_stderr = logging.StreamHandler(sys.stderr)
  158. ha_file.setFormatter(
  159. logging.Formatter('%(asctime)s %(name)s[%(process)d]: %(message)s'))
  160. logging.root.addHandler(ha_stderr)
  161. else:
  162. # close io
  163. sys.stdout.close()
  164. sys.stderr.close()
  165. sys.stdin.close()
  166. logging.root.setLevel(parser.get_loglevel_from_verbosity(args))
  167. log = logging.getLogger('qmemman.daemon')
  168. config = ConfigParser.SafeConfigParser({
  169. 'vm-min-mem': str(qubes.qmemman.algo.MIN_PREFMEM),
  170. 'dom0-mem-boost': str(qubes.qmemman.algo.DOM0_MEM_BOOST),
  171. 'cache-margin-factor': str(qubes.qmemman.algo.CACHE_FACTOR)
  172. })
  173. config.read(args.config)
  174. if config.has_section('global'):
  175. qubes.qmemman.algo.MIN_PREFMEM = \
  176. qubes.utils.parse_size(config.get('global', 'vm-min-mem'))
  177. qubes.qmemman.algo.DOM0_MEM_BOOST = \
  178. qubes.utils.parse_size(config.get('global', 'dom0-mem-boost'))
  179. qubes.qmemman.algo.CACHE_FACTOR = \
  180. config.getfloat('global', 'cache-margin-factor')
  181. log.info('MIN_PREFMEM={algo.MIN_PREFMEM}'
  182. ' DOM0_MEM_BOOST={algo.DOM0_MEM_BOOST}'
  183. ' CACHE_FACTOR={algo.CACHE_FACTOR}'.format(
  184. algo=qubes.qmemman.algo))
  185. try:
  186. os.unlink(SOCK_PATH)
  187. except:
  188. pass
  189. log.debug('instantiating server')
  190. os.umask(0)
  191. server = SocketServer.UnixStreamServer(SOCK_PATH, QMemmanReqHandler)
  192. os.umask(077)
  193. # notify systemd
  194. nofity_socket = os.getenv('NOTIFY_SOCKET')
  195. if nofity_socket:
  196. log.debug('notifying systemd')
  197. s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
  198. if nofity_socket.startswith('@'):
  199. nofity_socket = '\0%s' % nofity_socket[1:]
  200. s.connect(nofity_socket)
  201. s.sendall("READY=1")
  202. s.close()
  203. thread.start_new_thread(server.serve_forever, ())
  204. XS_Watcher().watch_loop()