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