qmemman_server.py 8.9 KB

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