first stab at nannying a qemu booted node
[tests.git] / qaapi / qa / tests / boot_node.py
1 #!/usr/bin/python
2
3 import os
4 import sys
5 import signal
6 import time
7 import tempfile
8 import select
9 import base64
10 from Test import Test
11 from qa import utils
12
13 image_types = ['node-iso', 'node-usb', 'generic-iso', 'generic-usb']
14
15 def killqemu(pid):
16     try:
17         os.kill(pid, signal.SIGKILL)
18         os.waitpid(pid,os.WNOHANG)
19     except:
20         pass
21
22 class boot_node(Test):
23     """
24     Attempts to boot the specified node using qemu. 
25     """
26
27     def state_nanny(self):
28         hostname = self.hostname
29         nodes = self.config.api.GetNodes(self.config.auth, [hostname], ['boot_state'])
30         node = nodes[0]
31         boot_state = node['boot_state']
32         if self.config.verbose:
33             utils.header("%(hostname)s boot_state is %(boot_state)s" % locals()) 
34             
35         if boot_state in ['boot', 'debug']:
36             self.exit = True
37
38         if self.config.verbose:
39             if boot_state in ['boot']:
40                 utils.header("%(hostname)s correctly installed and booted" % locals())
41             else:
42                 utils.header("%(hostname)s not fully booted" % locals())
43         self.boot_state = boot_state
44         return self.exit
45         
46     def console_nanny(self,console_states):
47         ready = select.select([],[self.stdout],[],1)[1]
48         output = 0
49         if len(ready)>0:
50             output = 1
51             line = self.stdout.readline()
52             for searchstring in console_states.keys():
53                 result = line.find(searchstring)
54                 # if result... ret = console_states[searchstring]
55                 break
56                 # check result for whether we found it
57
58             # for now just print it out
59             utils.header(line) 
60             # should be parsing for special strings that indicate whether
61             # we've reached a particular state within the boot sequence
62
63         return output
64
65     def call(self, hostname, image_type = 'node-iso', disk_size="4G", wait = 30):
66         # wait up to 30 minutes for a node to boot and install itself correctly
67         self.hostname = hostname
68         self.totaltime = 60*60*wait
69
70         api = self.config.api
71         auth = self.config.auth
72         tdir = "/usr/tmp/"
73         
74         # validate hostname
75         nodes = api.GetNodes(auth, [hostname], ['hostname'])
76         if not nodes:
77             raise Exception, "No such node %(hostname)s" % locals() 
78
79         bootimage = api.GetBootMedium(auth, hostname, image_type, '')
80         bootimage_path = '/%(tdir)s/%(hostname)s-bootcd.iso' % locals()
81
82         if self.config.verbose:
83             utils.header("Creating bootcd for %(hostname)s at %(bootimage_path)s" % locals())   
84         # Create a temporary bootcd file
85         fp = open(bootimage_path, 'w')
86         fp.write(base64.b64decode(bootimage))
87         fp.close()
88         
89         # Create a temporary disk image
90         diskimage_path = "/%(tdir)s/%(hostname)s-hda.img" % locals() 
91         qemu_img_cmd = "qemu-img create -f qcow2 %(diskimage_path)s %(disk_size)s" % locals()
92         (stdin, stdout, stderr) = os.popen3(qemu_img_cmd)
93         self.errors = stderr.readlines()
94         if self.errors: 
95             raise Exception, "Unable to create disk image\n" + \
96                             "\n".join(self.errors)
97
98         if self.config.verbose:
99             utils.header("Booting %(hostname)s" % locals())
100
101         # Attempt to boot this node image
102
103         # generate a temp filename to which qemu should store its pid (crappy approach)
104         tmp = tempfile.mkstemp(".pid","qemu_")
105         pidfile=tmp[1]
106         os.unlink(pidfile)
107         os.close(tmp[0])
108
109         # boot node with ramsize memory
110         ramsize=400
111
112         # always use the 64 bit version of qemu, as this will work on both 32 & 64 bit host kernels
113         bootcmd = "qemu-system-x86_64" 
114         # tell qemu to store its pid ina  file
115         bootcmd = bootcmd + " -pidfile %(pidfile)s " % locals()
116         # boot with ramsize memory
117         bootcmd = bootcmd + " -m %(ramsize)s" % locals()
118         # uniprocessor only
119         bootcmd = bootcmd + " -smp 1"
120         # no graphics support -> assume we are booting via serial console
121         bootcmd = bootcmd + " -nographic"
122         # boot from the supplied cdrom iso file
123         bootcmd = bootcmd + " -boot d"
124         bootcmd = bootcmd + " -cdrom %(bootimage_path)s" % locals()
125         # hard disk image to use for the node
126         bootcmd = bootcmd + " %(diskimage_path)s" % locals()
127         # launch qemu
128         (self.stdin, self.stdout, self.stderr) = os.popen3(bootcmd)
129
130         # wait for qemu to start up
131         time.sleep(3)
132         
133         # get qemu's pid from its pidfile (crappy approach)
134         fp = file(pidfile)
135         buf=fp.read()
136         self.pid=int(buf)
137         fp.close()
138         os.unlink(pidfile)
139
140         # loop until the node is either fully booted, some error
141         # occured, or we've reached our totaltime out
142         def catch(sig, frame):
143             self.totaltime = self.totaltime -1
144             total = self.totaltime
145             if (total == 0) or \
146                    (((total % 60)==0) and self.state_nanny()):
147                 killqemu(self.pid)
148                 self.exit = True
149             else:
150                 signal.alarm(1)
151
152         try:
153             signal.signal(signal.SIGALRM, catch)
154             signal.alarm(1)
155             self.exit = False
156
157             console_states = {"login:":1}
158             while not self.exit:
159                 try:
160                     self.console_nanny(console_states)
161                 except: # need a better way to catch exceptions
162                     pass
163
164             signal.alarm(0)
165         except:
166             signal.alarm(0)
167
168         killqemu(self.pid)
169         return 1
170
171 if __name__ == '__main__':
172     args = tuple(sys.argv[1:])
173     boot_node()(*args)