11from oeqa .selftest .systemupdate .systemupdatebase import SystemUpdateBase
22from oeqa .utils .commands import runqemu , get_bb_vars , bitbake
33
4+ import contextlib
45import http .server
56import os
67import stat
78import errno
89import tempfile
10+ import traceback
911import threading
1012
13+ class HTTPServer (object ):
14+ """
15+ Dynamically finds an available port and serves a certain directory there.
16+ To be used in a "with HTTPServer(dir) as httpd" construct.
17+ """
18+ def __init__ (self , root , logger ):
19+ self .root = root
20+ self .logger = logger
21+ self .server = None
22+ self .http_log = []
23+ self .stop_at = None
24+
25+ def __enter__ (self ):
26+ try :
27+ class HTTPRequestHandler (http .server .SimpleHTTPRequestHandler ):
28+ parent = self
29+ request_counter = 0
30+ def log_message (self , format , * args ):
31+ msg = format % args
32+ self .parent .logger .info (msg )
33+ self .parent .http_log .append (msg )
34+
35+ def translate_path (self , path ):
36+ """
37+ Return absolute path based on document root instead of current directory.
38+ """
39+
40+ # The original implementation returns an absolute path rooted in the
41+ # current directory. We need to serve a different
42+ # directory without being able to chdir(), because
43+ # doing that would cause commands like bitbake to
44+ # run there, which is undesirable because for
45+ # example bitbake creates a bitbake-cookerdaemon.log
46+ # in the current directory.
47+ path = super ().translate_path (path )
48+ relpath = os .path .relpath (path )
49+ path = os .path .join (self .parent .root , relpath )
50+ return path
51+
52+ def do_GET (self ):
53+ """
54+ Inject errors.
55+ """
56+ counter = HTTPRequestHandler .request_counter
57+ HTTPRequestHandler .request_counter += 1
58+ if self .parent .stop_at is not None and counter >= self .parent .stop_at :
59+ self .send_error (500 , 'test server is intentionally down' )
60+ else :
61+ super ().do_GET ()
62+
63+ handler = HTTPRequestHandler
64+
65+ def create_httpd ():
66+ for port in range (9999 , 10000 ):
67+ try :
68+ server = http .server .HTTPServer (('localhost' , port ), handler )
69+ return server
70+ except OSError as ex :
71+ if ex .errno != errno .EADDRINUSE :
72+ raise
73+ self .fail ('no port available for HTTP server' )
74+
75+ self .server = create_httpd ()
76+ self .port = self .server .server_port
77+ self .logger .info ('serving repo %s on port %d' % (self .root , self .port ))
78+ helper = threading .Thread (name = 'HTTPD' , target = self .server .serve_forever )
79+ helper .start ()
80+
81+ # Now let caller do its work while the server runs.
82+ return self
83+ except :
84+ self ._stop ()
85+ raise
86+
87+ def __exit__ (self , exc_type , exc_val , exc_tb ):
88+ self ._stop ()
89+
90+ def _stop (self ):
91+ # We have to stop a running server under all circumstances,
92+ # otherwise the helper thread will keep running and we end up
93+ # with thread locking issues.
94+ if self .server :
95+ self .server .shutdown ()
96+ self .server .server_close ()
97+ self .server = None
98+
1199class HTTPUpdate (SystemUpdateBase ):
12100 """
13101 System update tests for image update mechanisms which depend on
@@ -51,7 +139,8 @@ def track_for_cleanup(self, name):
51139 if 'NO_CLEANUP' not in os .environ :
52140 super ().track_for_cleanup (name )
53141
54- def boot_image (self , overrides ):
142+ @contextlib .contextmanager
143+ def boot_image (self , overrides = {}):
55144 # We don't know the final port yet, so instead we create a placeholder script
56145 # for qemu to use and rewrite that script once we are ready. The kernel refuses
57146 # to execute a shell script while we have it open, so here we close it
@@ -60,106 +149,55 @@ def boot_image(self, overrides):
60149 # The helper script also keeps command line handling a bit simpler (no whitespace
61150 # in -netdev parameter), which may or may not be relevant.
62151 self .httpd_netcat = tempfile .NamedTemporaryFile (mode = 'w' , prefix = 'httpd-netcat-' , dir = os .getcwd (), delete = False )
63- self .httpd_netcat .close ()
64- os .chmod (self .httpd_netcat .name , stat .S_IRUSR | stat .S_IWUSR | stat .S_IXUSR )
65- self .track_for_cleanup (self .httpd_netcat .name )
66-
67- qemuboot_conf = os .path .join (self .image_dir_test ,
68- '%s-%s.qemuboot.conf' % (self .IMAGE_PN , self .BB_VARS ['MACHINE' ]))
69- with open (qemuboot_conf ) as f :
70- conf = f .read ()
71- with open (qemuboot_conf , 'w' ) as f :
72- f .write ('\n ' .join ([x for x in conf .splitlines () if not x .startswith ('qb_slirp_opt' )]))
73- f .write ('\n qb_slirp_opt = -netdev user,id=net0,guestfwd=tcp:%s-cmd:%s\n ' % \
74- (self .HTTPD_SERVER , self .httpd_netcat .name ))
75- return runqemu (self .IMAGE_PN ,
76- discard_writes = False , ssh = False ,
77- overrides = overrides ,
78- runqemuparams = 'ovmf slirp nographic' ,
79- image_fstype = 'wic' )
80-
81- def update_image (self , qemu ):
82- # We need to bring up some simple HTTP server for the
83- # update repo.
84- server = None
85- self .http_log = []
86- http_log = self .http_log
87152 try :
88- class HTTPRequestHandler (http .server .SimpleHTTPRequestHandler ):
89- parent = self
90- request_counter = 0
91- def log_message (self , format , * args ):
92- msg = format % args
93- self .parent .logger .info (msg )
94- self .parent .http_log .append (msg )
95-
96- def translate_path (self , path ):
97- """
98- Return absolute path based on document root instead of current directory.
99- """
100-
101- # The original implementation returns an absolute path rooted in the
102- # current directory. We need to serve a different
103- # directory without being able to chdir(), because
104- # doing that would cause commands like bitbake to
105- # run there, which is undesirable because for
106- # example bitbake creates a bitbake-cookerdaemon.log
107- # in the current directory.
108- path = super ().translate_path (path )
109- relpath = os .path .relpath (path )
110- path = os .path .join (self .parent .REPO_DIR , relpath )
111- return path
112-
113- def do_GET (self ):
114- """
115- Inject errors.
116- """
117- counter = HTTPRequestHandler .request_counter
118- HTTPRequestHandler .request_counter += 1
119- stop_at = getattr (self .parent , 'stop_serving_http_at' , None )
120- if stop_at is not None and counter >= stop_at :
121- self .send_error (500 , 'test server is intentionally down' )
122- else :
123- super ().do_GET ()
153+ self .httpd_netcat .close ()
154+ os .chmod (self .httpd_netcat .name , stat .S_IRUSR | stat .S_IWUSR | stat .S_IXUSR )
155+ qemuboot_conf = os .path .join (self .image_dir_test ,
156+ '%s-%s.qemuboot.conf' % (self .IMAGE_PN , self .BB_VARS ['MACHINE' ]))
157+ with open (qemuboot_conf ) as f :
158+ conf = f .read ()
159+ with open (qemuboot_conf , 'w' ) as f :
160+ f .write ('\n ' .join ([x for x in conf .splitlines () if not x .startswith ('qb_slirp_opt' )]))
161+ f .write ('\n qb_slirp_opt = -netdev user,id=net0,guestfwd=tcp:%s-cmd:%s\n ' % \
162+ (self .HTTPD_SERVER , self .httpd_netcat .name ))
163+ with super ().boot_image (ssh = False ,
164+ runqemuparams = 'ovmf slirp nographic' ,
165+ image_fstype = 'wic' ) as qemu :
166+ yield qemu
167+ finally :
168+ os .unlink (self .httpd_netcat .name )
124169
125- handler = HTTPRequestHandler
170+ @contextlib .contextmanager
171+ def start_httpd (self ):
172+ """
173+ Bring up the HTTP server when entering the context and shut it down when done.
174+ """
126175
127- def create_httpd ():
128- for port in range (9999 , 10000 ):
129- try :
130- server = http .server .HTTPServer (('localhost' , port ), handler )
131- return server
132- except OSError as ex :
133- if ex .errno != errno .EADDRINUSE :
134- raise
135- self .fail ('no port available for HTTP server' )
176+ # netcat can't be assumed to be present. Build and use socat instead.
177+ # It's a bit more complicated but has the advantage that it is in OE-core.
178+ socat = os .path .join (self .BB_VARS ['RECIPE_SYSROOT_NATIVE' ], 'usr' , 'bin' , 'socat' )
179+ if not os .path .exists (socat ):
180+ bitbake ('socat-native:do_addto_recipe_sysroot' , output_log = self .logger )
181+ self .assertExists (socat , 'socat-native was not built as expected' )
136182
137- server = create_httpd ()
138- port = server .server_port
139- self .logger .info ('serving repo %s on port %d' % (self .REPO_DIR , port ))
140- helper = threading .Thread (name = 'HTTPD' , target = server .serve_forever )
141- helper .start ()
142- # netcat can't be assumed to be present. Build and use socat instead.
143- # It's a bit more complicated but has the advantage that it is in OE-core.
144- socat = os .path .join (self .BB_VARS ['RECIPE_SYSROOT_NATIVE' ], 'usr' , 'bin' , 'socat' )
145- if not os .path .exists (socat ):
146- bitbake ('socat-native:do_addto_recipe_sysroot' , output_log = self .logger )
147- self .assertExists (socat , 'socat-native was not built as expected' )
183+ with HTTPServer (self .REPO_DIR , self .logger ) as httpd :
148184 with open (self .httpd_netcat .name , 'w' ) as f :
149185 f .write ('''#!/bin/sh
150186exec %s 2>/tmp/httpd.log -D -v -d -d -d -d STDIO TCP:localhost:%d
151- ''' % (socat , port ))
187+ ''' % (socat , httpd .port ))
188+ yield httpd
152189
190+
191+ def update_image (self , qemu ):
192+ # We need to bring up some simple HTTP server for the
193+ # update repo.
194+ with self .start_httpd () as self .httpd :
153195 # Now run the real update command inside the virtual machine.
154196 return self .update_image_via_http (qemu )
155197
156- finally :
157- if server :
158- server .shutdown ()
159- server .server_close ()
160-
161198 def update_image_via_http (self , qemu ):
162199 """
163200 Called by update_image() with the HTTPD server running.
164201 """
165202 return False
203+
0 commit comments