qubes.py 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753
  1. #!/usr/bin/python2.6
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. #
  21. #
  22. import sys
  23. import os
  24. import os.path
  25. import subprocess
  26. import xml.etree.ElementTree
  27. import xml.parsers.expat
  28. import fcntl
  29. import re
  30. import shutil
  31. from qmemman_client import QMemmanClient
  32. # Do not use XenAPI or create/read any VM files
  33. # This is for testing only!
  34. dry_run = False
  35. #dry_run = True
  36. if not dry_run:
  37. # Xen API
  38. import xmlrpclib
  39. from xen.xm import XenAPI
  40. from xen.xend import sxp
  41. qubes_guid_path = "/usr/bin/qubes_guid"
  42. qubes_base_dir = "/var/lib/qubes"
  43. qubes_appvms_dir = qubes_base_dir + "/appvms"
  44. qubes_templates_dir = qubes_base_dir + "/vm-templates"
  45. qubes_servicevms_dir = qubes_base_dir + "/servicevms"
  46. qubes_store_filename = qubes_base_dir + "/qubes.xml"
  47. qubes_max_qid = 254*254
  48. qubes_max_netid = 254
  49. vm_default_netmask = "255.255.0.0"
  50. default_root_img = "root.img"
  51. default_rootcow_img = "root-cow.img"
  52. default_swapcow_img = "swap-cow.img"
  53. default_private_img = "private.img"
  54. default_appvms_conf_file = "appvm-template.conf"
  55. default_templatevm_conf_template = "templatevm.conf" # needed for TemplateVM cloning
  56. default_appmenus_templates_subdir = "apps.templates"
  57. default_kernels_subdir = "kernels"
  58. # do not allow to start a new AppVM if Dom0 mem was to be less than this
  59. dom0_min_memory = 700*1024*1024
  60. # We need this global reference, as each instance of QubesVM
  61. # must be able to ask Dom0 VM about how much memory it currently has...
  62. dom0_vm = None
  63. qubes_appmenu_create_cmd = "/usr/lib/qubes/create_apps_for_appvm.sh"
  64. qubes_appmenu_remove_cmd = "/usr/lib/qubes/remove_appvm_appmenus.sh"
  65. # TODO: we should detect the actual size of the AppVM's swap partition
  66. # rather than using this ugly hardcoded value, which was choosen here
  67. # as "should be good for everyone"
  68. swap_cow_sz = 1024*1024*1024
  69. VM_TEMPLATE = 'TempleteVM'
  70. VM_APPVM = 'AppVM'
  71. VM_NETVM = 'NetVM'
  72. VM_DISPOSABLEVM = 'DisposableVM'
  73. class XendSession(object):
  74. def __init__(self):
  75. self.get_xend_session_old_api()
  76. self.get_xend_session_new_api()
  77. def get_xend_session_old_api(self):
  78. from xen.xend import XendClient
  79. from xen.util.xmlrpcclient import ServerProxy
  80. self.xend_server = ServerProxy(XendClient.uri)
  81. if self.xend_server is None:
  82. print "get_xend_session_old_api(): cannot open session!"
  83. def get_xend_session_new_api(self):
  84. xend_socket_uri = "httpu:///var/run/xend/xen-api.sock"
  85. self.session = XenAPI.Session (xend_socket_uri)
  86. self.session.login_with_password ("", "")
  87. if self.session is None:
  88. print "get_xend_session_new_api(): cannot open session!"
  89. if not dry_run:
  90. xend_session = XendSession()
  91. class QubesException (Exception) : pass
  92. class QubesHost(object):
  93. def __init__(self):
  94. self.hosts = xend_session.session.xenapi.host.get_all()
  95. self.host_record = xend_session.session.xenapi.host.get_record(self.hosts[0])
  96. self.host_metrics_record = xend_session.session.xenapi.host_metrics.get_record(self.host_record["metrics"])
  97. self.xen_total_mem = long(self.host_metrics_record["memory_total"])
  98. self.xen_no_cpus = len (self.host_record["host_CPUs"])
  99. # print "QubesHost: total_mem = {0}B".format (self.xen_total_mem)
  100. # print "QubesHost: free_mem = {0}".format (self.get_free_xen_memory())
  101. # print "QubesHost: total_cpus = {0}".format (self.xen_no_cpus)
  102. @property
  103. def memory_total(self):
  104. return self.xen_total_mem
  105. @property
  106. def no_cpus(self):
  107. return self.xen_no_cpus
  108. def get_free_xen_memory(self):
  109. ret = self.host_metrics_record["memory_free"]
  110. return long(ret)
  111. class QubesVmLabel(object):
  112. def __init__(self, name, index, color = None, icon = None):
  113. self.name = name
  114. self.index = index
  115. self.color = color if color is not None else name
  116. self.icon = icon if icon is not None else name
  117. self.icon_path = "/usr/share/qubes/icons/" + self.icon + ".png"
  118. # Globally defined lables
  119. QubesVmLabels = {
  120. "red" : QubesVmLabel ("red", 1),
  121. "orange" : QubesVmLabel ("orange", 2),
  122. "yellow" : QubesVmLabel ("yellow", 3),
  123. "green" : QubesVmLabel ("green", 4, color="0x5fa05e"),
  124. "gray" : QubesVmLabel ("gray", 5),
  125. "blue" : QubesVmLabel ("blue", 6),
  126. "purple" : QubesVmLabel ("purple", 7, color="0xb83374"),
  127. "black" : QubesVmLabel ("black", 8),
  128. }
  129. default_appvm_label = QubesVmLabels["red"]
  130. default_template_label = QubesVmLabels["gray"]
  131. default_servicevm_label = QubesVmLabels["red"]
  132. class QubesVm(object):
  133. """
  134. A representation of one Qubes VM
  135. Only persistent information are stored here, while all the runtime
  136. information, e.g. Xen dom id, etc, are to be retrieved via Xen API
  137. Note that qid is not the same as Xen's domid!
  138. """
  139. def __init__(self, qid, name, type,
  140. dir_path, conf_file = None,
  141. uses_default_netvm = True,
  142. netvm_vm = None,
  143. installed_by_rpm = False,
  144. updateable = False,
  145. label = None):
  146. assert qid < qubes_max_qid, "VM id out of bounds!"
  147. self.__qid = qid
  148. self.name = name
  149. dir_path = dir_path
  150. self.dir_path = dir_path
  151. conf_file = conf_file
  152. if self.dir_path is not None:
  153. if (conf_file is None):
  154. self.conf_file = dir_path + "/" + name + ".conf"
  155. else:
  156. if os.path.isabs(conf_file):
  157. self.conf_file = conf_file
  158. else:
  159. self.conf_file = dir_path + "/" + conf_file
  160. self.__type = type
  161. self.uses_default_netvm = uses_default_netvm
  162. self.netvm_vm = netvm_vm
  163. # We use it in remove from disk to avoid removing rpm files (for templates and netvms)
  164. self.installed_by_rpm = installed_by_rpm
  165. self.updateable = updateable
  166. self.label = label if label is not None else QubesVmLabels["red"]
  167. if self.dir_path is not None:
  168. self.icon_path = self.dir_path + "/icon.png"
  169. else:
  170. self.icon_path = None
  171. if not dry_run and xend_session.session is not None:
  172. self.refresh_xend_session()
  173. @property
  174. def qid(self):
  175. return self.__qid
  176. @property
  177. def type(self):
  178. return self.__type
  179. @property
  180. def ip(self):
  181. if self.netvm_vm is not None:
  182. return self.netvm_vm.get_ip_for_vm(self.qid)
  183. else:
  184. return None
  185. @property
  186. def netmask(self):
  187. if self.netvm_vm is not None:
  188. return self.netvm_vm.netmask
  189. else:
  190. return None
  191. @property
  192. def gateway(self):
  193. if self.netvm_vm is not None:
  194. return self.netvm_vm.gateway
  195. else:
  196. return None
  197. @property
  198. def secondary_dns(self):
  199. if self.netvm_vm is not None:
  200. return self.netvm_vm.secondary_dns
  201. else:
  202. return None
  203. def is_updateable(self):
  204. return self.updateable
  205. def is_networked(self):
  206. if self.is_netvm():
  207. return True
  208. if self.netvm_vm is not None:
  209. return True
  210. else:
  211. return False
  212. def set_nonupdateable(self):
  213. if not self.is_updateable():
  214. return
  215. assert not self.is_running()
  216. # We can always downgrade a VM to non-updateable...
  217. self.updateable = False
  218. def is_templete(self):
  219. if self.type == VM_TEMPLATE:
  220. return True
  221. else:
  222. return False
  223. def is_appvm(self):
  224. if self.type == VM_APPVM:
  225. return True
  226. else:
  227. return False
  228. def is_netvm(self):
  229. if self.type == VM_NETVM:
  230. return True
  231. else:
  232. return False
  233. def is_disposablevm(self):
  234. if self.type == VM_DISPOSABLEVM:
  235. return True
  236. else:
  237. return False
  238. def add_to_xen_storage(self):
  239. if dry_run:
  240. return
  241. retcode = subprocess.call (["/usr/sbin/xm", "new", "-q", self.conf_file])
  242. if retcode != 0:
  243. raise OSError ("Cannot add VM '{0}' to Xen Store!".format(self.name))
  244. return True
  245. def remove_from_xen_storage(self):
  246. if dry_run:
  247. return
  248. retcode = subprocess.call (["/usr/sbin/xm", "delete", self.name])
  249. if retcode != 0:
  250. raise OSError ("Cannot remove VM '{0}' from Xen Store!".format(self.name))
  251. self.in_xen_storage = False
  252. def refresh_xend_session(self):
  253. uuids = xend_session.session.xenapi.VM.get_by_name_label (self.name)
  254. self.session_uuid = uuids[0] if len (uuids) > 0 else None
  255. if self.session_uuid is not None:
  256. self.session_metrics = xend_session.session.xenapi.VM.get_metrics(self.session_uuid)
  257. else:
  258. self.session_metrics = None
  259. def update_xen_storage(self):
  260. self.remove_from_xen_storage()
  261. self.add_to_xen_storage()
  262. if not dry_run and xend_session.session is not None:
  263. self.refresh_xend_session()
  264. def get_xid(self):
  265. if dry_run:
  266. return 666
  267. try:
  268. xid = int (xend_session.session.xenapi.VM.get_domid (self.session_uuid))
  269. except XenAPI.Failure:
  270. self.refresh_xend_session()
  271. xid = int (xend_session.session.xenapi.VM.get_domid (self.session_uuid))
  272. return xid
  273. def get_mem(self):
  274. if dry_run:
  275. return 666
  276. try:
  277. mem = int (xend_session.session.xenapi.VM_metrics.get_memory_actual (self.session_metrics))
  278. except XenAPI.Failure:
  279. self.refresh_xend_session()
  280. mem = int (xend_session.session.xenapi.VM_metrics.get_memory_actual (self.session_metrics))
  281. return mem
  282. def get_mem_static_max(self):
  283. if dry_run:
  284. return 666
  285. try:
  286. mem = int(xend_session.session.xenapi.VM.get_memory_static_max(self.session_uuid))
  287. except XenAPI.Failure:
  288. self.refresh_xend_session()
  289. mem = int(xend_session.session.xenapi.VM.get_memory_static_max(self.session_uuid))
  290. return mem
  291. def get_mem_dynamic_max(self):
  292. if dry_run:
  293. return 666
  294. try:
  295. mem = int(xend_session.session.xenapi.VM.get_memory_dynamic_max(self.session_uuid))
  296. except XenAPI.Failure:
  297. self.refresh_xend_session()
  298. mem = int(xend_session.session.xenapi.VM.get_memory_dynamic_max(self.session_uuid))
  299. return mem
  300. def get_cpu_total_load(self):
  301. if dry_run:
  302. import random
  303. return random.random() * 100
  304. try:
  305. cpus_util = xend_session.session.xenapi.VM_metrics.get_VCPUs_utilisation (self.session_metrics)
  306. except XenAPI.Failure:
  307. self.refresh_xend_session()
  308. cpus_util = xend_session.session.xenapi.VM_metrics.get_VCPUs_utilisation (self.session_metrics)
  309. if len (cpus_util) == 0:
  310. return 0
  311. cpu_total_load = 0.0
  312. for cpu in cpus_util:
  313. cpu_total_load += cpus_util[cpu]
  314. cpu_total_load /= len(cpus_util)
  315. p = 100*cpu_total_load
  316. if p > 100:
  317. p = 100
  318. return p
  319. def get_power_state(self):
  320. if dry_run:
  321. return "NA"
  322. try:
  323. power_state = xend_session.session.xenapi.VM.get_power_state (self.session_uuid)
  324. except XenAPI.Failure:
  325. self.refresh_xend_session()
  326. if self.session_uuid is None:
  327. return "NA"
  328. power_state = xend_session.session.xenapi.VM.get_power_state (self.session_uuid)
  329. return power_state
  330. def is_running(self):
  331. if self.get_power_state() == "Running":
  332. return True
  333. else:
  334. return False
  335. def is_paused(self):
  336. if self.get_power_state() == "Paused":
  337. return True
  338. else:
  339. return False
  340. def get_disk_usage(self, file_or_dir):
  341. if not os.path.exists(file_or_dir):
  342. return 0
  343. p = subprocess.Popen (["du", "-s", "--block-size=1", file_or_dir],
  344. stdout=subprocess.PIPE)
  345. result = p.communicate()
  346. m = re.match(r"^(\d+)\s.*", result[0])
  347. sz = int(m.group(1)) if m is not None else 0
  348. return sz
  349. def get_disk_utilization(self):
  350. return self.get_disk_usage(self.dir_path)
  351. def get_disk_utilization_private_img(self):
  352. return self.get_disk_usage(self.private_img)
  353. def get_private_img_sz(self):
  354. if not os.path.exists(self.private_img):
  355. return 0
  356. return os.path.getsize(self.private_img)
  357. def create_xenstore_entries(self, xid):
  358. if dry_run:
  359. return
  360. # Set Xen Store entires with VM networking info:
  361. retcode = subprocess.check_call ([
  362. "/usr/bin/xenstore-write",
  363. "/local/domain/{0}/qubes_vm_type".format(xid),
  364. self.type])
  365. if self.is_netvm():
  366. retcode = subprocess.check_call ([
  367. "/usr/bin/xenstore-write",
  368. "/local/domain/{0}/qubes_netvm_gateway".format(xid),
  369. self.gateway])
  370. retcode = subprocess.check_call ([
  371. "/usr/bin/xenstore-write",
  372. "/local/domain/{0}/qubes_netvm_secondary_dns".format(xid),
  373. self.secondary_dns])
  374. retcode = subprocess.check_call ([
  375. "/usr/bin/xenstore-write",
  376. "/local/domain/{0}/qubes_netvm_netmask".format(xid),
  377. self.netmask])
  378. retcode = subprocess.check_call ([
  379. "/usr/bin/xenstore-write",
  380. "/local/domain/{0}/qubes_netvm_network".format(xid),
  381. self.network])
  382. elif self.netvm_vm is not None:
  383. retcode = subprocess.check_call ([
  384. "/usr/bin/xenstore-write",
  385. "/local/domain/{0}/qubes_ip".format(xid),
  386. self.ip])
  387. retcode = subprocess.check_call ([
  388. "/usr/bin/xenstore-write",
  389. "/local/domain/{0}/qubes_netmask".format(xid),
  390. self.netmask])
  391. retcode = subprocess.check_call ([
  392. "/usr/bin/xenstore-write",
  393. "/local/domain/{0}/qubes_gateway".format(xid),
  394. self.gateway])
  395. retcode = subprocess.check_call ([
  396. "/usr/bin/xenstore-write",
  397. "/local/domain/{0}/qubes_secondary_dns".format(xid),
  398. self.secondary_dns])
  399. else:
  400. pass
  401. def get_total_xen_memory(self):
  402. hosts = xend_session.session.xenapi.host.get_all()
  403. host_record = xend_session.session.xenapi.host.get_record(hosts[0])
  404. host_metrics_record = xend_session.session.xenapi.host_metrics.get_record(host_record["metrics"])
  405. ret = host_metrics_record["memory_total"]
  406. return long(ret)
  407. def start(self, debug_console = False, verbose = False, preparing_dvm = False):
  408. if dry_run:
  409. return
  410. if self.is_running():
  411. raise QubesException ("VM is already running!")
  412. if verbose:
  413. print "--> Rereading the VM's conf file ({0})...".format(self.conf_file)
  414. self.update_xen_storage()
  415. if verbose:
  416. print "--> Loading the VM (type = {0})...".format(self.type)
  417. if not self.is_netvm():
  418. total_mem_mb = self.get_total_xen_memory()/1024/1024
  419. xend_session.xend_server.xend.domain.maxmem_set(self.name, total_mem_mb)
  420. mem_required = self.get_mem_dynamic_max()
  421. qmemman_client = QMemmanClient()
  422. if not qmemman_client.request_memory(mem_required):
  423. qmemman_client.close()
  424. raise MemoryError ("ERROR: insufficient memory to start this VM")
  425. try:
  426. xend_session.session.xenapi.VM.start (self.session_uuid, True) # Starting a VM paused
  427. except XenAPI.Failure:
  428. self.refresh_xend_session()
  429. xend_session.session.xenapi.VM.start (self.session_uuid, True) # Starting a VM paused
  430. qmemman_client.close() # let qmemman_daemon resume balancing
  431. xid = int (xend_session.session.xenapi.VM.get_domid (self.session_uuid))
  432. if verbose:
  433. print "--> Setting Xen Store info for the VM..."
  434. self.create_xenstore_entries(xid)
  435. if not self.is_netvm() and self.netvm_vm is not None:
  436. assert self.netvm_vm is not None
  437. if verbose:
  438. print "--> Attaching to the network backend (netvm={0})...".format(self.netvm_vm.name)
  439. if preparing_dvm:
  440. actual_ip = "254.254.254.254"
  441. else:
  442. actual_ip = self.ip
  443. xm_cmdline = ["/usr/sbin/xm", "network-attach", self.name, "script=vif-route-qubes", "ip="+actual_ip]
  444. if self.netvm_vm.qid != 0:
  445. if not self.netvm_vm.is_running():
  446. print "ERROR: NetVM not running, please start it first"
  447. self.force_shutdown()
  448. raise QubesException ("NetVM not running")
  449. retcode = subprocess.call (xm_cmdline + ["backend={0}".format(self.netvm_vm.name)])
  450. if retcode != 0:
  451. self.force_shutdown()
  452. raise OSError ("ERROR: Cannot attach to network backend!")
  453. else:
  454. retcode = subprocess.call (xm_cmdline)
  455. if retcode != 0:
  456. self.force_shutdown()
  457. raise OSError ("ERROR: Cannot attach to network backend!")
  458. if verbose:
  459. print "--> Starting the VM..."
  460. xend_session.session.xenapi.VM.unpause (self.session_uuid)
  461. # perhaps we should move it before unpause and fork?
  462. if debug_console:
  463. from xen.xm import console
  464. if verbose:
  465. print "--> Starting debug console..."
  466. console.execConsole (xid)
  467. return xid
  468. def force_shutdown(self):
  469. if dry_run:
  470. return
  471. try:
  472. xend_session.session.xenapi.VM.hard_shutdown (self.session_uuid)
  473. except XenAPI.Failure:
  474. self.refresh_xend_session()
  475. xend_session.session.xenapi.VM.hard_shutdown (self.session_uuid)
  476. def remove_from_disk(self):
  477. if dry_run:
  478. return
  479. shutil.rmtree (self.dir_path)
  480. class QubesTemplateVm(QubesVm):
  481. """
  482. A class that represents an TemplateVM. A child of QubesVM.
  483. """
  484. def __init__(self, **kwargs):
  485. if "dir_path" not in kwargs or kwargs["dir_path"] is None:
  486. kwargs["dir_path"] = qubes_templates_dir + "/" + kwargs["name"]
  487. if "updateable" not in kwargs or kwargs["updateable"] is None :
  488. kwargs["updateable"] = True
  489. root_img = kwargs.pop("root_img") if "root_img" in kwargs else None
  490. private_img = kwargs.pop("private_img") if "private_img" in kwargs else None
  491. appvms_conf_file = kwargs.pop("appvms_conf_file") if "appvms_conf_file" in kwargs else None
  492. super(QubesTemplateVm, self).__init__(type=VM_TEMPLATE, label = default_template_label, **kwargs)
  493. dir_path = kwargs["dir_path"]
  494. if root_img is not None and os.path.isabs(root_img):
  495. self.root_img = root_img
  496. else:
  497. self.root_img = dir_path + "/" + (
  498. root_img if root_img is not None else default_root_img)
  499. self.rootcow_img = dir_path + "/" + default_rootcow_img
  500. if private_img is not None and os.path.isabs(private_img):
  501. self.private_img = private_img
  502. else:
  503. self.private_img = dir_path + "/" + (
  504. private_img if private_img is not None else default_private_img)
  505. if appvms_conf_file is not None and os.path.isabs(appvms_conf_file):
  506. self.appvms_conf_file = appvms_conf_file
  507. else:
  508. self.appvms_conf_file = dir_path + "/" + (
  509. appvms_conf_file if appvms_conf_file is not None else default_appvms_conf_file)
  510. self.templatevm_conf_template = self.dir_path + "/" + default_templatevm_conf_template
  511. self.kernels_dir = self.dir_path + "/" + default_kernels_subdir
  512. self.appmenus_templates_dir = self.dir_path + "/" + default_appmenus_templates_subdir
  513. self.appvms = QubesVmCollection()
  514. def set_updateable(self):
  515. if self.is_updateable():
  516. return
  517. assert not self.is_running()
  518. # Make sure that all the AppVMs are non-updateable...
  519. for appvm in self.appvms.values():
  520. if appvm.is_updateable():
  521. raise QubesException("One of the AppVMs ('{0}')is also 'updateable'\
  522. -- cannot make the TempleteVM {'{1}'} 'nonupdatable'".\
  523. format (appvm.name, self.name))
  524. self.updateable = True
  525. def clone_disk_files(self, src_template_vm, verbose):
  526. if dry_run:
  527. return
  528. assert not src_template_vm.is_running(), "Attempt to clone a running Template VM!"
  529. if verbose:
  530. print "--> Creating directory: {0}".format(self.dir_path)
  531. os.mkdir (self.dir_path)
  532. if verbose:
  533. print "--> Copying the VM config file:\n{0} =*>\n{1}".\
  534. format(src_template_vm.templatevm_conf_template, self.conf_file)
  535. conf_templatevm_template = open (src_template_vm.templatevm_conf_template, "r")
  536. conf_file = open(self.conf_file, "w")
  537. rx_templatename = re.compile (r"%TEMPLATENAME%")
  538. for line in conf_templatevm_template:
  539. line = rx_templatename.sub (self.name, line)
  540. conf_file.write(line)
  541. conf_templatevm_template.close()
  542. conf_file.close()
  543. if verbose:
  544. print "--> Copying the VM config template :\n{0} ==>\n{1}".\
  545. format(src_template_vm.templatevm_conf_template, self.templatevm_conf_template)
  546. shutil.copy (src_template_vm.templatevm_conf_template, self.templatevm_conf_template)
  547. if verbose:
  548. print "--> Copying the VM config template :\n{0} ==>\n{1}".\
  549. format(src_template_vm.appvms_conf_file, self.appvms_conf_file)
  550. shutil.copy (src_template_vm.appvms_conf_file, self.appvms_conf_file)
  551. if verbose:
  552. print "--> Copying the template's private image:\n{0} ==>\n{1}".\
  553. format(src_template_vm.private_img, self.private_img)
  554. # We prefer to use Linux's cp, because it nicely handles sparse files
  555. retcode = subprocess.call (["cp", src_template_vm.private_img, self.private_img])
  556. if retcode != 0:
  557. raise IOError ("Error while copying {0} to {1}".\
  558. format(src_template_vm.private_img, self.private_img))
  559. if verbose:
  560. print "--> Copying the template's root image:\n{0} ==>\n{1}".\
  561. format(src_template_vm.root_img, self.root_img)
  562. # We prefer to use Linux's cp, because it nicely handles sparse files
  563. retcode = subprocess.call (["cp", src_template_vm.root_img, self.root_img])
  564. if retcode != 0:
  565. raise IOError ("Error while copying {0} to {1}".\
  566. format(src_template_vm.root_img, self.root_img))
  567. if verbose:
  568. print "--> Copying the template's root COW image:\n{0} ==>\n{1}".\
  569. format(src_template_vm.rootcow_img, self.rootcow_img)
  570. # We prefer to use Linux's cp, because it nicely handles sparse files
  571. retcode = subprocess.call (["cp", src_template_vm.rootcow_img, self.rootcow_img])
  572. if retcode != 0:
  573. raise IOError ("Error while copying {0} to {1}".\
  574. format(src_template_vm.root_img, self.root_img))
  575. if verbose:
  576. print "--> Copying the template's kernel dir:\n{0} ==>\n{1}".\
  577. format(src_template_vm.kernels_dir, self.kernels_dir)
  578. shutil.copytree (src_template_vm.kernels_dir, self.kernels_dir)
  579. if verbose:
  580. print "--> Copying the template's appvm templates dir:\n{0} ==>\n{1}".\
  581. format(src_template_vm.appmenus_templates_dir, self.appmenus_templates_dir)
  582. shutil.copytree (src_template_vm.appmenus_templates_dir, self.appmenus_templates_dir)
  583. def get_disk_utilization_root_img(self):
  584. return self.get_disk_usage(self.root_img)
  585. def get_root_img_sz(self):
  586. if not os.path.exists(self.root_img):
  587. return 0
  588. return os.path.getsize(self.root_img)
  589. def verify_files(self):
  590. if dry_run:
  591. return
  592. if not os.path.exists (self.dir_path):
  593. raise QubesException (
  594. "VM directory doesn't exist: {0}".\
  595. format(self.dir_path))
  596. if not os.path.exists (self.conf_file):
  597. raise QubesException (
  598. "VM config file doesn't exist: {0}".\
  599. format(self.conf_file))
  600. if not os.path.exists (self.appvms_conf_file):
  601. raise QubesException (
  602. "Appvm template config file doesn't exist: {0}".\
  603. format(self.appvms_conf_file))
  604. if not os.path.exists (self.root_img):
  605. raise QubesException (
  606. "VM root image file doesn't exist: {0}".\
  607. format(self.root_img))
  608. if not os.path.exists (self.private_img):
  609. raise QubesException (
  610. "VM private image file doesn't exist: {0}".\
  611. format(self.private_img))
  612. if not os.path.exists (self.kernels_dir):
  613. raise QubesException (
  614. "VM's kernels directory does not exist: {0}".\
  615. format(self.kernels_dir))
  616. return True
  617. def start(self, debug_console = False, verbose = False):
  618. if dry_run:
  619. return
  620. if not self.is_updateable():
  621. raise QubesException ("Cannot start Template VM that is marked \"nonupdatable\"")
  622. # TODO?: check if none of running appvms are outdated
  623. return super(QubesTemplateVm, self).start(debug_console=debug_console, verbose=verbose)
  624. def commit_changes (self):
  625. assert not self.is_running(), "Attempt to commit changes on running Template VM!"
  626. print "--> Commiting template updates... COW: {0}...".format (self.rootcow_img)
  627. if dry_run:
  628. return
  629. if os.path.exists (self.rootcow_img):
  630. os.remove (self.rootcow_img)
  631. f_cow = open (self.rootcow_img, "w")
  632. f_root = open (self.root_img, "r")
  633. f_root.seek(0, os.SEEK_END)
  634. f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
  635. f_cow.close ()
  636. f_root.close()
  637. def create_xml_element(self):
  638. element = xml.etree.ElementTree.Element(
  639. "QubesTemplateVm",
  640. qid=str(self.qid),
  641. name=self.name,
  642. dir_path=self.dir_path,
  643. conf_file=self.conf_file,
  644. appvms_conf_file=self.appvms_conf_file,
  645. root_img=self.root_img,
  646. rootcow_img=self.rootcow_img,
  647. private_img=self.private_img,
  648. uses_default_netvm=str(self.uses_default_netvm),
  649. netvm_qid=str(self.netvm_vm.qid) if self.netvm_vm is not None else "none",
  650. installed_by_rpm=str(self.installed_by_rpm),
  651. updateable=str(self.updateable),
  652. )
  653. return element
  654. class QubesServiceVm(QubesVm):
  655. """
  656. A class that represents a ServiceVM, e.g. NetVM. A child of QubesVM.
  657. """
  658. def __init__(self, **kwargs):
  659. if "dir_path" not in kwargs or kwargs["dir_path"] is None:
  660. kwargs["dir_path"] = qubes_servicevms_dir + "/" + kwargs["name"]
  661. root_img = kwargs.pop("root_img") if "root_img" in kwargs else None
  662. private_img = kwargs.pop("private_img") if "private_img" in kwargs else None
  663. kwargs["updateable"] = True
  664. super(QubesServiceVm, self).__init__(**kwargs)
  665. dir_path = kwargs["dir_path"]
  666. assert dir_path is not None
  667. if root_img is not None and os.path.isabs(root_img):
  668. self.root_img = root_img
  669. else:
  670. self.root_img = dir_path + "/" + (
  671. root_img if root_img is not None else default_root_img)
  672. if private_img is not None and os.path.isabs(private_img):
  673. self.private_img = private_img
  674. else:
  675. self.private_img = dir_path + "/" + (
  676. private_img if private_img is not None else default_private_img)
  677. def set_updateable(self):
  678. if self.is_updateable():
  679. return
  680. # ServiceVMs are standalone, we can always make it updateable
  681. # In fact there is no point in making it unpdatebale...
  682. # So, this is just for completncess
  683. assert not self.is_running()
  684. self.updateable = True
  685. def get_disk_utilization_root_img(self):
  686. return self.get_disk_usage(self.root_img)
  687. def get_root_img_sz(self):
  688. if not os.path.exists(self.root_img):
  689. return 0
  690. return os.path.getsize(self.root_img)
  691. def verify_files(self):
  692. if dry_run:
  693. return
  694. if not os.path.exists (self.dir_path):
  695. raise QubesException (
  696. "VM directory doesn't exist: {0}".\
  697. format(self.dir_path))
  698. if not os.path.exists (self.conf_file):
  699. raise QubesException (
  700. "VM config file doesn't exist: {0}".\
  701. format(self.conf_file))
  702. if not os.path.exists (self.root_img):
  703. raise QubesException (
  704. "VM root image file doesn't exist: {0}".\
  705. format(self.root_img))
  706. return True
  707. def create_xml_element(self):
  708. raise NotImplementedError
  709. class QubesNetVm(QubesServiceVm):
  710. """
  711. A class that represents a NetVM. A child of QubesServiceVM.
  712. """
  713. def __init__(self, **kwargs):
  714. netid = kwargs.pop("netid")
  715. self.netid = netid
  716. self.__network = "10.{0}.0.0".format(netid)
  717. self.netprefix = "10.{0}.".format(netid)
  718. self.__netmask = vm_default_netmask
  719. self.__gateway = self.netprefix + "0.1"
  720. self.__secondary_dns = self.netprefix + "255.254"
  721. if "label" not in kwargs or kwargs["label"] is None:
  722. kwargs["label"] = default_servicevm_label
  723. super(QubesNetVm, self).__init__(type=VM_NETVM, installed_by_rpm=True, **kwargs)
  724. @property
  725. def gateway(self):
  726. return self.__gateway
  727. @property
  728. def secondary_dns(self):
  729. return self.__secondary_dns
  730. @property
  731. def netmask(self):
  732. return self.__netmask
  733. @property
  734. def network(self):
  735. return self.__network
  736. def get_ip_for_vm(self, qid):
  737. hi = qid / 253
  738. lo = qid % 253 + 2
  739. assert hi >= 0 and hi <= 254 and lo >= 2 and lo <= 254, "Wrong IP address for VM"
  740. return self.netprefix + "{0}.{1}".format(hi,lo)
  741. def create_xml_element(self):
  742. element = xml.etree.ElementTree.Element(
  743. "QubesNetVm",
  744. qid=str(self.qid),
  745. netid=str(self.netid),
  746. name=self.name,
  747. dir_path=self.dir_path,
  748. conf_file=self.conf_file,
  749. root_img=self.root_img,
  750. private_img=self.private_img,
  751. installed_by_rpm=str(self.installed_by_rpm),
  752. )
  753. return element
  754. class QubesDom0NetVm(QubesNetVm):
  755. def __init__(self):
  756. super(QubesDom0NetVm, self).__init__(qid=0, name="dom0", netid=0,
  757. dir_path=None, root_img = None,
  758. private_img = None,
  759. label = default_template_label)
  760. if not dry_run and xend_session.session is not None:
  761. self.session_hosts = xend_session.session.xenapi.host.get_all()
  762. self.session_cpus = xend_session.session.xenapi.host.get_host_CPUs(self.session_hosts[0])
  763. def is_running(self):
  764. return True
  765. def get_cpu_total_load(self):
  766. if dry_run:
  767. import random
  768. return random.random() * 100
  769. cpu_total_load = 0.0
  770. for cpu in self.session_cpus:
  771. cpu_total_load += xend_session.session.xenapi.host_cpu.get_utilisation(cpu)
  772. cpu_total_load /= len(self.session_cpus)
  773. p = 100*cpu_total_load
  774. if p > 100:
  775. p = 100
  776. return p
  777. def get_mem(self):
  778. # Unfortunately XenAPI provides only info about total memory, not the one actually usable by Dom0...
  779. #session = get_xend_session_new_api()
  780. #hosts = session.xenapi.host.get_all()
  781. #metrics = session.xenapi.host.get_metrics(hosts[0])
  782. #memory_total = int(session.xenapi.metrics.get_memory_total(metrics))
  783. # ... so we must read /proc/meminfo, just like free command does
  784. f = open ("/proc/meminfo")
  785. for line in f:
  786. match = re.match(r"^MemTotal\:\s*(\d+) kB", line)
  787. if match is not None:
  788. break
  789. f.close()
  790. assert match is not None
  791. return int(match.group(1))*1024
  792. def get_xid(self):
  793. return 0
  794. def get_power_state(self):
  795. return "Running"
  796. def get_disk_usage(self, file_or_dir):
  797. return 0
  798. def get_disk_utilization(self):
  799. return 0
  800. def get_disk_utilization_private_img(self):
  801. return 0
  802. def get_private_img_sz(self):
  803. return 0
  804. def start(self, debug_console = False, verbose = False):
  805. raise QubesException ("Cannot start Dom0 fake domain!")
  806. def create_xml_element(self):
  807. return None
  808. def verify_files(self):
  809. return True
  810. class QubesDisposableVm(QubesVm):
  811. """
  812. A class that represents an DisposableVM. A child of QubesVM.
  813. """
  814. def __init__(self, **kwargs):
  815. template_vm = kwargs.pop("template_vm")
  816. super(QubesDisposableVm, self).__init__(type=VM_DISPOSABLEVM, dir_path=None, **kwargs)
  817. qid = kwargs["qid"]
  818. assert template_vm is not None, "Missing template_vm for DisposableVM!"
  819. if not template_vm.is_templete():
  820. print "ERROR: template_qid={0} doesn't point to a valid TempleteVM".\
  821. format(new_vm.template_vm.qid)
  822. return False
  823. self.template_vm = template_vm
  824. template_vm.appvms[qid] = self
  825. def create_xml_element(self):
  826. element = xml.etree.ElementTree.Element(
  827. "QubesDisposableVm",
  828. qid=str(self.qid),
  829. name=self.name,
  830. template_qid=str(self.template_vm.qid),
  831. label=self.label.name)
  832. return element
  833. def verify_files(self):
  834. return True
  835. class QubesAppVm(QubesVm):
  836. """
  837. A class that represents an AppVM. A child of QubesVM.
  838. """
  839. def __init__(self, **kwargs):
  840. if "dir_path" not in kwargs or kwargs["dir_path"] is None:
  841. kwargs["dir_path"] = qubes_appvms_dir + "/" + kwargs["name"]
  842. if "updateable" not in kwargs or kwargs["updateable"] is None:
  843. kwargs["updateable"] = False
  844. private_img = kwargs.pop("private_img")
  845. template_vm = kwargs.pop("template_vm")
  846. super(QubesAppVm, self).__init__(type=VM_APPVM, **kwargs)
  847. qid = kwargs["qid"]
  848. dir_path = kwargs["dir_path"]
  849. assert template_vm is not None, "Missing template_vm for AppVM!"
  850. if not template_vm.is_templete():
  851. print "ERROR: template_qid={0} doesn't point to a valid TempleteVM".\
  852. format(new_vm.template_vm.qid)
  853. return False
  854. self.template_vm = template_vm
  855. template_vm.appvms[qid] = self
  856. # AppVM doesn't have its own root_img, it uses the one provided by the TemplateVM
  857. if private_img is not None and os.path.isabs(private_img):
  858. self.private_img = private_img
  859. else:
  860. self.private_img = dir_path + "/" + (
  861. private_img if private_img is not None else default_private_img)
  862. self.rootcow_img = dir_path + "/" + default_rootcow_img
  863. self.swapcow_img = dir_path + "/" + default_swapcow_img
  864. def set_updateable(self):
  865. if self.is_updateable():
  866. return
  867. assert not self.is_running()
  868. # Check if the TemaplteVM is *non* updatable...
  869. if not self.template_vm.is_updateable():
  870. self.updateable = True
  871. self.reset_cow_storage()
  872. self.reset_swap_cow_storage()
  873. else:
  874. # Temaplate VM is Updatable itself --> can't make the AppVM updateable too
  875. # as this would cause COW-backed storage incoherency
  876. raise QubesException ("TemaplteVM is updateable: cannot make the AppVM '{0}' updateable".format(self.name))
  877. def create_config_file(self):
  878. conf_template = open (self.template_vm.appvms_conf_file, "r")
  879. if os.path.isfile(self.conf_file):
  880. shutil.copy(self.conf_file, self.conf_file + ".backup")
  881. conf_appvm = open(self.conf_file, "w")
  882. rx_vmname = re.compile (r"%VMNAME%")
  883. rx_vmdir = re.compile (r"%VMDIR%")
  884. rx_template = re.compile (r"%TEMPLATEDIR%")
  885. for line in conf_template:
  886. line = rx_vmname.sub (self.name, line)
  887. line = rx_vmdir.sub (self.dir_path, line)
  888. line = rx_template.sub (self.template_vm.dir_path, line)
  889. conf_appvm.write(line)
  890. conf_template.close()
  891. conf_appvm.close()
  892. def create_on_disk(self, verbose):
  893. if dry_run:
  894. return
  895. if verbose:
  896. print "--> Creating directory: {0}".format(self.dir_path)
  897. os.mkdir (self.dir_path)
  898. if verbose:
  899. print "--> Creating the VM config file: {0}".format(self.conf_file)
  900. self.create_config_file()
  901. template_priv = self.template_vm.private_img
  902. if verbose:
  903. print "--> Copying the template's private image: {0}".\
  904. format(template_priv)
  905. # We prefer to use Linux's cp, because it nicely handles sparse files
  906. retcode = subprocess.call (["cp", template_priv, self.private_img])
  907. if retcode != 0:
  908. raise IOError ("Error while copying {0} to {1}".\
  909. format(template_priv, self.private_img))
  910. if verbose:
  911. print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
  912. os.symlink (self.label.icon_path, self.icon_path)
  913. self.create_appmenus (verbose)
  914. def create_appmenus(self, verbose):
  915. subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name])
  916. def get_disk_utilization_root_img(self):
  917. return 0
  918. def get_root_img_sz(self):
  919. return 0
  920. def verify_files(self):
  921. if dry_run:
  922. return
  923. if not os.path.exists (self.dir_path):
  924. raise QubesException (
  925. "VM directory doesn't exist: {0}".\
  926. format(self.dir_path))
  927. if not os.path.exists (self.conf_file):
  928. raise QubesException (
  929. "VM config file doesn't exist: {0}".\
  930. format(self.conf_file))
  931. if not os.path.exists (self.private_img):
  932. raise QubesException (
  933. "VM private image file doesn't exist: {0}".\
  934. format(self.private_img))
  935. return True
  936. def create_xml_element(self):
  937. element = xml.etree.ElementTree.Element(
  938. "QubesAppVm",
  939. qid=str(self.qid),
  940. name=self.name,
  941. dir_path=self.dir_path,
  942. conf_file=self.conf_file,
  943. template_qid=str(self.template_vm.qid),
  944. uses_default_netvm=str(self.uses_default_netvm),
  945. netvm_qid=str(self.netvm_vm.qid) if self.netvm_vm is not None else "none",
  946. private_img=self.private_img,
  947. installed_by_rpm=str(self.installed_by_rpm),
  948. updateable=str(self.updateable),
  949. label=self.label.name)
  950. return element
  951. def start(self, debug_console = False, verbose = False, preparing_dvm = False):
  952. if dry_run:
  953. return
  954. if self.is_running():
  955. raise QubesException("VM is already running!")
  956. if not self.is_updateable():
  957. self.reset_cow_storage()
  958. self.reset_swap_cow_storage()
  959. return super(QubesAppVm, self).start(debug_console=debug_console, verbose=verbose, preparing_dvm=preparing_dvm)
  960. def reset_cow_storage (self):
  961. print "--> Resetting the COW storage: {0}...".format (self.rootcow_img)
  962. if dry_run:
  963. return
  964. # this is probbaly not needed, as open (..., "w") should remove the previous file
  965. if os.path.exists (self.rootcow_img):
  966. os.remove (self.rootcow_img)
  967. f_cow = open (self.rootcow_img, "w")
  968. f_root = open (self.template_vm.root_img, "r")
  969. f_root.seek(0, os.SEEK_END)
  970. f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
  971. f_cow.close ()
  972. f_root.close()
  973. def reset_swap_cow_storage (self):
  974. print "--> Resetting the swap COW storage: {0}...".format (self.swapcow_img)
  975. if os.path.exists (self.swapcow_img):
  976. os.remove (self.swapcow_img)
  977. f_swap_cow = open (self.swapcow_img, "w")
  978. f_swap_cow.truncate (swap_cow_sz)
  979. f_swap_cow.close()
  980. def remove_from_disk(self):
  981. if dry_run:
  982. return
  983. subprocess.check_call ([qubes_appmenu_remove_cmd, self.name])
  984. shutil.rmtree (self.dir_path)
  985. class QubesVmCollection(dict):
  986. """
  987. A collection of Qubes VMs indexed by Qubes id (qid)
  988. """
  989. def __init__(self, store_filename=qubes_store_filename):
  990. super(QubesVmCollection, self).__init__()
  991. self.default_netvm_qid = None
  992. self.default_template_qid = None
  993. self.qubes_store_filename = store_filename
  994. def values(self):
  995. for qid in self.keys():
  996. yield self[qid]
  997. def items(self):
  998. for qid in self.keys():
  999. yield (qid, self[qid])
  1000. def __iter__(self):
  1001. for qid in sorted(super(QubesVmCollection, self).keys()):
  1002. yield qid
  1003. keys = __iter__
  1004. def __setitem__(self, key, value):
  1005. if key not in self:
  1006. return super(QubesVmCollection, self).__setitem__(key, value)
  1007. else:
  1008. assert False, "Attempt to add VM with qid that already exists in the collection!"
  1009. def add_new_appvm(self, name, template_vm,
  1010. dir_path = None, conf_file = None,
  1011. private_img = None,
  1012. label = None):
  1013. qid = self.get_new_unused_qid()
  1014. vm = QubesAppVm (qid=qid, name=name, template_vm=template_vm,
  1015. dir_path=dir_path, conf_file=conf_file,
  1016. private_img=private_img,
  1017. netvm_vm = self.get_default_netvm_vm(),
  1018. label=label)
  1019. if not self.verify_new_vm (vm):
  1020. assert False, "Wrong VM description!"
  1021. self[vm.qid]=vm
  1022. return vm
  1023. def add_new_disposablevm(self, name, template_vm,
  1024. label = None):
  1025. qid = self.get_new_unused_qid()
  1026. vm = QubesDisposableVm (qid=qid, name=name, template_vm=template_vm,
  1027. netvm_vm = self.get_default_netvm_vm(),
  1028. label=label)
  1029. if not self.verify_new_vm (vm):
  1030. assert False, "Wrong VM description!"
  1031. self[vm.qid]=vm
  1032. return vm
  1033. def add_new_templatevm(self, name,
  1034. dir_path = None, conf_file = None,
  1035. root_img = None, private_img = None,
  1036. installed_by_rpm = True):
  1037. qid = self.get_new_unused_qid()
  1038. vm = QubesTemplateVm (qid=qid, name=name,
  1039. dir_path=dir_path, conf_file=conf_file,
  1040. root_img=root_img, private_img=private_img,
  1041. installed_by_rpm=installed_by_rpm,
  1042. netvm_vm = self.get_default_netvm_vm())
  1043. if not self.verify_new_vm (vm):
  1044. assert False, "Wrong VM description!"
  1045. self[vm.qid]=vm
  1046. if self.default_template_qid is None:
  1047. self.set_default_template_vm(vm)
  1048. return vm
  1049. def clone_templatevm(self, src_template_vm, name, dir_path = None, verbose = False):
  1050. assert not src_template_vm.is_running(), "Attempt to clone a running Template VM!"
  1051. vm = self.add_new_templatevm (name=name, dir_path=dir_path, installed_by_rpm = False)
  1052. return vm
  1053. def add_new_netvm(self, name,
  1054. dir_path = None, conf_file = None,
  1055. root_img = None):
  1056. qid = self.get_new_unused_qid()
  1057. netid = self.get_new_unused_netid()
  1058. vm = QubesNetVm (qid=qid, name=name,
  1059. netid=netid,
  1060. dir_path=dir_path, conf_file=conf_file,
  1061. root_img=root_img)
  1062. if not self.verify_new_vm (vm):
  1063. assert False, "Wrong VM description!"
  1064. self[vm.qid]=vm
  1065. if self.default_netvm_qid is None:
  1066. self.set_default_netvm_vm(vm)
  1067. return vm
  1068. def set_default_template_vm(self, vm):
  1069. assert vm.is_templete(), "VM {0} is not a TempleteVM!".format(vm.name)
  1070. self.default_template_qid = vm.qid
  1071. def get_default_template_vm(self):
  1072. if self.default_template_qid is None:
  1073. return None
  1074. else:
  1075. return self[self.default_template_qid]
  1076. def set_default_netvm_vm(self, vm):
  1077. assert vm.is_netvm(), "VM {0} is not a NetVM!".format(vm.name)
  1078. self.default_netvm_qid = vm.qid
  1079. def get_default_netvm_vm(self):
  1080. if self.default_netvm_qid is None:
  1081. return None
  1082. else:
  1083. return self[self.default_netvm_qid]
  1084. def get_vm_by_name(self, name):
  1085. for vm in self.values():
  1086. if (vm.name == name):
  1087. return vm
  1088. return None
  1089. def get_qid_by_name(self, name):
  1090. vm = self.get_vm_by_name(name)
  1091. return vm.qid if vm is not None else None
  1092. def get_vms_based_on(self, template_qid):
  1093. vms = set([vm for vm in self.values()
  1094. if (vm.is_appvm() and vm.template_vm.qid == template_qid)])
  1095. return vms
  1096. def verify_new_vm(self, new_vm):
  1097. # Verify that qid is unique
  1098. for vm in self.values():
  1099. if vm.qid == new_vm.qid:
  1100. print "ERROR: The qid={0} is already used by VM '{1}'!".\
  1101. format(vm.qid, vm.name)
  1102. return False
  1103. # Verify that name is unique
  1104. for vm in self.values():
  1105. if vm.name == new_vm.name:
  1106. print "ERROR: The name={0} is already used by other VM with qid='{1}'!".\
  1107. format(vm.name, vm.qid)
  1108. return False
  1109. return True
  1110. def get_new_unused_qid(self):
  1111. used_ids = set([vm.qid for vm in self.values()])
  1112. for id in range (1, qubes_max_qid):
  1113. if id not in used_ids:
  1114. return id
  1115. raise LookupError ("Cannot find unused qid!")
  1116. def get_new_unused_netid(self):
  1117. used_ids = set([vm.netid for vm in self.values() if vm.is_netvm()])
  1118. for id in range (1, qubes_max_netid):
  1119. if id not in used_ids:
  1120. return id
  1121. raise LookupError ("Cannot find unused netid!")
  1122. def check_if_storage_exists(self):
  1123. try:
  1124. f = open (self.qubes_store_filename, 'r')
  1125. except IOError:
  1126. return False
  1127. f.close()
  1128. return True
  1129. def create_empty_storage(self):
  1130. self.qubes_store_file = open (self.qubes_store_filename, 'w')
  1131. self.clear()
  1132. self.save()
  1133. def lock_db_for_reading(self):
  1134. self.qubes_store_file = open (self.qubes_store_filename, 'r')
  1135. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_SH)
  1136. def lock_db_for_writing(self):
  1137. self.qubes_store_file = open (self.qubes_store_filename, 'r+')
  1138. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX)
  1139. def unlock_db(self):
  1140. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_UN)
  1141. self.qubes_store_file.close()
  1142. def save(self):
  1143. root = xml.etree.ElementTree.Element(
  1144. "QubesVmCollection",
  1145. default_template=str(self.default_template_qid) \
  1146. if self.default_template_qid is not None else "None",
  1147. default_netvm=str(self.default_netvm_qid) \
  1148. if self.default_netvm_qid is not None else "None"
  1149. )
  1150. for vm in self.values():
  1151. element = vm.create_xml_element()
  1152. if element is not None:
  1153. root.append(element)
  1154. tree = xml.etree.ElementTree.ElementTree(root)
  1155. try:
  1156. # We need to manually truncate the file, as we open the
  1157. # file as "r+" in the lock_db_for_writing() function
  1158. self.qubes_store_file.seek (0, os.SEEK_SET)
  1159. self.qubes_store_file.truncate()
  1160. tree.write(self.qubes_store_file, "UTF-8")
  1161. except EnvironmentError as err:
  1162. print("{0}: import error: {1}".format(
  1163. os.path.basename(sys.argv[0]), err))
  1164. return False
  1165. return True
  1166. def load(self):
  1167. self.clear()
  1168. dom0vm = QubesDom0NetVm ()
  1169. self[dom0vm.qid] = dom0vm
  1170. self.default_netvm_qid = 0
  1171. global dom0_vm
  1172. dom0_vm = dom0vm
  1173. try:
  1174. tree = xml.etree.ElementTree.parse(self.qubes_store_file)
  1175. except (EnvironmentError,
  1176. xml.parsers.expat.ExpatError) as err:
  1177. print("{0}: import error: {1}".format(
  1178. os.path.basename(sys.argv[0]), err))
  1179. return False
  1180. element = tree.getroot()
  1181. default_template = element.get("default_template")
  1182. self.default_template_qid = int(default_template) \
  1183. if default_template != "None" else None
  1184. default_netvm = element.get("default_netvm")
  1185. if default_netvm is not None:
  1186. self.default_netvm_qid = int(default_netvm) \
  1187. if default_netvm != "None" else None
  1188. #assert self.default_netvm_qid is not None
  1189. # Read in the NetVMs first, because a reference to NetVM
  1190. # is needed to create all other VMs
  1191. for element in tree.findall("QubesNetVm"):
  1192. try:
  1193. kwargs = {}
  1194. attr_list = ("qid", "netid", "name", "dir_path", "conf_file",
  1195. "private_img", "root_img",
  1196. )
  1197. for attribute in attr_list:
  1198. kwargs[attribute] = element.get(attribute)
  1199. kwargs["qid"] = int(kwargs["qid"])
  1200. kwargs["netid"] = int(kwargs["netid"])
  1201. vm = QubesNetVm(**kwargs)
  1202. self[vm.qid] = vm
  1203. except (ValueError, LookupError) as err:
  1204. print("{0}: import error (QubesNetVM) {1}".format(
  1205. os.path.basename(sys.argv[0]), err))
  1206. return False
  1207. self.default_template_qid
  1208. # Then, read in the TemplateVMs, because a reference to templete VM
  1209. # is needed to create each AppVM
  1210. for element in tree.findall("QubesTemplateVm"):
  1211. try:
  1212. kwargs = {}
  1213. attr_list = ("qid", "name", "dir_path", "conf_file",
  1214. "appvms_conf_file", "private_img", "root_img",
  1215. "installed_by_rpm", "updateable",
  1216. "uses_default_netvm", "netvm_qid")
  1217. for attribute in attr_list:
  1218. kwargs[attribute] = element.get(attribute)
  1219. kwargs["qid"] = int(kwargs["qid"])
  1220. kwargs["installed_by_rpm"] = True if kwargs["installed_by_rpm"] == "True" else False
  1221. if kwargs["updateable"] is not None:
  1222. kwargs["updateable"] = True if kwargs["updateable"] == "True" else False
  1223. if "uses_default_netvm" not in kwargs:
  1224. kwargs["uses_default_netvm"] = True
  1225. else:
  1226. kwargs["uses_default_netvm"] = True if kwargs["uses_default_netvm"] == "True" else False
  1227. if kwargs["uses_default_netvm"] is True:
  1228. netvm_vm = self.get_default_netvm_vm()
  1229. kwargs.pop("netvm_qid")
  1230. else:
  1231. if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None:
  1232. netvm_vm = None
  1233. kwargs.pop("netvm_qid")
  1234. else:
  1235. netvm_qid = int(kwargs.pop("netvm_qid"))
  1236. if netvm_qid not in self:
  1237. netvm_vm = None
  1238. else:
  1239. netvm_vm = self[netvm_qid]
  1240. kwargs["netvm_vm"] = netvm_vm
  1241. vm = QubesTemplateVm(**kwargs)
  1242. self[vm.qid] = vm
  1243. except (ValueError, LookupError) as err:
  1244. print("{0}: import error (QubesTemplateVm): {1}".format(
  1245. os.path.basename(sys.argv[0]), err))
  1246. return False
  1247. # Finally, read in the AppVMs
  1248. for element in tree.findall("QubesAppVm"):
  1249. try:
  1250. kwargs = {}
  1251. attr_list = ("qid", "name", "dir_path", "conf_file",
  1252. "private_img", "template_qid",
  1253. "updateable", "label", "netvm_qid",
  1254. "uses_default_netvm")
  1255. for attribute in attr_list:
  1256. kwargs[attribute] = element.get(attribute)
  1257. kwargs["qid"] = int(kwargs["qid"])
  1258. kwargs["template_qid"] = int(kwargs["template_qid"])
  1259. if kwargs["updateable"] is not None:
  1260. kwargs["updateable"] = True if kwargs["updateable"] == "True" else False
  1261. template_vm = self[kwargs.pop("template_qid")]
  1262. if template_vm is None:
  1263. print "ERROR: AppVM '{0}' uses unkown template qid='{1}'!".\
  1264. format(kwargs["name"], kwargs["template_qid"])
  1265. kwargs["template_vm"] = template_vm
  1266. if "uses_default_netvm" not in kwargs:
  1267. kwargs["uses_default_netvm"] = True
  1268. else:
  1269. kwargs["uses_default_netvm"] = True if kwargs["uses_default_netvm"] == "True" else False
  1270. if kwargs["uses_default_netvm"] is True:
  1271. netvm_vm = self.get_default_netvm_vm()
  1272. kwargs.pop("netvm_qid")
  1273. else:
  1274. if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None:
  1275. netvm_vm = None
  1276. kwargs.pop("netvm_qid")
  1277. else:
  1278. netvm_qid = int(kwargs.pop("netvm_qid"))
  1279. if netvm_qid not in self:
  1280. netvm_vm = None
  1281. else:
  1282. netvm_vm = self[netvm_qid]
  1283. kwargs["netvm_vm"] = netvm_vm
  1284. if kwargs["label"] is not None:
  1285. if kwargs["label"] not in QubesVmLabels:
  1286. print "ERROR: incorrect label for VM '{0}'".format(kwargs["name"])
  1287. kwargs.pop ("label")
  1288. else:
  1289. kwargs["label"] = QubesVmLabels[kwargs["label"]]
  1290. vm = QubesAppVm(**kwargs)
  1291. self[vm.qid] = vm
  1292. except (ValueError, LookupError) as err:
  1293. print("{0}: import error (QubesAppVm): {1}".format(
  1294. os.path.basename(sys.argv[0]), err))
  1295. return False
  1296. # Really finally, read in the DisposableVMs
  1297. for element in tree.findall("QubesDisposableVm"):
  1298. try:
  1299. kwargs = {}
  1300. attr_list = ("qid", "name",
  1301. "template_qid",
  1302. "label")
  1303. for attribute in attr_list:
  1304. kwargs[attribute] = element.get(attribute)
  1305. kwargs["qid"] = int(kwargs["qid"])
  1306. kwargs["template_qid"] = int(kwargs["template_qid"])
  1307. template_vm = self[kwargs.pop("template_qid")]
  1308. if template_vm is None:
  1309. print "ERROR: DisposableVM '{0}' uses unkown template qid='{1}'!".\
  1310. format(kwargs["name"], kwargs["template_qid"])
  1311. kwargs["template_vm"] = template_vm
  1312. kwargs["netvm_vm"] = self.get_default_netvm_vm()
  1313. if kwargs["label"] is not None:
  1314. if kwargs["label"] not in QubesVmLabels:
  1315. print "ERROR: incorrect label for VM '{0}'".format(kwargs["name"])
  1316. kwargs.pop ("label")
  1317. else:
  1318. kwargs["label"] = QubesVmLabels[kwargs["label"]]
  1319. vm = QubesDisposableVm(**kwargs)
  1320. self[vm.qid] = vm
  1321. except (ValueError, LookupError) as err:
  1322. print("{0}: import error (DisposableAppVm): {1}".format(
  1323. os.path.basename(sys.argv[0]), err))
  1324. return False
  1325. return True
  1326. class QubesDaemonPidfile(object):
  1327. def __init__(self, name):
  1328. self.name = name
  1329. self.path = "/var/run/qubes/" + name + ".pid"
  1330. def create_pidfile(self):
  1331. f = open (self.path, 'w')
  1332. f.write(str(os.getpid()))
  1333. f.close()
  1334. def pidfile_exists(self):
  1335. return os.path.exists(self.path)
  1336. def read_pid(self):
  1337. f = open (self.path)
  1338. pid = f.read ().strip()
  1339. f.close()
  1340. return int(pid)
  1341. def pidfile_is_stale(self):
  1342. if not self.pidfile_exists():
  1343. return False
  1344. # check if the pid file is valid...
  1345. proc_path = "/proc/" + str(self.read_pid()) + "/cmdline"
  1346. if not os.path.exists (proc_path):
  1347. print "Path {0} doesn't exist, assuming stale pidfile.".format(proc_path)
  1348. return True
  1349. f = open (proc_path)
  1350. cmdline = f.read ()
  1351. f.close()
  1352. # The following doesn't work with python -- one would have to get argv[1] and compare it with self.name...
  1353. # if not cmdline.strip().endswith(self.name):
  1354. # print "{0} = {1} doesn't seem to point to our process ({2}), assuming stale pidile.".format(proc_path, cmdline, self.name)
  1355. # return True
  1356. return False # It's a good pidfile
  1357. def remove_pidfile(self):
  1358. os.remove (self.path)
  1359. def __enter__ (self):
  1360. # assumes the pidfile doesn't exist -- you should ensure it before opening the context
  1361. self.create_pidfile()
  1362. def __exit__ (self):
  1363. self.remove_pidfile()
  1364. # vim:sw=4:et: