labcalcoloctl 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. #!/usr/bin/python
  2. ##Author: Elisa Aliverti
  3. ##Last edit: May 2019 - nicolopalazzini
  4. from time import time
  5. start = time()
  6. import argparse
  7. import textwrap
  8. import os
  9. import subprocess, sys
  10. from threading import Thread
  11. from getpass import getpass
  12. choices = ('status','start','stop', 'doctor')
  13. parser = argparse.ArgumentParser(description="Simple tool to handle LabCalcolo's VMs.", usage='%(prog)s {'+','.join(choices)+'} [--options]')
  14. ## positional arguments
  15. parser.add_argument( 'cmd', nargs="?", choices=choices, default='status',
  16. help='Specify command to execute, default is \'status\'' )
  17. ## optional arguments
  18. parser.add_argument( '-a', '--all', action='store_true', dest='lcm',
  19. help='All LCM nodes are considered' )
  20. parser.add_argument( '-n', nargs='+', dest='node',
  21. help='Select one or more nodes (at least one)' )
  22. parser.add_argument( '-1', '--lcm1', action='store_true', dest='lcm1',
  23. help='LCM1 nodes are considered' )
  24. parser.add_argument( '-2', '--lcm2', action='store_true', dest='lcm2',
  25. help='LCM2 nodes are considered' )
  26. parser.add_argument( '-v', '--version', action='version', version='%(prog)s 1.4',
  27. help='Print program version' )
  28. ##
  29. args = parser.parse_args()
  30. def print_progressbar(index, num) :
  31. sys.stdout.write('\r ['
  32. + '='*index
  33. + '>'*(1-int(index/num))
  34. + ' '*(num-index-1) + ']')
  35. sys.stdout.flush()
  36. ## main path (you don't say)
  37. main_path = "/var/etc/vmctl"
  38. class Host(Thread):
  39. # Constructor
  40. def __init__(self, name, location):
  41. # Fork the thread first thing
  42. Thread.__init__(self)
  43. # Variables initialization
  44. self.hostname = name
  45. self.location = location
  46. self.running = False
  47. self.up = False
  48. # Run method called on Thread start. Check if host is up and if is running a VM
  49. def run(self):
  50. if self.isup() :
  51. self.up = True
  52. self.running=self.vmstatus()
  53. # Ping the host to see if it's up
  54. def isup(self):
  55. # Is the host up?
  56. ping = os.popen("ping -w1 -c1 " + self.hostname, "r")
  57. # print("pinging " self.hostname)
  58. if "0 received" in ping.read():
  59. return False
  60. else:
  61. return True
  62. def vmstatus(self):
  63. statuscmd = "ps aux | grep qemu | grep -v grep"
  64. ssh = self.sshcommand(statuscmd)
  65. result = [ l for l in ssh.stdout.readlines() if 'qemu' in l ]
  66. if result == []:
  67. return False
  68. else:
  69. return True
  70. def sshcommand(self, command):
  71. if self.up:
  72. ssh = subprocess.Popen( ["ssh", "%s" % self.hostname, command], shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE )
  73. return ssh
  74. else:
  75. print self.hostname + ' is not up.'
  76. return False
  77. def vmstart(self):
  78. if self.up:
  79. if not self.running:
  80. startcmd = main_path + " 1"
  81. self.sshcommand(startcmd)
  82. print 'VM is now starting on ' + self.hostname
  83. else:
  84. print 'VM is already running on ' + self.hostname
  85. else:
  86. print self.hostname + ' is not up.'
  87. def vmstop(self):
  88. if self.up:
  89. if self.running:
  90. stopcmd = main_path + " 0"
  91. self.sshcommand(stopcmd)
  92. print 'VM stopped on ' + self.hostname
  93. else:
  94. print 'VM is not running on ' + self.hostname
  95. else:
  96. print self.hostname + ' is not up.'
  97. def vmdoctor(self) :
  98. to_search=['qemu', 'spicec']
  99. # It's ugly (and more or less useless), but it should not be necessary: passwords should not be visible from ps aux
  100. pw_remove=['s/,password=\w*$//', 's/-w \w*$//']
  101. # Build the status query with programs names and relative pw remove strings
  102. status_query="ps aux | grep -E '" + '|'.join(to_search) + "' | grep -v grep | sed '" + ';'.join(pw_remove)+ "'"
  103. if self.up:
  104. ssh = self.sshcommand(status_query).stdout.readlines()
  105. qemu_status = [ l for l in ssh if 'qemu' in l ] # Filter for a single command
  106. if len(qemu_status) :
  107. print "Qemu command running on", self.hostname+":\n", qemu_status
  108. else :
  109. print "Qemu is not running on", self.hostname
  110. spicec_status = [ l for l in ssh if 'spicec' in l ]
  111. if not len(spicec_status) and len(qemu_status) :
  112. print "Spicec is not running on", self.hostname,
  113. if raw_input("Do you want to start it now? [y/n] ")=="y" :
  114. pw=getpass("Vm password: ")
  115. spiceccmd="export DISPLAY=:4 ; spicec -f -h 127.0.0.1 -p 5900 -w "+pw+" &"
  116. self.sshcommand(spiceccmd) # How to check if everything went as expected?
  117. # Check if everything works fine now
  118. spicec_status = [ l for l in self.sshcommand(status_query).stdout.readlines() if 'spicec' in l ]
  119. if len(spicec_status) :
  120. print "Spicec command running on", self.hostname+": ", spicec_status
  121. ### end class Host
  122. ## Host list
  123. Hosts = [
  124. Host('abe', 'LCM1'),
  125. Host('crash', 'LCM1'),
  126. Host('duke', 'LCM1'),
  127. Host('glados', 'LCM1'),
  128. Host('lara', 'LCM1'),
  129. Host('link', 'LCM1'),
  130. Host('king', 'LCM1'),
  131. Host('pang', 'LCM1'),
  132. Host('pong', 'LCM1'),
  133. Host('snake', 'LCM1'),
  134. Host('sonic', 'LCM1'),
  135. Host('spyro', 'LCM1'),
  136. Host('yoshi', 'LCM1'),
  137. Host('actarus', 'LCM2'),
  138. Host('elwood', 'LCM2'),
  139. Host('gex', 'LCM2'),
  140. Host('gin', 'LCM2'),
  141. Host('jake', 'LCM2'),
  142. Host('kirk', 'LCM2'),
  143. Host('martini', 'LCM2'),
  144. Host('picard', 'LCM2'),
  145. Host('q', 'LCM2'),
  146. Host('raziel', 'LCM2'),
  147. Host('sarek', 'LCM2'),
  148. Host('spock', 'LCM2'),
  149. Host('tron', 'LCM2'),
  150. Host('worf', 'LCM2'),
  151. Host('zombie', 'LCM2')
  152. ]
  153. nodes = []
  154. # Show usage if no arguments
  155. if len(sys.argv) < 2:
  156. parser.print_usage()
  157. print "\nSimple tool to handle LabCalcolo's VMs."
  158. print '\nTry: "labcalcoloctl --help" to display help message.'
  159. sys.exit(1)
  160. # Filter hostlist according to arguments
  161. if args.lcm:
  162. nodes = Hosts
  163. elif args.lcm1:
  164. nodes = [ host for host in Hosts if host.location == 'LCM1' ]
  165. elif args.lcm2:
  166. nodes = [ host for host in Hosts if host.location == 'LCM2' ]
  167. elif args.node:
  168. nodes = [ j for j in Hosts if j.hostname in args.node ]
  169. # Start the threads and run commands on nodes
  170. for n in nodes :
  171. n.start()
  172. running=[]
  173. down=[]
  174. num=len(nodes)
  175. index=0
  176. print ' Querying ' + str(num) + ' hosts...'
  177. for i in nodes:
  178. # Rejoin them when their work is done
  179. i.join()
  180. if i.running:
  181. running.append(i.hostname)
  182. if not i.up :
  183. down.append(i.hostname)
  184. index += 1
  185. print_progressbar(index, num)
  186. # New line after progress bar
  187. print '\n Done... (%(t).3f s)' % {'t': (time() - start)}
  188. if args.cmd == 'status':
  189. if len(running):
  190. print "VM(s) running on:"
  191. for i in running : print '\t',i
  192. else :
  193. print "No VMs are running"
  194. if len(down):
  195. print "Down nodes:"
  196. for i in down : print '\t',i
  197. #########################################################################################################
  198. ## Since lcm2 nodes have no VMs installed a control is added to make sure operator knows what he's doing.
  199. ## When future (or present?) admins will fix the issue this part should be deleted.
  200. elif args.cmd == 'start':
  201. yes = {'yes','y','ye'}
  202. no = {'no','n'}
  203. control = True
  204. parsed = False
  205. for n in nodes:
  206. if n.location == 'LCM2' and parsed == False :
  207. print 'You are trying to start a VM on lcm2 nodes. Are you sure you want to continue? [yes/no]'
  208. while True:
  209. choice = raw_input().lower()
  210. if choice in yes:
  211. parsed = True
  212. break
  213. elif choice in no:
  214. control = False
  215. parsed = True
  216. break
  217. else:
  218. sys.stdout.write("Be kind, respond with 'yes' or 'no'")
  219. if control == True:
  220. for i in nodes: i.vmstart()
  221. #########################################################################################################
  222. ## Uncomment this part when the lcm2 VMs issue will be fixed.
  223. #elif args.cmd == 'start':
  224. # for i in nodes: i.vmstart()
  225. #########################################################################################################
  226. elif args.cmd == 'stop':
  227. for i in nodes: i.vmstop()
  228. elif args.cmd == 'doctor' :
  229. for i in nodes : i.vmdoctor()