Merge lp:~jaypipes/glance/bug731304 into lp:~glance-coresec/glance/cactus-trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 99
Merged at revision: 94
Proposed branch: lp:~jaypipes/glance/bug731304
Merge into: lp:~glance-coresec/glance/cactus-trunk
Diff against target: 1329 lines (+896/-329)
11 files modified
MANIFEST.in (+1/-2)
bin/glance (+1/-1)
glance/server.py (+3/-1)
glance/store/http.py (+1/-1)
tests/functional/__init__.py (+225/-0)
tests/functional/test_bin_glance.py (+152/-0)
tests/functional/test_curl_api.py (+302/-0)
tests/functional/test_logging.py (+79/-0)
tests/functional/test_misc.py (+26/-315)
tests/unit/test_utils.py (+67/-0)
tests/utils.py (+39/-9)
To merge this branch: bzr merge lp:~jaypipes/glance/bug731304
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Christopher MacGown (community) Approve
Ed Leafe (community) Approve
Rick Harris (community) Approve
Cory Wright Pending
Review via email: mp+53915@code.launchpad.net

Commit message

Adds robust functional testing to Glance.

Description of the change

Adds robust functional testing to Glance.

- Add /tests/functional/*
- tests.functional.FunctionalTest is the base class for
  any test that needs to execute against *actual* Glance
  API and registry servers instead of stubbed out fakes.
- Adds functional test case that uses cURL to execute a
  series of actions against the API server
- Adds functional test case that uses bin/glance to
  execute a series of actions against the API server

Functional tests start the servers on unused ports, and the test runner configures the servers to use an image directory, log files, and pid files all contained in a test directory controlled by the FunctionalTest class. If a functional test fails, the user can check the test directory to see the log files that were written during the test. If the functional test passes, the test directory is destroyed.

To post a comment you must log in.
Revision history for this message
Rick Harris (rconradharris) wrote :

Really great stuff here, Jay-- thanks!

No nits, just a suggestion that we may eventually want to add a --unittests-only option to ./run_tests.sh so we can iterate quickly as we're refactoring, though not necessary at the moment since the functional tests are completely surprisingly quickly.

review: Approve
Revision history for this message
Ed Leafe (ed-leafe) wrote :

lgtm - great addition!

review: Approve
Revision history for this message
Christopher MacGown (0x44) wrote :

Very nice, Jay.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
Jay Pipes (jaypipes) wrote :

approving merge of trunk...seems the Glance tarmac job is hanging when running python setup.py test, but as usual, everything runs fine locally and I have no way of figuring out what's going on on the Jenkins box. :( grr.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MANIFEST.in'
2--- MANIFEST.in 2011-02-21 22:11:27 +0000
3+++ MANIFEST.in 2011-03-21 19:26:04 +0000
4@@ -1,9 +1,8 @@
5-include LICENSE run_tests.sh ChangeLog
6+include run_tests.sh
7 include README builddeb.sh
8 include MANIFEST.in pylintrc
9 include tests/__init__.py
10 include tests/stubs.py
11-include tests/test_data.py
12 include tests/utils.py
13 include run_tests.py
14 include glance/registry/db/migrate_repo/migrate.cfg
15
16=== modified file 'bin/glance'
17--- bin/glance 2011-03-06 17:14:59 +0000
18+++ bin/glance 2011-03-21 19:26:04 +0000
19@@ -270,7 +270,7 @@
20
21 # Have to handle "boolean" values specially...
22 if 'is_public' in fields:
23- image_meta['is_public'] = utils.int_from_bool_as_string(
24+ image_meta['is_public'] = utils.bool_from_string(
25 fields.pop('is_public'))
26
27 # Add custom attributes, which are all the arguments remaining
28
29=== modified file 'glance/server.py'
30--- glance/server.py 2011-03-09 00:07:44 +0000
31+++ glance/server.py 2011-03-21 19:26:04 +0000
32@@ -163,7 +163,9 @@
33 yield chunk
34
35 res = Response(app_iter=image_iterator(),
36- content_type="text/plain")
37+ content_type="application/octet-stream")
38+ # Using app_iter blanks content-length, so we set it here...
39+ res.headers.add('Content-Length', image['size'])
40 utils.inject_image_meta_into_headers(res, image)
41 return req.get_response(res)
42
43
44=== modified file 'glance/store/http.py'
45--- glance/store/http.py 2011-01-27 04:19:13 +0000
46+++ glance/store/http.py 2011-03-21 19:26:04 +0000
47@@ -24,7 +24,7 @@
48 """ An implementation of the HTTP Backend Adapter """
49
50 @classmethod
51- def get(cls, parsed_uri, expected_size, conn_class=None):
52+ def get(cls, parsed_uri, expected_size, options=None, conn_class=None):
53 """Takes a parsed uri for an HTTP resource, fetches it, and yields the
54 data.
55 """
56
57=== added directory 'tests/functional'
58=== added file 'tests/functional/__init__.py'
59--- tests/functional/__init__.py 1970-01-01 00:00:00 +0000
60+++ tests/functional/__init__.py 2011-03-21 19:26:04 +0000
61@@ -0,0 +1,225 @@
62+# vim: tabstop=4 shiftwidth=4 softtabstop=4
63+
64+# Copyright 2011 OpenStack, LLC
65+# All Rights Reserved.
66+#
67+# Licensed under the Apache License, Version 2.0 (the "License"); you may
68+# not use this file except in compliance with the License. You may obtain
69+# a copy of the License at
70+#
71+# http://www.apache.org/licenses/LICENSE-2.0
72+#
73+# Unless required by applicable law or agreed to in writing, software
74+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
75+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
76+# License for the specific language governing permissions and limitations
77+# under the License.
78+
79+"""
80+Base test class for running non-stubbed tests (functional tests)
81+
82+The FunctionalTest class contains helper methods for starting the API
83+and Registry server, grabbing the logs of each, cleaning up pidfiles,
84+and spinning down the servers.
85+"""
86+
87+import datetime
88+import os
89+import random
90+import shutil
91+import signal
92+import socket
93+import tempfile
94+import time
95+import unittest
96+
97+from tests.utils import execute, get_unused_port
98+
99+
100+class FunctionalTest(unittest.TestCase):
101+
102+ """
103+ Base test class for any test that wants to test the actual
104+ servers and clients and not just the stubbed out interfaces
105+ """
106+
107+ def setUp(self):
108+
109+ self.test_id = random.randint(0, 100000)
110+ self.test_dir = os.path.join("/", "tmp", "test.%d" % self.test_id)
111+
112+ self.api_port = get_unused_port()
113+ self.api_pid_file = os.path.join(self.test_dir,
114+ "glance-api.pid")
115+ self.api_log_file = os.path.join(self.test_dir, "apilog")
116+
117+ self.registry_port = get_unused_port()
118+ self.registry_pid_file = ("/tmp/test.%d/glance-registry.pid"
119+ % self.test_id)
120+ self.registry_log_file = os.path.join(self.test_dir, "registrylog")
121+
122+ self.image_dir = "/tmp/test.%d/images" % self.test_id
123+
124+ self.sql_connection = os.environ.get('GLANCE_SQL_CONNECTION',
125+ "sqlite://")
126+ self.pid_files = [self.api_pid_file,
127+ self.registry_pid_file]
128+ self.files_to_destroy = []
129+
130+ def tearDown(self):
131+ self.cleanup()
132+
133+ def cleanup(self):
134+ """
135+ Makes sure anything we created or started up in the
136+ tests are destroyed or spun down
137+ """
138+
139+ for pid_file in self.pid_files:
140+ if os.path.exists(pid_file):
141+ pid = int(open(pid_file).read().strip())
142+ try:
143+ os.killpg(pid, signal.SIGTERM)
144+ except:
145+ pass # Ignore if the process group is dead
146+ os.unlink(pid_file)
147+
148+ for f in self.files_to_destroy:
149+ if os.path.exists(f):
150+ os.unlink(f)
151+
152+ def start_servers(self, **kwargs):
153+ """
154+ Starts the API and Registry servers (bin/glance-api and
155+ bin/glance-registry) on unused ports and returns a tuple
156+ of the (api_port, registry_port, conf_file_name).
157+
158+ Any kwargs passed to this method will override the configuration
159+ value in the conf file used in starting the servers.
160+ """
161+ self.cleanup()
162+
163+ conf_override = self.__dict__.copy()
164+ if kwargs:
165+ conf_override.update(**kwargs)
166+
167+ # A config file to use just for this test...we don't want
168+ # to trample on currently-running Glance servers, now do we?
169+
170+ conf_file = tempfile.NamedTemporaryFile()
171+ conf_contents = """[DEFAULT]
172+verbose = True
173+debug = True
174+
175+[app:glance-api]
176+paste.app_factory = glance.server:app_factory
177+filesystem_store_datadir=%(image_dir)s
178+default_store = file
179+bind_host = 0.0.0.0
180+bind_port = %(api_port)s
181+registry_host = 0.0.0.0
182+registry_port = %(registry_port)s
183+log_file = %(api_log_file)s
184+
185+[app:glance-registry]
186+paste.app_factory = glance.registry.server:app_factory
187+bind_host = 0.0.0.0
188+bind_port = %(registry_port)s
189+log_file = %(registry_log_file)s
190+sql_connection = %(sql_connection)s
191+sql_idle_timeout = 3600
192+""" % conf_override
193+ conf_file.write(conf_contents)
194+ conf_file.flush()
195+ self.conf_file_name = conf_file.name
196+
197+ # Start up the API and default registry server
198+ cmd = ("./bin/glance-control api start "
199+ "%(conf_file_name)s --pid-file=%(api_pid_file)s"
200+ % self.__dict__)
201+ exitcode, out, err = execute(cmd)
202+
203+ self.assertEqual(0, exitcode,
204+ "Failed to spin up the API server. "
205+ "Got: %s" % err)
206+ self.assertTrue("Starting glance-api with" in out)
207+
208+ cmd = ("./bin/glance-control registry start "
209+ "%(conf_file_name)s --pid-file=%(registry_pid_file)s"
210+ % self.__dict__)
211+ exitcode, out, err = execute(cmd)
212+
213+ self.assertEqual(0, exitcode,
214+ "Failed to spin up the Registry server. "
215+ "Got: %s" % err)
216+ self.assertTrue("Starting glance-registry with" in out)
217+
218+ self.wait_for_servers()
219+
220+ return self.api_port, self.registry_port, self.conf_file_name
221+
222+ def ping_server(self, port):
223+ """
224+ Simple ping on the port. If responsive, return True, else
225+ return False.
226+
227+ :note We use raw sockets, not ping here, since ping uses ICMP and
228+ has no concept of ports...
229+ """
230+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
231+ try:
232+ s.connect(("127.0.0.1", port))
233+ s.close()
234+ return True
235+ except socket.error, e:
236+ return False
237+
238+ def wait_for_servers(self, timeout=3):
239+ """
240+ Tight loop, waiting for both API and registry server to be
241+ available on the ports. Returns when both are pingable. There
242+ is a timeout on waiting for the servers to come up.
243+
244+ :param timeout: Optional, defaults to 3
245+ """
246+ now = datetime.datetime.now()
247+ timeout_time = now + datetime.timedelta(seconds=timeout)
248+ while (timeout_time > now):
249+ if self.ping_server(self.api_port) and\
250+ self.ping_server(self.registry_port):
251+ return
252+ now = datetime.datetime.now()
253+ time.sleep(0.05)
254+ self.assertFalse(True, "Failed to start servers.")
255+
256+ def stop_servers(self):
257+ """
258+ Called to stop the started servers in a normal fashion. Note
259+ that cleanup() will stop the servers using a fairly draconian
260+ method of sending a SIGTERM signal to the servers. Here, we use
261+ the glance-control stop method to gracefully shut the server down.
262+ This method also asserts that the shutdown was clean, and so it
263+ is meant to be called during a normal test case sequence.
264+ """
265+
266+ # Spin down the API and default registry server
267+ cmd = ("./bin/glance-control api start "
268+ "%(conf_file_name)s --pid-file=%(api_pid_file)s"
269+ % self.__dict__)
270+ exitcode, out, err = execute(cmd)
271+ self.assertEqual(0, exitcode,
272+ "Failed to spin down the API server. "
273+ "Got: %s" % err)
274+ cmd = ("./bin/glance-control registry start "
275+ "%(conf_file_name)s --pid-file=%(registry_pid_file)s"
276+ % self.__dict__)
277+ exitcode, out, err = execute(cmd)
278+ self.assertEqual(0, exitcode,
279+ "Failed to spin down the Registry server. "
280+ "Got: %s" % err)
281+
282+ # If all went well, then just remove the test directory.
283+ # We only want to check the logs and stuff if something
284+ # went wrong...
285+ if os.path.exists(self.test_dir):
286+ shutil.rmtree(self.test_dir)
287
288=== added file 'tests/functional/test_bin_glance.py'
289--- tests/functional/test_bin_glance.py 1970-01-01 00:00:00 +0000
290+++ tests/functional/test_bin_glance.py 2011-03-21 19:26:04 +0000
291@@ -0,0 +1,152 @@
292+# vim: tabstop=4 shiftwidth=4 softtabstop=4
293+
294+# Copyright 2011 OpenStack, LLC
295+# All Rights Reserved.
296+#
297+# Licensed under the Apache License, Version 2.0 (the "License"); you may
298+# not use this file except in compliance with the License. You may obtain
299+# a copy of the License at
300+#
301+# http://www.apache.org/licenses/LICENSE-2.0
302+#
303+# Unless required by applicable law or agreed to in writing, software
304+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
305+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
306+# License for the specific language governing permissions and limitations
307+# under the License.
308+
309+"""Functional test case that utilizes the bin/glance CLI tool"""
310+
311+import os
312+import unittest
313+
314+from tests import functional
315+from tests.utils import execute
316+
317+
318+class TestBinGlance(functional.FunctionalTest):
319+
320+ """Functional tests for the bin/glance CLI tool"""
321+
322+ def test_add_list_delete_list(self):
323+ """
324+ We test the following:
325+
326+ 0. Verify no public images in index
327+ 1. Add a public image with a location attr
328+ and no image data
329+ 2. Check that image exists in index
330+ 3. Delete the image
331+ 4. Verify no longer in index
332+ """
333+
334+ self.cleanup()
335+ api_port, reg_port, conf_file = self.start_servers()
336+
337+ # 0. Verify no public images
338+ cmd = "bin/glance --port=%d index" % api_port
339+
340+ exitcode, out, err = execute(cmd)
341+
342+ self.assertEqual(0, exitcode)
343+ self.assertEqual('No public images found.', out.strip())
344+
345+ # 1. Add public image
346+ cmd = "bin/glance --port=%d add is_public=True name=MyImage" % api_port
347+
348+ exitcode, out, err = execute(cmd)
349+
350+ self.assertEqual(0, exitcode)
351+ self.assertEqual('Added new image with ID: 1', out.strip())
352+
353+ # 2. Verify image added as public image
354+ cmd = "bin/glance --port=%d index" % api_port
355+
356+ exitcode, out, err = execute(cmd)
357+
358+ self.assertEqual(0, exitcode)
359+ lines = out.split("\n")
360+ first_line = lines[0]
361+ image_data_line = lines[3]
362+ self.assertEqual('Found 1 public images...', first_line)
363+ self.assertTrue('MyImage' in image_data_line)
364+
365+ # 3. Delete the image
366+ cmd = "bin/glance --port=%d delete 1" % api_port
367+
368+ exitcode, out, err = execute(cmd)
369+
370+ self.assertEqual(0, exitcode)
371+ self.assertEqual('Deleted image 1', out.strip())
372+
373+ # 4. Verify no public images
374+ cmd = "bin/glance --port=%d index" % api_port
375+
376+ exitcode, out, err = execute(cmd)
377+
378+ self.assertEqual(0, exitcode)
379+ self.assertEqual('No public images found.', out.strip())
380+
381+ self.stop_servers()
382+
383+ def test_add_list_update_list(self):
384+ """
385+ Test for LP Bug #736295
386+ We test the following:
387+
388+ 0. Verify no public images in index
389+ 1. Add a NON-public image
390+ 2. Check that image does not appear in index
391+ 3. Update the image to be public
392+ 4. Check that image now appears in index
393+ """
394+
395+ self.cleanup()
396+ api_port, reg_port, conf_file = self.start_servers()
397+
398+ # 0. Verify no public images
399+ cmd = "bin/glance --port=%d index" % api_port
400+
401+ exitcode, out, err = execute(cmd)
402+
403+ self.assertEqual(0, exitcode)
404+ self.assertEqual('No public images found.', out.strip())
405+
406+ # 1. Add public image
407+ cmd = "bin/glance --port=%d add name=MyImage" % api_port
408+
409+ exitcode, out, err = execute(cmd)
410+
411+ self.assertEqual(0, exitcode)
412+ self.assertEqual('Added new image with ID: 1', out.strip())
413+
414+ # 2. Verify image added as public image
415+ cmd = "bin/glance --port=%d index" % api_port
416+
417+ exitcode, out, err = execute(cmd)
418+
419+ self.assertEqual(0, exitcode)
420+ self.assertEqual('No public images found.', out.strip())
421+
422+ # 3. Update the image to make it public
423+ cmd = "bin/glance --port=%d update 1 is_public=True" % api_port
424+
425+ exitcode, out, err = execute(cmd)
426+
427+ self.assertEqual(0, exitcode)
428+ self.assertEqual('Updated image 1', out.strip())
429+
430+ # 4. Verify image 1 in list of public images
431+ cmd = "bin/glance --port=%d index" % api_port
432+
433+ exitcode, out, err = execute(cmd)
434+
435+ self.assertEqual(0, exitcode)
436+ lines = out.split("\n")
437+ first_line = lines[0]
438+ self.assertEqual('Found 1 public images...', first_line)
439+
440+ image_data_line = lines[3]
441+ self.assertTrue('MyImage' in image_data_line)
442+
443+ self.stop_servers()
444
445=== added file 'tests/functional/test_curl_api.py'
446--- tests/functional/test_curl_api.py 1970-01-01 00:00:00 +0000
447+++ tests/functional/test_curl_api.py 2011-03-21 19:26:04 +0000
448@@ -0,0 +1,302 @@
449+# vim: tabstop=4 shiftwidth=4 softtabstop=4
450+
451+# Copyright 2011 OpenStack, LLC
452+# All Rights Reserved.
453+#
454+# Licensed under the Apache License, Version 2.0 (the "License"); you may
455+# not use this file except in compliance with the License. You may obtain
456+# a copy of the License at
457+#
458+# http://www.apache.org/licenses/LICENSE-2.0
459+#
460+# Unless required by applicable law or agreed to in writing, software
461+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
462+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
463+# License for the specific language governing permissions and limitations
464+# under the License.
465+
466+"""Functional test case that utilizes cURL against the API server"""
467+
468+import json
469+import os
470+import unittest
471+
472+from tests import functional
473+from tests.utils import execute
474+
475+FIVE_KB = 5 * 1024
476+
477+
478+class TestCurlApi(functional.FunctionalTest):
479+
480+ """Functional tests using straight cURL against the API server"""
481+
482+ def test_get_head_simple_post(self):
483+ """
484+ We test the following sequential series of actions:
485+
486+ 0. GET /images
487+ - Verify no public images
488+ 1. GET /images/detail
489+ - Verify no public images
490+ 2. HEAD /images/1
491+ - Verify 404 returned
492+ 3. POST /images with public image named Image1 with a location
493+ attribute and no custom properties
494+ - Verify 201 returned
495+ 4. HEAD /images/1
496+ - Verify HTTP headers have correct information we just added
497+ 5. GET /images/1
498+ - Verify all information on image we just added is correct
499+ 6. GET /images
500+ - Verify the image we just added is returned
501+ 7. GET /images/detail
502+ - Verify the image we just added is returned
503+ 8. PUT /images/1 with custom properties of "distro" and "arch"
504+ - Verify 200 returned
505+ 9. GET /images/1
506+ - Verify updated information about image was stored
507+ """
508+
509+ self.cleanup()
510+ api_port, reg_port, conf_file = self.start_servers()
511+
512+ # 0. GET /images
513+ # Verify no public images
514+ cmd = "curl -g http://0.0.0.0:%d/images" % api_port
515+
516+ exitcode, out, err = execute(cmd)
517+
518+ self.assertEqual(0, exitcode)
519+ self.assertEqual('{"images": []}', out.strip())
520+
521+ # 1. GET /images/detail
522+ # Verify no public images
523+ cmd = "curl -g http://0.0.0.0:%d/images/detail" % api_port
524+
525+ exitcode, out, err = execute(cmd)
526+
527+ self.assertEqual(0, exitcode)
528+ self.assertEqual('{"images": []}', out.strip())
529+
530+ # 2. HEAD /images/1
531+ # Verify 404 returned
532+ cmd = "curl -i -X HEAD http://0.0.0.0:%d/images/1" % api_port
533+
534+ exitcode, out, err = execute(cmd)
535+
536+ self.assertEqual(0, exitcode)
537+
538+ lines = out.split("\r\n")
539+ status_line = lines[0]
540+
541+ self.assertEqual("HTTP/1.1 404 Not Found", status_line)
542+
543+ # 3. POST /images with public image named Image1
544+ # attribute and no custom properties. Verify a 200 OK is returned
545+ image_data = "*" * FIVE_KB
546+
547+ cmd = ("curl -i -X POST "
548+ "-H 'Expect: ' " # Necessary otherwise sends 100 Continue
549+ "-H 'Content-Type: application/octet-stream' "
550+ "-H 'X-Image-Meta-Name: Image1' "
551+ "-H 'X-Image-Meta-Is-Public: True' "
552+ "--data-binary \"%s\" "
553+ "http://0.0.0.0:%d/images") % (image_data, api_port)
554+
555+ exitcode, out, err = execute(cmd)
556+ self.assertEqual(0, exitcode)
557+
558+ lines = out.split("\r\n")
559+ status_line = lines[0]
560+
561+ self.assertEqual("HTTP/1.1 200 OK", status_line)
562+
563+ # 4. HEAD /images
564+ # Verify image found now
565+ cmd = "curl -i -X HEAD http://0.0.0.0:%d/images/1" % api_port
566+
567+ exitcode, out, err = execute(cmd)
568+
569+ self.assertEqual(0, exitcode)
570+
571+ lines = out.split("\r\n")
572+ status_line = lines[0]
573+
574+ self.assertEqual("HTTP/1.1 200 OK", status_line)
575+ self.assertTrue("X-Image-Meta-Name: Image1" in out)
576+
577+ # 5. GET /images/1
578+ # Verify all information on image we just added is correct
579+
580+ cmd = "curl -i -g http://0.0.0.0:%d/images/1" % api_port
581+
582+ exitcode, out, err = execute(cmd)
583+
584+ self.assertEqual(0, exitcode)
585+
586+ lines = out.split("\r\n")
587+
588+ self.assertEqual("HTTP/1.1 200 OK", lines.pop(0))
589+
590+ # Handle the headers
591+ image_headers = {}
592+ std_headers = {}
593+ other_lines = []
594+ for line in lines:
595+ if line.strip() == '':
596+ continue
597+ if line.startswith("X-Image"):
598+ pieces = line.split(":")
599+ key = pieces[0].strip()
600+ value = ":".join(pieces[1:]).strip()
601+ image_headers[key] = value
602+ elif ':' in line:
603+ pieces = line.split(":")
604+ key = pieces[0].strip()
605+ value = ":".join(pieces[1:]).strip()
606+ std_headers[key] = value
607+ else:
608+ other_lines.append(line)
609+
610+ expected_image_headers = {
611+ 'X-Image-Meta-Id': '1',
612+ 'X-Image-Meta-Name': 'Image1',
613+ 'X-Image-Meta-Is_public': 'True',
614+ 'X-Image-Meta-Status': 'active',
615+ 'X-Image-Meta-Disk_format': '',
616+ 'X-Image-Meta-Container_format': '',
617+ 'X-Image-Meta-Size': str(FIVE_KB),
618+ 'X-Image-Meta-Location': 'file://%s/1' % self.image_dir}
619+
620+ expected_std_headers = {
621+ 'Content-Length': str(FIVE_KB),
622+ 'Content-Type': 'application/octet-stream'}
623+
624+ for expected_key, expected_value in expected_image_headers.items():
625+ self.assertTrue(expected_key in image_headers,
626+ "Failed to find key %s in image_headers"
627+ % expected_key)
628+ self.assertEqual(expected_value, image_headers[expected_key],
629+ "For key '%s' expected header value '%s'. Got '%s'"
630+ % (expected_key,
631+ expected_value,
632+ image_headers[expected_key]))
633+
634+ for expected_key, expected_value in expected_std_headers.items():
635+ self.assertTrue(expected_key in std_headers,
636+ "Failed to find key %s in std_headers"
637+ % expected_key)
638+ self.assertEqual(expected_value, std_headers[expected_key],
639+ "For key '%s' expected header value '%s'. Got '%s'"
640+ % (expected_key,
641+ expected_value,
642+ std_headers[expected_key]))
643+
644+ # Now the image data...
645+ expected_image_data = "*" * FIVE_KB
646+
647+ # Should only be a single "line" left, and
648+ # that's the image data
649+ self.assertEqual(1, len(other_lines))
650+ self.assertEqual(expected_image_data, other_lines[0])
651+
652+ # 6. GET /images
653+ # Verify no public images
654+ cmd = "curl -g http://0.0.0.0:%d/images" % api_port
655+
656+ exitcode, out, err = execute(cmd)
657+
658+ self.assertEqual(0, exitcode)
659+
660+ expected_result = {"images": [
661+ {"container_format": None,
662+ "disk_format": None,
663+ "id": 1,
664+ "name": "Image1",
665+ "size": 5120}]}
666+ self.assertEqual(expected_result, json.loads(out.strip()))
667+
668+ # 7. GET /images/detail
669+ # Verify image and all its metadata
670+ cmd = "curl -g http://0.0.0.0:%d/images/detail" % api_port
671+
672+ exitcode, out, err = execute(cmd)
673+
674+ self.assertEqual(0, exitcode)
675+
676+ expected_image = {
677+ "status": "active",
678+ "name": "Image1",
679+ "deleted": False,
680+ "container_format": None,
681+ "disk_format": None,
682+ "id": 1,
683+ "location": "file://%s/1" % self.image_dir,
684+ "is_public": True,
685+ "deleted_at": None,
686+ "properties": {},
687+ "size": 5120}
688+
689+ image = json.loads(out.strip())['images'][0]
690+
691+ for expected_key, expected_value in expected_image.items():
692+ self.assertTrue(expected_key in image,
693+ "Failed to find key %s in image"
694+ % expected_key)
695+ self.assertEqual(expected_value, expected_image[expected_key],
696+ "For key '%s' expected header value '%s'. Got '%s'"
697+ % (expected_key,
698+ expected_value,
699+ image[expected_key]))
700+
701+ # 8. PUT /images/1 with custom properties of "distro" and "arch"
702+ # Verify 200 returned
703+
704+ cmd = ("curl -i -X PUT "
705+ "-H 'X-Image-Meta-Property-Distro: Ubuntu' "
706+ "-H 'X-Image-Meta-Property-Arch: x86_64' "
707+ "http://0.0.0.0:%d/images/1") % api_port
708+
709+ exitcode, out, err = execute(cmd)
710+ self.assertEqual(0, exitcode)
711+
712+ lines = out.split("\r\n")
713+ status_line = lines[0]
714+
715+ self.assertEqual("HTTP/1.1 200 OK", status_line)
716+
717+ # 9. GET /images/detail
718+ # Verify image and all its metadata
719+ cmd = "curl -g http://0.0.0.0:%d/images/detail" % api_port
720+
721+ exitcode, out, err = execute(cmd)
722+
723+ self.assertEqual(0, exitcode)
724+
725+ expected_image = {
726+ "status": "active",
727+ "name": "Image1",
728+ "deleted": False,
729+ "container_format": None,
730+ "disk_format": None,
731+ "id": 1,
732+ "location": "file://%s/1" % self.image_dir,
733+ "is_public": True,
734+ "deleted_at": None,
735+ "properties": {'distro': 'Ubuntu', 'arch': 'x86_64'},
736+ "size": 5120}
737+
738+ image = json.loads(out.strip())['images'][0]
739+
740+ for expected_key, expected_value in expected_image.items():
741+ self.assertTrue(expected_key in image,
742+ "Failed to find key %s in image"
743+ % expected_key)
744+ self.assertEqual(expected_value, image[expected_key],
745+ "For key '%s' expected header value '%s'. Got '%s'"
746+ % (expected_key,
747+ expected_value,
748+ image[expected_key]))
749+
750+ self.stop_servers()
751
752=== added file 'tests/functional/test_logging.py'
753--- tests/functional/test_logging.py 1970-01-01 00:00:00 +0000
754+++ tests/functional/test_logging.py 2011-03-21 19:26:04 +0000
755@@ -0,0 +1,79 @@
756+# vim: tabstop=4 shiftwidth=4 softtabstop=4
757+
758+# Copyright 2011 OpenStack, LLC
759+# All Rights Reserved.
760+#
761+# Licensed under the Apache License, Version 2.0 (the "License"); you may
762+# not use this file except in compliance with the License. You may obtain
763+# a copy of the License at
764+#
765+# http://www.apache.org/licenses/LICENSE-2.0
766+#
767+# Unless required by applicable law or agreed to in writing, software
768+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
769+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
770+# License for the specific language governing permissions and limitations
771+# under the License.
772+
773+import os
774+import unittest
775+
776+from tests import functional
777+from tests.utils import execute
778+
779+
780+class TestLogging(functional.FunctionalTest):
781+
782+ """Tests that logging can be configured correctly"""
783+
784+ def test_logfile(self):
785+ """
786+ A test that logging can be configured properly from the
787+ glance.conf file with the log_file option.
788+
789+ We start both servers daemonized with a temporary config
790+ file that has some logging options in it.
791+
792+ We then use curl to issue a few requests and verify that each server's
793+ logging statements were logged to the one log file
794+ """
795+ self.cleanup()
796+ api_port, reg_port, conf_file = self.start_servers()
797+
798+ cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
799+ "-H 'X-Image-Meta-Name: ImageName' "\
800+ "-H 'X-Image-Meta-Disk-Format: Invalid' "\
801+ "http://0.0.0.0:%d/images" % api_port
802+ ignored, out, err = execute(cmd)
803+
804+ self.assertTrue('Invalid disk format' in out,
805+ "Could not find 'Invalid disk format' "
806+ "in output: %s" % out)
807+
808+ self.assertTrue(os.path.exists(self.api_log_file),
809+ "API Logfile %s does not exist!"
810+ % self.api_log_file)
811+ self.assertTrue(os.path.exists(self.registry_log_file),
812+ "Registry Logfile %s does not exist!"
813+ % self.registry_log_file)
814+
815+ api_logfile_contents = open(self.api_log_file, 'rb').read()
816+ registry_logfile_contents = open(self.registry_log_file, 'rb').read()
817+
818+ # Check that BOTH the glance API and registry server
819+ # modules are logged to their respective logfiles.
820+ self.assertTrue('[glance.server]'
821+ in api_logfile_contents,
822+ "Could not find '[glance.server]' "
823+ "in API logfile: %s" % api_logfile_contents)
824+ self.assertTrue('[glance.registry.server]'
825+ in registry_logfile_contents,
826+ "Could not find '[glance.registry.server]' "
827+ "in Registry logfile: %s" % registry_logfile_contents)
828+
829+ # Test that the error we caused above is in the log
830+ self.assertTrue('Invalid disk format' in api_logfile_contents,
831+ "Could not find 'Invalid disk format' "
832+ "in API logfile: %s" % api_logfile_contents)
833+
834+ self.stop_servers()
835
836=== renamed file 'tests/unit/test_misc.py' => 'tests/functional/test_misc.py'
837--- tests/unit/test_misc.py 2011-03-16 16:11:56 +0000
838+++ tests/functional/test_misc.py 2011-03-21 19:26:04 +0000
839@@ -15,101 +15,16 @@
840 # License for the specific language governing permissions and limitations
841 # under the License.
842
843-import os
844-import shutil
845-import signal
846-import subprocess
847-import tempfile
848-import time
849 import unittest
850
851-from glance import utils
852-
853-
854-def execute(cmd):
855- env = os.environ.copy()
856- # Make sure that we use the programs in the
857- # current source directory's bin/ directory.
858- env['PATH'] = os.path.join(os.getcwd(), 'bin') + ':' + env['PATH']
859- process = subprocess.Popen(cmd,
860- shell=True,
861- stdin=subprocess.PIPE,
862- stdout=subprocess.PIPE,
863- stderr=subprocess.PIPE,
864- env=env)
865- result = process.communicate()
866- (out, err) = result
867- exitcode = process.returncode
868- if process.returncode != 0:
869- msg = "Command %(cmd)s did not succeed. Returned an exit "\
870- "code of %(exitcode)d."\
871- "\n\nSTDOUT: %(out)s"\
872- "\n\nSTDERR: %(err)s" % locals()
873- raise RuntimeError(msg)
874- return exitcode, out, err
875-
876-
877-class TestMiscellaneous(unittest.TestCase):
878+from tests import functional
879+from tests.utils import execute
880+
881+
882+class TestMiscellaneous(functional.FunctionalTest):
883
884 """Some random tests for various bugs and stuff"""
885
886- def tearDown(self):
887- self._cleanup_test_servers()
888-
889- def _cleanup_test_servers(self):
890- # Clean up any leftover test servers...
891- pid_files = ('glance-api.pid', 'glance-registry.pid')
892- for pid_file in pid_files:
893- if os.path.exists(pid_file):
894- pid = int(open(pid_file).read().strip())
895- try:
896- os.killpg(pid, signal.SIGTERM)
897- except:
898- pass # Ignore if the process group is dead
899- os.unlink(pid_file)
900-
901- def test_headers_are_unicode(self):
902- """
903- Verifies that the headers returned by conversion code are unicode.
904-
905- Headers are passed via http in non-testing mode, which automatically
906- converts them to unicode. Verifying that the method does the
907- conversion proves that we aren't passing data that works in tests
908- but will fail in production.
909- """
910- fixture = {'name': 'fake public image',
911- 'is_public': True,
912- 'type': 'kernel',
913- 'size': 19,
914- 'location': "file:///tmp/glance-tests/2",
915- 'properties': {'distro': 'Ubuntu 10.04 LTS'}}
916- headers = utils.image_meta_to_http_headers(fixture)
917- for k, v in headers.iteritems():
918- self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
919-
920- def test_data_passed_properly_through_headers(self):
921- """
922- Verifies that data is the same after being passed through headers
923- """
924- fixture = {'name': 'fake public image',
925- 'is_public': True,
926- 'deleted': False,
927- 'type': 'kernel',
928- 'name': None,
929- 'size': 19,
930- 'location': "file:///tmp/glance-tests/2",
931- 'properties': {'distro': 'Ubuntu 10.04 LTS'}}
932- headers = utils.image_meta_to_http_headers(fixture)
933-
934- class FakeResponse():
935- pass
936-
937- response = FakeResponse()
938- response.headers = headers
939- result = utils.get_image_meta_from_headers(response)
940- for k, v in fixture.iteritems():
941- self.assertEqual(v, result[k])
942-
943 def test_exception_not_eaten_from_registry_to_api(self):
944 """
945 A test for LP bug #704854 -- Exception thrown by registry
946@@ -125,228 +40,24 @@
947 and verify that glance-upload doesn't eat the exception either...
948 """
949
950- self._cleanup_test_servers()
951-
952- # Port numbers hopefully not used by anything...
953- api_port = 32001
954- reg_port = 32000
955- image_dir = "/tmp/test.images.%d" % api_port
956- sql_connection = os.environ.get('GLANCE_SQL_CONNECTION', "sqlite://")
957- if os.path.exists(image_dir):
958- shutil.rmtree(image_dir)
959-
960- # A config file to use just for this test...we don't want
961- # to trample on currently-running Glance servers, now do we?
962- with tempfile.NamedTemporaryFile() as conf_file:
963- conf_contents = """[DEFAULT]
964-verbose = True
965-debug = True
966-
967-[app:glance-api]
968-paste.app_factory = glance.server:app_factory
969-filesystem_store_datadir=%(image_dir)s
970-default_store = file
971-bind_host = 0.0.0.0
972-bind_port = %(api_port)s
973-registry_host = 0.0.0.0
974-registry_port = %(reg_port)s
975-
976-[app:glance-registry]
977-paste.app_factory = glance.registry.server:app_factory
978-bind_host = 0.0.0.0
979-bind_port = %(reg_port)s
980-sql_connection = %(sql_connection)s
981-sql_idle_timeout = 3600
982-""" % locals()
983- conf_file.write(conf_contents)
984- conf_file.flush()
985- conf_file_name = conf_file.name
986-
987- venv = ""
988- if 'VIRTUAL_ENV' in os.environ:
989- venv = "tools/with_venv.sh "
990-
991- # Start up the API and default registry server
992- cmd = venv + "./bin/glance-control api start "\
993- "%s --pid-file=glance-api.pid" % conf_file_name
994- exitcode, out, err = execute(cmd)
995-
996- self.assertEquals(0, exitcode)
997- self.assertTrue("Starting glance-api with" in out)
998-
999- cmd = venv + "./bin/glance-control registry start "\
1000- "%s --pid-file=glance-registry.pid" % conf_file_name
1001- exitcode, out, err = execute(cmd)
1002-
1003- self.assertEquals(0, exitcode)
1004- self.assertTrue("Starting glance-registry with" in out)
1005-
1006- time.sleep(2) # Gotta give some time for spin up...
1007-
1008- cmd = "curl -g http://0.0.0.0:%d/images" % api_port
1009-
1010- exitcode, out, err = execute(cmd)
1011-
1012- self.assertEquals(0, exitcode)
1013- self.assertEquals('{"images": []}', out.strip())
1014-
1015- cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
1016- "-H 'X-Image-Meta-Name: ImageName' "\
1017- "-H 'X-Image-Meta-Disk-Format: Invalid' "\
1018- "http://0.0.0.0:%d/images" % api_port
1019- ignored, out, err = execute(cmd)
1020-
1021- self.assertTrue('Invalid disk format' in out,
1022- "Could not find 'Invalid disk format' "
1023- "in output: %s" % out)
1024-
1025- # Spin down the API and default registry server
1026- cmd = "./bin/glance-control api stop "\
1027- "%s --pid-file=glance-api.pid" % conf_file_name
1028- ignored, out, err = execute(cmd)
1029- cmd = "./bin/glance-control registry stop "\
1030- "%s --pid-file=glance-registry.pid" % conf_file_name
1031- ignored, out, err = execute(cmd)
1032-
1033-
1034-# TODO(jaypipes): Move this to separate test file once
1035-# LP Bug#731304 moves execute() out to a common file, etc
1036-class TestLogging(unittest.TestCase):
1037-
1038- """Tests that logging can be configured correctly"""
1039-
1040- def setUp(self):
1041- self.logfiles = []
1042-
1043- def tearDown(self):
1044- self._cleanup_test_servers()
1045- self._cleanup_log_files()
1046-
1047- def _cleanup_test_servers(self):
1048- # Clean up any leftover test servers...
1049- pid_files = ('glance-api.pid', 'glance-registry.pid')
1050- for pid_file in pid_files:
1051- if os.path.exists(pid_file):
1052- pid = int(open(pid_file).read().strip())
1053- try:
1054- os.killpg(pid, signal.SIGTERM)
1055- except:
1056- pass # Ignore if the process group is dead
1057- os.unlink(pid_file)
1058-
1059- def _cleanup_log_files(self):
1060- for f in self.logfiles:
1061- if os.path.exists(f):
1062- os.unlink(f)
1063-
1064- def test_logfile(self):
1065- """
1066- A test that logging can be configured properly from the
1067- glance.conf file with the log_file option.
1068-
1069- We start both servers daemonized with a temporary config
1070- file that has some logging options in it.
1071-
1072- We then use curl to issue a few requests and verify that each server's
1073- logging statements were logged to the one log file
1074- """
1075- logfile = "/tmp/test_logfile.log"
1076- self.logfiles.append(logfile)
1077-
1078- if os.path.exists(logfile):
1079- os.unlink(logfile)
1080-
1081- self._cleanup_test_servers()
1082-
1083- # Port numbers hopefully not used by anything...
1084- api_port = 32001
1085- reg_port = 32000
1086- image_dir = "/tmp/test.images.%d" % api_port
1087- if os.path.exists(image_dir):
1088- shutil.rmtree(image_dir)
1089-
1090- # A config file to use just for this test...we don't want
1091- # to trample on currently-running Glance servers, now do we?
1092- with tempfile.NamedTemporaryFile() as conf_file:
1093- conf_contents = """[DEFAULT]
1094-verbose = True
1095-debug = True
1096-log_file = %(logfile)s
1097-
1098-[app:glance-api]
1099-paste.app_factory = glance.server:app_factory
1100-filesystem_store_datadir=%(image_dir)s
1101-default_store = file
1102-bind_host = 0.0.0.0
1103-bind_port = %(api_port)s
1104-registry_host = 0.0.0.0
1105-registry_port = %(reg_port)s
1106-
1107-[app:glance-registry]
1108-paste.app_factory = glance.registry.server:app_factory
1109-bind_host = 0.0.0.0
1110-bind_port = %(reg_port)s
1111-sql_connection = sqlite://
1112-sql_idle_timeout = 3600
1113-""" % locals()
1114- conf_file.write(conf_contents)
1115- conf_file.flush()
1116- conf_file_name = conf_file.name
1117-
1118- venv = ""
1119- if 'VIRTUAL_ENV' in os.environ:
1120- venv = "tools/with_venv.sh "
1121-
1122- # Start up the API and default registry server
1123- cmd = venv + "./bin/glance-control api start "\
1124- "%s --pid-file=glance-api.pid" % conf_file_name
1125- exitcode, out, err = execute(cmd)
1126-
1127- self.assertEquals(0, exitcode)
1128- self.assertTrue("Starting glance-api with" in out)
1129-
1130- cmd = venv + "./bin/glance-control registry start "\
1131- "%s --pid-file=glance-registry.pid" % conf_file_name
1132- exitcode, out, err = execute(cmd)
1133-
1134- self.assertEquals(0, exitcode)
1135- self.assertTrue("Starting glance-registry with" in out)
1136-
1137- time.sleep(2) # Gotta give some time for spin up...
1138-
1139- cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
1140- "-H 'X-Image-Meta-Name: ImageName' "\
1141- "-H 'X-Image-Meta-Disk-Format: Invalid' "\
1142- "http://0.0.0.0:%d/images" % api_port
1143- ignored, out, err = execute(cmd)
1144-
1145- self.assertTrue('Invalid disk format' in out,
1146- "Could not find 'Invalid disk format' "
1147- "in output: %s" % out)
1148-
1149- self.assertTrue(os.path.exists(logfile),
1150- "Logfile %s does not exist!" % logfile)
1151-
1152- logfile_contents = open(logfile, 'rb').read()
1153-
1154- # Check that BOTH the glance API and registry server
1155- # modules are logged to the file.
1156- self.assertTrue('[glance.server]' in logfile_contents,
1157- "Could not find '[glance.server]' "
1158- "in logfile: %s" % logfile_contents)
1159- self.assertTrue('[glance.registry.server]' in logfile_contents,
1160- "Could not find '[glance.registry.server]' "
1161- "in logfile: %s" % logfile_contents)
1162-
1163- # Test that the error we caused above is in the log
1164- self.assertTrue('Invalid disk format' in logfile_contents,
1165- "Could not find 'Invalid disk format' "
1166- "in logfile: %s" % logfile_contents)
1167-
1168- # Spin down the API and default registry server
1169- cmd = "./bin/glance-control api stop "\
1170- "%s --pid-file=glance-api.pid" % conf_file_name
1171- ignored, out, err = execute(cmd)
1172- cmd = "./bin/glance-control registry stop "\
1173- "%s --pid-file=glance-registry.pid" % conf_file_name
1174- ignored, out, err = execute(cmd)
1175+ self.cleanup()
1176+ api_port, reg_port, conf_file = self.start_servers()
1177+
1178+ cmd = "curl -g http://0.0.0.0:%d/images" % api_port
1179+
1180+ exitcode, out, err = execute(cmd)
1181+
1182+ self.assertEqual(0, exitcode)
1183+ self.assertEqual('{"images": []}', out.strip())
1184+
1185+ cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
1186+ "-H 'X-Image-Meta-Name: ImageName' "\
1187+ "-H 'X-Image-Meta-Disk-Format: Invalid' "\
1188+ "http://0.0.0.0:%d/images" % api_port
1189+ ignored, out, err = execute(cmd)
1190+
1191+ self.assertTrue('Invalid disk format' in out,
1192+ "Could not find 'Invalid disk format' "
1193+ "in output: %s" % out)
1194+
1195+ self.stop_servers()
1196
1197=== added file 'tests/unit/test_utils.py'
1198--- tests/unit/test_utils.py 1970-01-01 00:00:00 +0000
1199+++ tests/unit/test_utils.py 2011-03-21 19:26:04 +0000
1200@@ -0,0 +1,67 @@
1201+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1202+
1203+# Copyright 2011 OpenStack, LLC
1204+# All Rights Reserved.
1205+#
1206+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1207+# not use this file except in compliance with the License. You may obtain
1208+# a copy of the License at
1209+#
1210+# http://www.apache.org/licenses/LICENSE-2.0
1211+#
1212+# Unless required by applicable law or agreed to in writing, software
1213+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1214+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1215+# License for the specific language governing permissions and limitations
1216+# under the License.
1217+
1218+import unittest
1219+
1220+from glance import utils
1221+
1222+
1223+class TestUtils(unittest.TestCase):
1224+
1225+ """Test routines in glance.utils"""
1226+
1227+ def test_headers_are_unicode(self):
1228+ """
1229+ Verifies that the headers returned by conversion code are unicode.
1230+
1231+ Headers are passed via http in non-testing mode, which automatically
1232+ converts them to unicode. Verifying that the method does the
1233+ conversion proves that we aren't passing data that works in tests
1234+ but will fail in production.
1235+ """
1236+ fixture = {'name': 'fake public image',
1237+ 'is_public': True,
1238+ 'type': 'kernel',
1239+ 'size': 19,
1240+ 'location': "file:///tmp/glance-tests/2",
1241+ 'properties': {'distro': 'Ubuntu 10.04 LTS'}}
1242+ headers = utils.image_meta_to_http_headers(fixture)
1243+ for k, v in headers.iteritems():
1244+ self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
1245+
1246+ def test_data_passed_properly_through_headers(self):
1247+ """
1248+ Verifies that data is the same after being passed through headers
1249+ """
1250+ fixture = {'name': 'fake public image',
1251+ 'is_public': True,
1252+ 'deleted': False,
1253+ 'type': 'kernel',
1254+ 'name': None,
1255+ 'size': 19,
1256+ 'location': "file:///tmp/glance-tests/2",
1257+ 'properties': {'distro': 'Ubuntu 10.04 LTS'}}
1258+ headers = utils.image_meta_to_http_headers(fixture)
1259+
1260+ class FakeResponse():
1261+ pass
1262+
1263+ response = FakeResponse()
1264+ response.headers = headers
1265+ result = utils.get_image_meta_from_headers(response)
1266+ for k, v in fixture.iteritems():
1267+ self.assertEqual(v, result[k])
1268
1269=== modified file 'tests/utils.py'
1270--- tests/utils.py 2011-01-27 04:19:13 +0000
1271+++ tests/utils.py 2011-03-21 19:26:04 +0000
1272@@ -1,6 +1,6 @@
1273 # vim: tabstop=4 shiftwidth=4 softtabstop=4
1274
1275-# Copyright 2010 OpenStack, LLC
1276+# Copyright 2010-2011 OpenStack, LLC
1277 # All Rights Reserved.
1278 #
1279 # Licensed under the Apache License, Version 2.0 (the "License"); you may
1280@@ -17,11 +17,41 @@
1281
1282 """Common utilities used in testing"""
1283
1284-
1285-def is_swift_available():
1286- """Returns True if Swift/Cloudfiles is importable"""
1287- try:
1288- import swift
1289- return True
1290- except ImportError:
1291- return False
1292+import os
1293+import socket
1294+import subprocess
1295+
1296+
1297+def execute(cmd):
1298+ env = os.environ.copy()
1299+
1300+ # Make sure that we use the programs in the
1301+ # current source directory's bin/ directory.
1302+ env['PATH'] = os.path.join(os.getcwd(), 'bin') + ':' + env['PATH']
1303+ process = subprocess.Popen(cmd,
1304+ shell=True,
1305+ stdin=subprocess.PIPE,
1306+ stdout=subprocess.PIPE,
1307+ stderr=subprocess.PIPE,
1308+ env=env)
1309+ result = process.communicate()
1310+ (out, err) = result
1311+ exitcode = process.returncode
1312+ if process.returncode != 0:
1313+ msg = "Command %(cmd)s did not succeed. Returned an exit "\
1314+ "code of %(exitcode)d."\
1315+ "\n\nSTDOUT: %(out)s"\
1316+ "\n\nSTDERR: %(err)s" % locals()
1317+ raise RuntimeError(msg)
1318+ return exitcode, out, err
1319+
1320+
1321+def get_unused_port():
1322+ """
1323+ Returns an unused port on localhost.
1324+ """
1325+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1326+ s.bind(('localhost', 0))
1327+ addr, port = s.getsockname()
1328+ s.close()
1329+ return port

Subscribers

People subscribed via source and target branches