Browse Source

Merge "Allow glance tests to run on Windows"

tags/18.0.0.0rc1
Zuul 2 months ago
parent
commit
d501799a6a

+ 0
- 1
glance/registry/client/v2/api.py View File

@@ -32,7 +32,6 @@ CONF = cfg.CONF
32 32
 _registry_client = 'glance.registry.client'
33 33
 CONF.import_opt('registry_client_protocol', _registry_client)
34 34
 CONF.import_opt('registry_client_key_file', _registry_client)
35
-CONF.import_opt('registry_client_cert_file', _registry_client)
36 35
 CONF.import_opt('registry_client_ca_file', _registry_client)
37 36
 CONF.import_opt('registry_client_insecure', _registry_client)
38 37
 CONF.import_opt('registry_client_timeout', _registry_client)

+ 9
- 1
glance/tests/__init__.py View File

@@ -13,6 +13,8 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
+import os
17
+
16 18
 import eventlet
17 19
 # NOTE(jokke): As per the eventlet commit
18 20
 # b756447bab51046dfc6f1e0e299cc997ab343701 there's circular import happening
@@ -20,7 +22,13 @@ import eventlet
20 22
 # before calling monkey_patch(). This is solved in eventlet 0.22.0 but we
21 23
 # need to address it before that is widely used around.
22 24
 eventlet.hubs.get_hub()
23
-eventlet.patcher.monkey_patch()
25
+
26
+if os.name == 'nt':
27
+    # eventlet monkey patching the os module causes subprocess.Popen to fail
28
+    # on Windows when using pipes due to missing non-blocking IO support.
29
+    eventlet.patcher.monkey_patch(os=False)
30
+else:
31
+    eventlet.patcher.monkey_patch()
24 32
 
25 33
 # See http://code.google.com/p/python-nose/issues/detail?id=373
26 34
 # The code below enables tests to work with i18n _() blocks

+ 164
- 61
glance/tests/functional/__init__.py View File

@@ -21,6 +21,7 @@ and Registry server, grabbing the logs of each, cleaning up pidfiles,
21 21
 and spinning down the servers.
22 22
 """
23 23
 
24
+import abc
24 25
 import atexit
25 26
 import datetime
26 27
 import errno
@@ -28,12 +29,16 @@ import os
28 29
 import platform
29 30
 import shutil
30 31
 import signal
32
+import six
31 33
 import socket
34
+import subprocess
32 35
 import sys
33 36
 import tempfile
34 37
 import time
35 38
 
36 39
 import fixtures
40
+from os_win import utilsfactory as os_win_utilsfactory
41
+from oslo_config import cfg
37 42
 from oslo_serialization import jsonutils
38 43
 # NOTE(jokke): simplified transition to py3, behaves like py2 xrange
39 44
 from six.moves import range
@@ -48,8 +53,18 @@ from glance.tests import utils as test_utils
48 53
 execute, get_unused_port = test_utils.execute, test_utils.get_unused_port
49 54
 tracecmd_osmap = {'Linux': 'strace', 'FreeBSD': 'truss'}
50 55
 
56
+if os.name == 'nt':
57
+    SQLITE_CONN_TEMPLATE = 'sqlite:///%s/tests.sqlite'
58
+else:
59
+    SQLITE_CONN_TEMPLATE = 'sqlite:////%s/tests.sqlite'
51 60
 
52
-class Server(object):
61
+
62
+CONF = cfg.CONF
63
+CONF.import_opt('registry_host', 'glance.registry')
64
+
65
+
66
+@six.add_metaclass(abc.ABCMeta)
67
+class BaseServer(object):
53 68
     """
54 69
     Class used to easily manage starting and stopping
55 70
     a server during functional test runs.
@@ -131,6 +146,78 @@ class Server(object):
131 146
 
132 147
         return self.conf_file_name, overridden
133 148
 
149
+    @abc.abstractmethod
150
+    def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
151
+        pass
152
+
153
+    @abc.abstractmethod
154
+    def stop(self):
155
+        pass
156
+
157
+    def reload(self, expect_exit=True, expected_exitcode=0, **kwargs):
158
+        """
159
+        Start and stop the service to reload
160
+
161
+        Any kwargs passed to this method will override the configuration
162
+        value in the conf file used in starting the servers.
163
+        """
164
+        self.stop()
165
+        return self.start(expect_exit=expect_exit,
166
+                          expected_exitcode=expected_exitcode, **kwargs)
167
+
168
+    def create_database(self):
169
+        """Create database if required for this server"""
170
+        if self.needs_database:
171
+            conf_dir = os.path.join(self.test_dir, 'etc')
172
+            utils.safe_mkdirs(conf_dir)
173
+            conf_filepath = os.path.join(conf_dir, 'glance-manage.conf')
174
+
175
+            with open(conf_filepath, 'w') as conf_file:
176
+                conf_file.write('[DEFAULT]\n')
177
+                conf_file.write('sql_connection = %s' % self.sql_connection)
178
+                conf_file.flush()
179
+
180
+            glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
181
+            if glance_db_env in os.environ:
182
+                # use the empty db created and cached as a tempfile
183
+                # instead of spending the time creating a new one
184
+                db_location = os.environ[glance_db_env]
185
+                shutil.copyfile(db_location, "%s/tests.sqlite" % self.test_dir)
186
+            else:
187
+                cmd = ('%s -m glance.cmd.manage --config-file %s db sync' %
188
+                       (sys.executable, conf_filepath))
189
+                execute(cmd, no_venv=self.no_venv, exec_env=self.exec_env,
190
+                        expect_exit=True)
191
+
192
+                # copy the clean db to a temp location so that it
193
+                # can be reused for future tests
194
+                (osf, db_location) = tempfile.mkstemp()
195
+                os.close(osf)
196
+                shutil.copyfile('%s/tests.sqlite' % self.test_dir, db_location)
197
+                os.environ[glance_db_env] = db_location
198
+
199
+                # cleanup the temp file when the test suite is
200
+                # complete
201
+                def _delete_cached_db():
202
+                    try:
203
+                        os.remove(os.environ[glance_db_env])
204
+                    except Exception:
205
+                        glance_tests.logger.exception(
206
+                            "Error cleaning up the file %s" %
207
+                            os.environ[glance_db_env])
208
+                atexit.register(_delete_cached_db)
209
+
210
+    def dump_log(self):
211
+        if not self.log_file:
212
+            return "log_file not set for {name}".format(name=self.server_name)
213
+        elif not os.path.exists(self.log_file):
214
+            return "{log_file} for {name} did not exist".format(
215
+                log_file=self.log_file, name=self.server_name)
216
+        with open(self.log_file, 'r') as fptr:
217
+            return fptr.read().strip()
218
+
219
+
220
+class PosixServer(BaseServer):
134 221
     def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
135 222
         """
136 223
         Starts the server.
@@ -190,81 +277,92 @@ class Server(object):
190 277
             self.sock = None
191 278
         return (rc, '', '')
192 279
 
193
-    def reload(self, expect_exit=True, expected_exitcode=0, **kwargs):
280
+    def stop(self):
194 281
         """
195
-        Start and stop the service to reload
282
+        Spin down the server.
283
+        """
284
+        if not self.process_pid:
285
+            raise Exception('why is this being called? %s' % self.server_name)
286
+
287
+        if self.stop_kill:
288
+            os.kill(self.process_pid, signal.SIGTERM)
289
+        rc = test_utils.wait_for_fork(self.process_pid, raise_error=False)
290
+        return (rc, '', '')
291
+
292
+
293
+class Win32Server(BaseServer):
294
+    def __init__(self, *args, **kwargs):
295
+        super(Win32Server, self).__init__(*args, **kwargs)
296
+
297
+        self._processutils = os_win_utilsfactory.get_processutils()
298
+
299
+    def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
300
+        """
301
+        Starts the server.
196 302
 
197 303
         Any kwargs passed to this method will override the configuration
198 304
         value in the conf file used in starting the servers.
199 305
         """
200
-        self.stop()
201
-        return self.start(expect_exit=expect_exit,
202
-                          expected_exitcode=expected_exitcode, **kwargs)
203 306
 
204
-    def create_database(self):
205
-        """Create database if required for this server"""
206
-        if self.needs_database:
207
-            conf_dir = os.path.join(self.test_dir, 'etc')
208
-            utils.safe_mkdirs(conf_dir)
209
-            conf_filepath = os.path.join(conf_dir, 'glance-manage.conf')
307
+        # Ensure the configuration file is written
308
+        self.write_conf(**kwargs)
210 309
 
211
-            with open(conf_filepath, 'w') as conf_file:
212
-                conf_file.write('[DEFAULT]\n')
213
-                conf_file.write('sql_connection = %s' % self.sql_connection)
214
-                conf_file.flush()
310
+        self.create_database()
215 311
 
216
-            glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
217
-            if glance_db_env in os.environ:
218
-                # use the empty db created and cached as a tempfile
219
-                # instead of spending the time creating a new one
220
-                db_location = os.environ[glance_db_env]
221
-                os.system('cp %s %s/tests.sqlite'
222
-                          % (db_location, self.test_dir))
223
-            else:
224
-                cmd = ('%s -m glance.cmd.manage --config-file %s db sync' %
225
-                       (sys.executable, conf_filepath))
226
-                execute(cmd, no_venv=self.no_venv, exec_env=self.exec_env,
227
-                        expect_exit=True)
312
+        cmd = ("%(server_module)s --config-file %(conf_file_name)s"
313
+               % {"server_module": self.server_module,
314
+                  "conf_file_name": self.conf_file_name})
315
+        cmd = "%s -m %s" % (sys.executable, cmd)
228 316
 
229
-                # copy the clean db to a temp location so that it
230
-                # can be reused for future tests
231
-                (osf, db_location) = tempfile.mkstemp()
232
-                os.close(osf)
233
-                os.system('cp %s/tests.sqlite %s'
234
-                          % (self.test_dir, db_location))
235
-                os.environ[glance_db_env] = db_location
317
+        # Passing socket objects on Windows is a bit more cumbersome.
318
+        # We don't really have to do it.
319
+        if self.sock:
320
+            self.sock.close()
321
+            self.sock = None
236 322
 
237
-                # cleanup the temp file when the test suite is
238
-                # complete
239
-                def _delete_cached_db():
240
-                    try:
241
-                        os.remove(os.environ[glance_db_env])
242
-                    except Exception:
243
-                        glance_tests.logger.exception(
244
-                            "Error cleaning up the file %s" %
245
-                            os.environ[glance_db_env])
246
-                atexit.register(_delete_cached_db)
323
+        self.process = subprocess.Popen(
324
+            cmd,
325
+            env=self.exec_env)
326
+        self.process_pid = self.process.pid
327
+
328
+        try:
329
+            self.job_handle = self._processutils.kill_process_on_job_close(
330
+                self.process_pid)
331
+        except Exception:
332
+            # Could not associate child process with a job, killing it.
333
+            self.process.kill()
334
+            raise
335
+
336
+        self.stop_kill = not expect_exit
337
+        if self.pid_file:
338
+            pf = open(self.pid_file, 'w')
339
+            pf.write('%d\n' % self.process_pid)
340
+            pf.close()
341
+
342
+        rc = 0
343
+        if expect_exit:
344
+            self.process.communicate()
345
+            rc = self.process.returncode
346
+
347
+        return (rc, '', '')
247 348
 
248 349
     def stop(self):
249 350
         """
250 351
         Spin down the server.
251 352
         """
252 353
         if not self.process_pid:
253
-            raise Exception('why is this being called? %s' % self.server_name)
354
+            raise Exception('Server "%s" process not running.'
355
+                            % self.server_name)
254 356
 
255 357
         if self.stop_kill:
256
-            os.kill(self.process_pid, signal.SIGTERM)
257
-        rc = test_utils.wait_for_fork(self.process_pid, raise_error=False)
258
-        return (rc, '', '')
358
+            self.process.terminate()
359
+        return (0, '', '')
259 360
 
260
-    def dump_log(self):
261
-        if not self.log_file:
262
-            return "log_file not set for {name}".format(name=self.server_name)
263
-        elif not os.path.exists(self.log_file):
264
-            return "{log_file} for {name} did not exist".format(
265
-                log_file=self.log_file, name=self.server_name)
266
-        with open(self.log_file, 'r') as fptr:
267
-            return fptr.read().strip()
361
+
362
+if os.name == 'nt':
363
+    Server = Win32Server
364
+else:
365
+    Server = PosixServer
268 366
 
269 367
 
270 368
 class ApiServer(Server):
@@ -305,7 +403,7 @@ class ApiServer(Server):
305 403
         self.disable_path = None
306 404
 
307 405
         self.needs_database = True
308
-        default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
406
+        default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
309 407
         self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
310 408
                                              default_sql_connection)
311 409
         self.data_api = kwargs.get("data_api",
@@ -488,7 +586,7 @@ class ApiServerForMultipleBackend(Server):
488 586
         self.disable_path = None
489 587
 
490 588
         self.needs_database = True
491
-        default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
589
+        default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
492 590
         self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
493 591
                                              default_sql_connection)
494 592
         self.data_api = kwargs.get("data_api",
@@ -646,7 +744,7 @@ class RegistryServer(Server):
646 744
         self.server_module = 'glance.cmd.%s' % self.server_name
647 745
 
648 746
         self.needs_database = True
649
-        default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
747
+        default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
650 748
         self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
651 749
                                              default_sql_connection)
652 750
 
@@ -732,7 +830,7 @@ class ScrubberDaemon(Server):
732 830
         self.metadata_encryption_key = "012345678901234567890123456789ab"
733 831
         self.lock_path = self.test_dir
734 832
 
735
-        default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
833
+        default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
736 834
         self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
737 835
                                              default_sql_connection)
738 836
         self.policy_file = policy_file
@@ -790,6 +888,11 @@ class FunctionalTest(test_utils.BaseTestCase):
790 888
         # False in the test SetUps that do not require Scrubber to run.
791 889
         self.include_scrubber = True
792 890
 
891
+        # The clients will try to connect to this address. Let's make sure
892
+        # we're not using the default '0.0.0.0'
893
+        self.config(bind_host='127.0.0.1',
894
+                    registry_host='127.0.0.1')
895
+
793 896
         self.tracecmd = tracecmd_osmap.get(platform.system())
794 897
 
795 898
         conf_dir = os.path.join(self.test_dir, 'etc')

+ 5
- 0
glance/tests/functional/db/base.py View File

@@ -17,6 +17,7 @@
17 17
 
18 18
 import copy
19 19
 import datetime
20
+import time
20 21
 import uuid
21 22
 
22 23
 import mock
@@ -1340,6 +1341,10 @@ class DriverTests(object):
1340 1341
                     'deleted': False}
1341 1342
         self.assertEqual(expected, member)
1342 1343
 
1344
+        # The clock may not be very accurate, for which reason we may
1345
+        # get identical timestamps.
1346
+        time.sleep(0.01)
1347
+
1343 1348
         member = self.db_api.image_member_update(self.context,
1344 1349
                                                  member_id,
1345 1350
                                                  {'status': 'accepted'})

+ 2
- 0
glance/tests/functional/serial/test_scrubber.py View File

@@ -346,6 +346,8 @@ class TestScrubber(functional.FunctionalTest):
346 346
     def test_scrubber_restore_image_with_daemon_running(self):
347 347
         self.cleanup()
348 348
         self.scrubber_daemon.start(daemon=True)
349
+        # Give the scrubber some time to start.
350
+        time.sleep(5)
349 351
 
350 352
         exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
351 353
         cmd = ("%s --restore fake_image_id" % exe_cmd)

+ 13
- 13
glance/tests/functional/test_cache_middleware.py View File

@@ -45,7 +45,7 @@ class BaseCacheMiddlewareTest(object):
45 45
         self.start_servers(**self.__dict__.copy())
46 46
 
47 47
         # Add an image and verify success
48
-        path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
48
+        path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
49 49
         http = httplib2.Http()
50 50
         headers = {'content-type': 'application/json'}
51 51
         image_entity = {
@@ -61,7 +61,7 @@ class BaseCacheMiddlewareTest(object):
61 61
         data = jsonutils.loads(content)
62 62
         image_id = data['id']
63 63
 
64
-        path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
64
+        path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
65 65
                                                    image_id)
66 66
         headers = {'content-type': 'application/octet-stream'}
67 67
         image_data = "*" * FIVE_KB
@@ -87,7 +87,7 @@ class BaseCacheMiddlewareTest(object):
87 87
 
88 88
         # Now, we delete the image from the server and verify that
89 89
         # the image cache no longer contains the deleted image
90
-        path = "http://%s:%d/v2/images/%s" % ("0.0.0.0", self.api_port,
90
+        path = "http://%s:%d/v2/images/%s" % ("127.0.0.1", self.api_port,
91 91
                                               image_id)
92 92
         http = httplib2.Http()
93 93
         response, content = http.request(path, 'DELETE')
@@ -107,7 +107,7 @@ class BaseCacheMiddlewareTest(object):
107 107
         self.start_servers(**self.__dict__.copy())
108 108
 
109 109
         # Add an image and verify success
110
-        path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
110
+        path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
111 111
         http = httplib2.Http()
112 112
         headers = {'content-type': 'application/json'}
113 113
         image_entity = {
@@ -123,7 +123,7 @@ class BaseCacheMiddlewareTest(object):
123 123
         data = jsonutils.loads(content)
124 124
         image_id = data['id']
125 125
 
126
-        path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
126
+        path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
127 127
                                                    image_id)
128 128
         headers = {'content-type': 'application/octet-stream'}
129 129
         image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
@@ -187,7 +187,7 @@ class BaseCacheMiddlewareTest(object):
187 187
         self.start_servers(**self.__dict__.copy())
188 188
 
189 189
         # Add an image and verify success
190
-        path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
190
+        path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
191 191
         http = httplib2.Http()
192 192
         headers = {'content-type': 'application/json'}
193 193
         image_entity = {
@@ -203,7 +203,7 @@ class BaseCacheMiddlewareTest(object):
203 203
         data = jsonutils.loads(content)
204 204
         image_id = data['id']
205 205
 
206
-        path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
206
+        path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
207 207
                                                    image_id)
208 208
         headers = {'content-type': 'application/octet-stream'}
209 209
         image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
@@ -283,7 +283,7 @@ class BaseCacheMiddlewareTest(object):
283 283
         self.start_servers(**self.__dict__.copy())
284 284
 
285 285
         # Add an image and verify success
286
-        path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
286
+        path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
287 287
         http = httplib2.Http()
288 288
         headers = {'content-type': 'application/json'}
289 289
         image_entity = {
@@ -299,7 +299,7 @@ class BaseCacheMiddlewareTest(object):
299 299
         data = jsonutils.loads(content)
300 300
         image_id = data['id']
301 301
 
302
-        path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
302
+        path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
303 303
                                                    image_id)
304 304
         headers = {'content-type': 'application/octet-stream'}
305 305
         image_data = "*" * FIVE_KB
@@ -324,7 +324,7 @@ class BaseCacheMiddlewareTest(object):
324 324
 
325 325
         # Now, we delete the image from the server and verify that
326 326
         # the image cache no longer contains the deleted image
327
-        path = "http://%s:%d/v2/images/%s" % ("0.0.0.0", self.api_port,
327
+        path = "http://%s:%d/v2/images/%s" % ("127.0.0.1", self.api_port,
328 328
                                               image_id)
329 329
         http = httplib2.Http()
330 330
         response, content = http.request(path, 'DELETE')
@@ -347,7 +347,7 @@ class TestImageCacheXattr(functional.FunctionalTest,
347 347
         filesystem)
348 348
         """
349 349
         if getattr(self, 'disabled', False):
350
-            return
350
+            raise self.skipException('Test disabled.')
351 351
 
352 352
         if not getattr(self, 'inited', False):
353 353
             try:
@@ -356,7 +356,7 @@ class TestImageCacheXattr(functional.FunctionalTest,
356 356
                 self.inited = True
357 357
                 self.disabled = True
358 358
                 self.disabled_message = ("python-xattr not installed.")
359
-                return
359
+                raise self.skipException(self.disabled_message)
360 360
 
361 361
         self.inited = True
362 362
         self.disabled = False
@@ -370,7 +370,7 @@ class TestImageCacheXattr(functional.FunctionalTest,
370 370
             self.inited = True
371 371
             self.disabled = True
372 372
             self.disabled_message = ("filesystem does not support xattr")
373
-            return
373
+            raise self.skipException(self.disabled_message)
374 374
 
375 375
     def tearDown(self):
376 376
         super(TestImageCacheXattr, self).tearDown()

+ 0
- 1
glance/tests/functional/test_client_exceptions.py View File

@@ -28,7 +28,6 @@ from glance.common import wsgi
28 28
 from glance.tests import functional
29 29
 from glance.tests import utils
30 30
 
31
-
32 31
 eventlet.patcher.monkey_patch(socket=True)
33 32
 
34 33
 

+ 6
- 0
glance/tests/functional/test_logging.py View File

@@ -85,6 +85,12 @@ class TestLogging(functional.FunctionalTest):
85 85
         """
86 86
         Test that we notice when our log file has been rotated
87 87
         """
88
+
89
+        # Moving in-use files is not supported on Windows.
90
+        # The log handler itself may be configured to rotate files.
91
+        if os.name == 'nt':
92
+            raise self.skipException("Unsupported platform.")
93
+
88 94
         self.cleanup()
89 95
         self.start_servers()
90 96
 

+ 1
- 1
glance/tests/functional/test_sqlite.py View File

@@ -32,7 +32,7 @@ class TestSqlite(functional.FunctionalTest):
32 32
         self.cleanup()
33 33
         self.start_servers(**self.__dict__.copy())
34 34
 
35
-        cmd = "sqlite3 tests.sqlite '.schema'"
35
+        cmd = 'sqlite3 tests.sqlite ".schema"'
36 36
         exitcode, out, err = execute(cmd, raise_error=True)
37 37
 
38 38
         self.assertNotIn('BIGINT', out)

+ 7
- 1
glance/tests/functional/test_wsgi.py View File

@@ -15,6 +15,7 @@
15 15
 
16 16
 """Tests for `glance.wsgi`."""
17 17
 
18
+import os
18 19
 import socket
19 20
 import time
20 21
 
@@ -52,4 +53,9 @@ class TestWSGIServer(testtools.TestCase):
52 53
         # Should succeed - no timeout
53 54
         self.assertIn(greetings, get_request())
54 55
         # Should fail - connection timed out so we get nothing from the server
55
-        self.assertFalse(get_request(delay=1.1))
56
+        if os.name == 'nt':
57
+            self.assertRaises(ConnectionAbortedError,
58
+                              get_request,
59
+                              delay=1.1)
60
+        else:
61
+            self.assertFalse(get_request(delay=1.1))

+ 34
- 29
glance/tests/functional/v2/test_images.py View File

@@ -14,8 +14,6 @@
14 14
 #    under the License.
15 15
 
16 16
 import hashlib
17
-import os
18
-import signal
19 17
 import uuid
20 18
 
21 19
 from oslo_serialization import jsonutils
@@ -48,16 +46,17 @@ class TestImages(functional.FunctionalTest):
48 46
         for i in range(3):
49 47
             ret = test_utils.start_http_server("foo_image_id%d" % i,
50 48
                                                "foo_image%d" % i)
51
-            setattr(self, 'http_server%d_pid' % i, ret[0])
52
-            setattr(self, 'http_port%d' % i, ret[1])
49
+            setattr(self, 'http_server%d' % i, ret[1])
50
+            setattr(self, 'http_port%d' % i, ret[2])
53 51
         self.api_server.use_user_token = True
54 52
         self.api_server.send_identity_credentials = True
55 53
 
56 54
     def tearDown(self):
57 55
         for i in range(3):
58
-            pid = getattr(self, 'http_server%d_pid' % i, None)
59
-            if pid:
60
-                os.kill(pid, signal.SIGKILL)
56
+            httpd = getattr(self, 'http_server%d' % i, None)
57
+            if httpd:
58
+                httpd.shutdown()
59
+                httpd.server_close()
61 60
 
62 61
         super(TestImages, self).tearDown()
63 62
 
@@ -219,7 +218,7 @@ class TestImages(functional.FunctionalTest):
219 218
         func_utils.wait_for_status(request_path=path,
220 219
                                    request_headers=self._headers(),
221 220
                                    status='active',
222
-                                   max_sec=2,
221
+                                   max_sec=10,
223 222
                                    delay_sec=0.2)
224 223
         expect_c = six.text_type(hashlib.md5(image_data).hexdigest())
225 224
         expect_h = six.text_type(hashlib.sha512(image_data).hexdigest())
@@ -343,7 +342,7 @@ class TestImages(functional.FunctionalTest):
343 342
         })
344 343
 
345 344
         # Start http server locally
346
-        pid, port = test_utils.start_standalone_http_server()
345
+        thread, httpd, port = test_utils.start_standalone_http_server()
347 346
 
348 347
         image_data_uri = 'http://localhost:%s/' % port
349 348
         data = jsonutils.dumps({'method': {
@@ -373,7 +372,8 @@ class TestImages(functional.FunctionalTest):
373 372
                                                   status='active')
374 373
 
375 374
         # kill the local http server
376
-        os.kill(pid, signal.SIGKILL)
375
+        httpd.shutdown()
376
+        httpd.server_close()
377 377
 
378 378
         # Deleting image should work
379 379
         path = self._url('/v2/images/%s' % image_id)
@@ -1609,8 +1609,8 @@ class TestImages(functional.FunctionalTest):
1609 1609
         path = self._url('/v2/images/%s' % image_id)
1610 1610
         media_type = 'application/openstack-images-v2.1-json-patch'
1611 1611
         headers = self._headers({'content-type': media_type})
1612
-        http_server_pid, http_port = test_utils.start_http_server(image_id,
1613
-                                                                  "image-1")
1612
+        thread, httpd, http_port = test_utils.start_http_server(image_id,
1613
+                                                                "image-1")
1614 1614
         values = [{'url': 'http://127.0.0.1:%s/image-1' % http_port,
1615 1615
                    'metadata': {'idx': '0'}}]
1616 1616
         doc = [{'op': 'replace',
@@ -1627,7 +1627,8 @@ class TestImages(functional.FunctionalTest):
1627 1627
         self.assertEqual(http.OK, response.status_code)
1628 1628
 
1629 1629
         # Stop http server used to update image location
1630
-        os.kill(http_server_pid, signal.SIGKILL)
1630
+        httpd.shutdown()
1631
+        httpd.server_close()
1631 1632
 
1632 1633
         # Download an image should raise HTTPServiceUnavailable
1633 1634
         path = self._url('/v2/images/%s/file' % image_id)
@@ -3895,14 +3896,15 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
3895 3896
         for i in range(3):
3896 3897
             ret = test_utils.start_http_server("foo_image_id%d" % i,
3897 3898
                                                "foo_image%d" % i)
3898
-            setattr(self, 'http_server%d_pid' % i, ret[0])
3899
-            setattr(self, 'http_port%d' % i, ret[1])
3899
+            setattr(self, 'http_server%d' % i, ret[1])
3900
+            setattr(self, 'http_port%d' % i, ret[2])
3900 3901
 
3901 3902
     def tearDown(self):
3902 3903
         for i in range(3):
3903
-            pid = getattr(self, 'http_server%d_pid' % i, None)
3904
-            if pid:
3905
-                os.kill(pid, signal.SIGKILL)
3904
+            httpd = getattr(self, 'http_server%d' % i, None)
3905
+            if httpd:
3906
+                httpd.shutdown()
3907
+                httpd.server_close()
3906 3908
 
3907 3909
         super(TestImageLocationSelectionStrategy, self).tearDown()
3908 3910
 
@@ -4453,14 +4455,15 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4453 4455
         for i in range(3):
4454 4456
             ret = test_utils.start_http_server("foo_image_id%d" % i,
4455 4457
                                                "foo_image%d" % i)
4456
-            setattr(self, 'http_server%d_pid' % i, ret[0])
4457
-            setattr(self, 'http_port%d' % i, ret[1])
4458
+            setattr(self, 'http_server%d' % i, ret[1])
4459
+            setattr(self, 'http_port%d' % i, ret[2])
4458 4460
 
4459 4461
     def tearDown(self):
4460 4462
         for i in range(3):
4461
-            pid = getattr(self, 'http_server%d_pid' % i, None)
4462
-            if pid:
4463
-                os.kill(pid, signal.SIGKILL)
4463
+            httpd = getattr(self, 'http_server%d' % i, None)
4464
+            if httpd:
4465
+                httpd.shutdown()
4466
+                httpd.server_close()
4464 4467
 
4465 4468
         super(TestImagesMultipleBackend, self).tearDown()
4466 4469
 
@@ -4605,7 +4608,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4605 4608
         func_utils.wait_for_status(request_path=path,
4606 4609
                                    request_headers=self._headers(),
4607 4610
                                    status='active',
4608
-                                   max_sec=2,
4611
+                                   max_sec=15,
4609 4612
                                    delay_sec=0.2)
4610 4613
         expect_c = six.text_type(hashlib.md5(image_data).hexdigest())
4611 4614
         expect_h = six.text_type(hashlib.sha512(image_data).hexdigest())
@@ -4766,7 +4769,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4766 4769
         func_utils.wait_for_status(request_path=path,
4767 4770
                                    request_headers=self._headers(),
4768 4771
                                    status='active',
4769
-                                   max_sec=2,
4772
+                                   max_sec=15,
4770 4773
                                    delay_sec=0.2)
4771 4774
         expect_c = six.text_type(hashlib.md5(image_data).hexdigest())
4772 4775
         expect_h = six.text_type(hashlib.sha512(image_data).hexdigest())
@@ -4909,7 +4912,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4909 4912
         })
4910 4913
 
4911 4914
         # Start http server locally
4912
-        pid, port = test_utils.start_standalone_http_server()
4915
+        thread, httpd, port = test_utils.start_standalone_http_server()
4913 4916
 
4914 4917
         image_data_uri = 'http://localhost:%s/' % port
4915 4918
         data = jsonutils.dumps({'method': {
@@ -4939,7 +4942,8 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4939 4942
                                                   status='active')
4940 4943
 
4941 4944
         # kill the local http server
4942
-        os.kill(pid, signal.SIGKILL)
4945
+        httpd.shutdown()
4946
+        httpd.server_close()
4943 4947
         # Ensure image is created in default backend
4944 4948
         path = self._url('/v2/images/%s' % image_id)
4945 4949
         response = requests.get(path, headers=self._headers())
@@ -5069,7 +5073,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
5069 5073
         })
5070 5074
 
5071 5075
         # Start http server locally
5072
-        pid, port = test_utils.start_standalone_http_server()
5076
+        thread, httpd, port = test_utils.start_standalone_http_server()
5073 5077
 
5074 5078
         image_data_uri = 'http://localhost:%s/' % port
5075 5079
         data = jsonutils.dumps({'method': {
@@ -5099,7 +5103,8 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
5099 5103
                                                   status='active')
5100 5104
 
5101 5105
         # kill the local http server
5102
-        os.kill(pid, signal.SIGKILL)
5106
+        httpd.shutdown()
5107
+        httpd.server_close()
5103 5108
 
5104 5109
         # Ensure image is created in different backend
5105 5110
         path = self._url('/v2/images/%s' % image_id)

+ 3
- 4
glance/tests/integration/v2/base.py View File

@@ -15,6 +15,7 @@
15 15
 
16 16
 import atexit
17 17
 import os.path
18
+import shutil
18 19
 import tempfile
19 20
 
20 21
 import fixtures
@@ -163,8 +164,7 @@ class ApiTest(test_utils.BaseTestCase):
163 164
             # use the empty db created and cached as a tempfile
164 165
             # instead of spending the time creating a new one
165 166
             db_location = os.environ[glance_db_env]
166
-            test_utils.execute('cp %s %s/tests.sqlite'
167
-                               % (db_location, self.test_dir))
167
+            shutil.copyfile(db_location, "%s/tests.sqlite" % self.test_dir)
168 168
         else:
169 169
             test_utils.db_sync()
170 170
 
@@ -172,8 +172,7 @@ class ApiTest(test_utils.BaseTestCase):
172 172
             # can be reused for future tests
173 173
             (osf, db_location) = tempfile.mkstemp()
174 174
             os.close(osf)
175
-            test_utils.execute('cp %s/tests.sqlite %s'
176
-                               % (self.test_dir, db_location))
175
+            shutil.copyfile('%s/tests.sqlite' % self.test_dir, db_location)
177 176
             os.environ[glance_db_env] = db_location
178 177
 
179 178
             # cleanup the temp file when the test suite is

+ 2
- 2
glance/tests/unit/async_/flows/test_import.py View File

@@ -135,8 +135,8 @@ class TestImportTask(test_utils.BaseTestCase):
135 135
                 self.assertFalse(os.path.exists(tmp_image_path))
136 136
                 self.assertTrue(os.path.exists(image_path))
137 137
                 self.assertEqual(1, len(list(self.image.locations)))
138
-                self.assertEqual("file://%s/%s" % (self.test_dir,
139
-                                                   self.image.image_id),
138
+                self.assertEqual("file://%s%s%s" % (self.test_dir, os.sep,
139
+                                                    self.image.image_id),
140 140
                                  self.image.locations[0]['url'])
141 141
 
142 142
                 self._assert_qemu_process_limits(tmock)

+ 2
- 4
glance/tests/unit/base.py View File

@@ -108,11 +108,9 @@ class IsolatedUnitTest(StoreClearingUnitTest):
108 108
             DEFAULT_REGISTRY_PORT = 9191
109 109
             DEFAULT_API_PORT = 9292
110 110
 
111
-            if (client.port == DEFAULT_API_PORT and
112
-                    client.host == '0.0.0.0'):
111
+            if client.port == DEFAULT_API_PORT:
113 112
                 return stubs.FakeGlanceConnection
114
-            elif (client.port == DEFAULT_REGISTRY_PORT and
115
-                  client.host == '0.0.0.0'):
113
+            elif client.port == DEFAULT_REGISTRY_PORT:
116 114
                 return stubs.FakeRegistryConnection(registry=self.registry)
117 115
 
118 116
         self.patcher = mock.patch(

+ 4
- 1
glance/tests/unit/common/test_wsgi.py View File

@@ -588,8 +588,11 @@ class ServerTest(test_utils.BaseTestCase):
588 588
                                                 keepalive=False,
589 589
                                                 socket_timeout=900)
590 590
 
591
-    def test_number_of_workers(self):
591
+    def test_number_of_workers_posix(self):
592 592
         """Ensure the number of workers matches num cpus limited to 8."""
593
+        if os.name == 'nt':
594
+            raise self.skipException("Unsupported platform.")
595
+
593 596
         def pid():
594 597
             i = 1
595 598
             while True:

+ 7
- 0
glance/tests/unit/test_image_cache.py View File

@@ -281,6 +281,7 @@ class ImageCacheTestCase(object):
281 281
         self.assertEqual(['0', '1', '2'],
282 282
                          self.cache.get_queued_images())
283 283
 
284
+    @skip_if_disabled
284 285
     def test_open_for_write_good(self):
285 286
         """
286 287
         Test to see if open_for_write works in normal case
@@ -300,6 +301,7 @@ class ImageCacheTestCase(object):
300 301
         self.assertFalse(os.path.exists(incomplete_file_path))
301 302
         self.assertFalse(os.path.exists(invalid_file_path))
302 303
 
304
+    @skip_if_disabled
303 305
     def test_open_for_write_with_exception(self):
304 306
         """
305 307
         Test to see if open_for_write works in a failure case for each driver
@@ -324,6 +326,7 @@ class ImageCacheTestCase(object):
324 326
         self.assertFalse(os.path.exists(incomplete_file_path))
325 327
         self.assertTrue(os.path.exists(invalid_file_path))
326 328
 
329
+    @skip_if_disabled
327 330
     def test_caching_iterator(self):
328 331
         """
329 332
         Test to see if the caching iterator interacts properly with the driver
@@ -351,6 +354,7 @@ class ImageCacheTestCase(object):
351 354
         self.assertFalse(os.path.exists(incomplete_file_path))
352 355
         self.assertFalse(os.path.exists(invalid_file_path))
353 356
 
357
+    @skip_if_disabled
354 358
     def test_caching_iterator_handles_backend_failure(self):
355 359
         """
356 360
         Test that when the backend fails, caching_iter does not continue trying
@@ -374,6 +378,7 @@ class ImageCacheTestCase(object):
374 378
         # make sure bad image was not cached
375 379
         self.assertFalse(self.cache.is_cached(image_id))
376 380
 
381
+    @skip_if_disabled
377 382
     def test_caching_iterator_falloffend(self):
378 383
         """
379 384
         Test to see if the caching iterator interacts properly with the driver
@@ -402,6 +407,7 @@ class ImageCacheTestCase(object):
402 407
         self.assertFalse(os.path.exists(incomplete_file_path))
403 408
         self.assertTrue(os.path.exists(invalid_file_path))
404 409
 
410
+    @skip_if_disabled
405 411
     def test_gate_caching_iter_good_checksum(self):
406 412
         image = b"12345678990abcdefghijklmnop"
407 413
         image_id = 123
@@ -417,6 +423,7 @@ class ImageCacheTestCase(object):
417 423
         # checksum is valid, fake image should be cached:
418 424
         self.assertTrue(cache.is_cached(image_id))
419 425
 
426
+    @skip_if_disabled
420 427
     def test_gate_caching_iter_bad_checksum(self):
421 428
         image = b"12345678990abcdefghijklmnop"
422 429
         image_id = 123

+ 20
- 8
glance/tests/unit/v2/test_images_resource.py View File

@@ -165,7 +165,9 @@ class TestImagesController(base.IsolatedUnitTest):
165 165
                                     'metadata': {}, 'status': 'active'}],
166 166
                         disk_format='raw',
167 167
                         container_format='bare',
168
-                        status='active'),
168
+                        status='active',
169
+                        created_at=DATETIME,
170
+                        updated_at=DATETIME),
169 171
             _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
170 172
                         os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2,
171 173
                         name='2', size=512, virtual_size=2048,
@@ -175,13 +177,19 @@ class TestImagesController(base.IsolatedUnitTest):
175 177
                         status='active',
176 178
                         tags=['redhat', '64bit', 'power'],
177 179
                         properties={'hypervisor_type': 'kvm', 'foo': 'bar',
178
-                                    'bar': 'foo'}),
180
+                                    'bar': 'foo'},
181
+                        created_at=DATETIME + datetime.timedelta(seconds=1),
182
+                        updated_at=DATETIME + datetime.timedelta(seconds=1)),
179 183
             _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
180 184
                         os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2,
181 185
                         name='3', size=512, virtual_size=2048,
182
-                        visibility='public', tags=['windows', '64bit', 'x86']),
186
+                        visibility='public', tags=['windows', '64bit', 'x86'],
187
+                        created_at=DATETIME + datetime.timedelta(seconds=2),
188
+                        updated_at=DATETIME + datetime.timedelta(seconds=2)),
183 189
             _db_fixture(UUID4, owner=TENANT4, name='4',
184
-                        size=1024, virtual_size=3072),
190
+                        size=1024, virtual_size=3072,
191
+                        created_at=DATETIME + datetime.timedelta(seconds=3),
192
+                        updated_at=DATETIME + datetime.timedelta(seconds=3)),
185 193
         ]
186 194
         [self.db.image_create(None, image) for image in self.images]
187 195
 
@@ -4649,7 +4657,8 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
4649 4657
                                     'metadata': {}, 'status': 'active'}],
4650 4658
                         disk_format='raw',
4651 4659
                         container_format='bare',
4652
-                        status='active'),
4660
+                        status='active',
4661
+                        created_at=DATETIME),
4653 4662
             _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
4654 4663
                         name='2', size=512, virtual_size=2048,
4655 4664
                         visibility='public',
@@ -4658,12 +4667,15 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
4658 4667
                         status='active',
4659 4668
                         tags=['redhat', '64bit', 'power'],
4660 4669
                         properties={'hypervisor_type': 'kvm', 'foo': 'bar',
4661
-                                    'bar': 'foo'}),
4670
+                                    'bar': 'foo'},
4671
+                        created_at=DATETIME + datetime.timedelta(seconds=1)),
4662 4672
             _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
4663 4673
                         name='3', size=512, virtual_size=2048,
4664
-                        visibility='public', tags=['windows', '64bit', 'x86']),
4674
+                        visibility='public', tags=['windows', '64bit', 'x86'],
4675
+                        created_at=DATETIME + datetime.timedelta(seconds=2)),
4665 4676
             _db_fixture(UUID4, owner=TENANT4, name='4',
4666
-                        size=1024, virtual_size=3072),
4677
+                        size=1024, virtual_size=3072,
4678
+                        created_at=DATETIME + datetime.timedelta(seconds=3)),
4667 4679
         ]
4668 4680
         [self.db.image_create(None, image) for image in self.images]
4669 4681
 

+ 1
- 1
glance/tests/unit/v2/test_registry_client.py View File

@@ -79,7 +79,7 @@ class TestRegistryV2Client(base.IsolatedUnitTest,
79 79
                                    created_at=uuid2_time)]
80 80
         self.destroy_fixtures()
81 81
         self.create_fixtures()
82
-        self.client = rclient.RegistryClient("0.0.0.0")
82
+        self.client = rclient.RegistryClient("127.0.0.1")
83 83
 
84 84
     def tearDown(self):
85 85
         """Clear the test environment"""

+ 22
- 13
glance/tests/utils.py View File

@@ -22,6 +22,7 @@ import shlex
22 22
 import shutil
23 23
 import socket
24 24
 import subprocess
25
+import threading
25 26
 
26 27
 from alembic import command as alembic_command
27 28
 import fixtures
@@ -176,7 +177,11 @@ class depends_on_exe(object):
176 177
 
177 178
     def __call__(self, func):
178 179
         def _runner(*args, **kw):
179
-            cmd = 'which %s' % self.exe
180
+            if os.name != 'nt':
181
+                cmd = 'which %s' % self.exe
182
+            else:
183
+                cmd = 'where.exe', '%s' % self.exe
184
+
180 185
             exitcode, out, err = execute(cmd, raise_error=False)
181 186
             if exitcode != 0:
182 187
                 args[0].disabled_message = 'test requires exe: %s' % self.exe
@@ -325,7 +330,11 @@ def execute(cmd,
325 330
     path_ext = [os.path.join(os.getcwd(), 'bin')]
326 331
 
327 332
     # Also jack in the path cmd comes from, if it's absolute
328
-    args = shlex.split(cmd)
333
+    if os.name != 'nt':
334
+        args = shlex.split(cmd)
335
+    else:
336
+        args = cmd
337
+
329 338
     executable = args[0]
330 339
     if os.path.isabs(executable):
331 340
         path_ext.append(os.path.dirname(executable))
@@ -484,7 +493,7 @@ def start_http_server(image_id, image_data):
484 493
                 self.send_response(http.OK)
485 494
                 self.send_header('Content-Length', str(len(fixture)))
486 495
                 self.end_headers()
487
-                self.wfile.write(fixture)
496
+                self.wfile.write(six.b(fixture))
488 497
                 return
489 498
 
490 499
             def do_HEAD(self):
@@ -510,11 +519,11 @@ def start_http_server(image_id, image_data):
510 519
     httpd = BaseHTTPServer.HTTPServer(server_address, handler_class)
511 520
     port = httpd.socket.getsockname()[1]
512 521
 
513
-    pid = os.fork()
514
-    if pid == 0:
515
-        httpd.serve_forever()
516
-    else:
517
-        return pid, port
522
+    thread = threading.Thread(target=httpd.serve_forever)
523
+    thread.daemon = True
524
+    thread.start()
525
+
526
+    return thread, httpd, port
518 527
 
519 528
 
520 529
 class RegistryAPIMixIn(object):
@@ -730,8 +739,8 @@ def start_standalone_http_server():
730 739
     httpd = BaseHTTPServer.HTTPServer(server_address, handler_class)
731 740
     port = httpd.socket.getsockname()[1]
732 741
 
733
-    pid = os.fork()
734
-    if pid == 0:
735
-        httpd.serve_forever()
736
-    else:
737
-        return pid, port
742
+    thread = threading.Thread(target=httpd.serve_forever)
743
+    thread.daemon = True
744
+    thread.start()
745
+
746
+    return thread, httpd, port

Loading…
Cancel
Save