summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbhishek Kekane <akekane@redhat.com>2018-06-22 16:07:31 +0000
committerAbhishek Kekane <akekane@redhat.com>2018-08-01 08:57:56 +0000
commit73109de485b6d517c25456f56884627d56990acd (patch)
treedcd3823ec55b492b105346c65f7d7331851bb13f
parentcb45edf5c81f7d09c9ef0b88d40d56b4750beb10 (diff)
Unit/Functional tests for multi store support
Added some unit tests for coverage purpose. Added functional tests for create and import scenarios. Note: For functional tests I have considered file store with two different image directories. Related to blueprint multi-store Change-Id: I59e28ab822fb5f6940f48ddbf6dfba4cb7d4c509
Notes
Notes (review): Code-Review+2: Brian Rosmaita <rosmaita.fossdev@gmail.com> Code-Review+2: Sean McGinnis <sean.mcginnis@gmail.com> Workflow+1: Sean McGinnis <sean.mcginnis@gmail.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 07 Aug 2018 23:15:53 +0000 Reviewed-on: https://review.openstack.org/577502 Project: openstack/glance Branch: refs/heads/master
-rw-r--r--glance/tests/functional/__init__.py556
-rw-r--r--glance/tests/functional/v2/test_images.py1020
-rw-r--r--glance/tests/unit/base.py52
-rw-r--r--glance/tests/unit/v2/test_discovery_stores.py48
-rw-r--r--glance/tests/unit/v2/test_image_data_resource.py32
-rw-r--r--glance/tests/unit/v2/test_images_resource.py109
6 files changed, 1817 insertions, 0 deletions
diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py
index cbe94ec..d6ac835 100644
--- a/glance/tests/functional/__init__.py
+++ b/glance/tests/functional/__init__.py
@@ -445,6 +445,191 @@ allowed_origin=http://valid.example.com
445""" 445"""
446 446
447 447
448class ApiServerForMultipleBackend(Server):
449
450 """
451 Server object that starts/stops/manages the API server
452 """
453
454 def __init__(self, test_dir, port, policy_file, delayed_delete=False,
455 pid_file=None, sock=None, **kwargs):
456 super(ApiServerForMultipleBackend, self).__init__(
457 test_dir, port, sock=sock)
458 self.server_name = 'api'
459 self.server_module = 'glance.cmd.%s' % self.server_name
460 self.default_backend = kwargs.get("default_backend", "file1")
461 self.bind_host = "127.0.0.1"
462 self.registry_host = "127.0.0.1"
463 self.key_file = ""
464 self.cert_file = ""
465 self.metadata_encryption_key = "012345678901234567890123456789ab"
466 self.image_dir_backend_1 = os.path.join(self.test_dir, "images_1")
467 self.image_dir_backend_2 = os.path.join(self.test_dir, "images_2")
468 self.pid_file = pid_file or os.path.join(self.test_dir,
469 "multiple_backend_api.pid")
470 self.log_file = os.path.join(self.test_dir, "multiple_backend_api.log")
471 self.image_size_cap = 1099511627776
472 self.delayed_delete = delayed_delete
473 self.owner_is_tenant = True
474 self.workers = 0
475 self.scrub_time = 5
476 self.image_cache_dir = os.path.join(self.test_dir,
477 'cache')
478 self.image_cache_driver = 'sqlite'
479 self.policy_file = policy_file
480 self.policy_default_rule = 'default'
481 self.property_protection_rule_format = 'roles'
482 self.image_member_quota = 10
483 self.image_property_quota = 10
484 self.image_tag_quota = 10
485 self.image_location_quota = 2
486 self.disable_path = None
487
488 self.needs_database = True
489 default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
490 self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
491 default_sql_connection)
492 self.data_api = kwargs.get("data_api",
493 "glance.db.sqlalchemy.api")
494 self.user_storage_quota = '0'
495 self.lock_path = self.test_dir
496
497 self.location_strategy = 'location_order'
498 self.store_type_location_strategy_preference = ""
499
500 self.send_identity_headers = False
501
502 self.conf_base = """[DEFAULT]
503debug = %(debug)s
504default_log_levels = eventlet.wsgi.server=DEBUG
505bind_host = %(bind_host)s
506bind_port = %(bind_port)s
507key_file = %(key_file)s
508cert_file = %(cert_file)s
509metadata_encryption_key = %(metadata_encryption_key)s
510registry_host = %(registry_host)s
511registry_port = %(registry_port)s
512use_user_token = %(use_user_token)s
513send_identity_credentials = %(send_identity_credentials)s
514log_file = %(log_file)s
515image_size_cap = %(image_size_cap)d
516delayed_delete = %(delayed_delete)s
517owner_is_tenant = %(owner_is_tenant)s
518workers = %(workers)s
519scrub_time = %(scrub_time)s
520send_identity_headers = %(send_identity_headers)s
521image_cache_dir = %(image_cache_dir)s
522image_cache_driver = %(image_cache_driver)s
523data_api = %(data_api)s
524sql_connection = %(sql_connection)s
525show_image_direct_url = %(show_image_direct_url)s
526show_multiple_locations = %(show_multiple_locations)s
527user_storage_quota = %(user_storage_quota)s
528enable_v2_api = %(enable_v2_api)s
529lock_path = %(lock_path)s
530property_protection_file = %(property_protection_file)s
531property_protection_rule_format = %(property_protection_rule_format)s
532image_member_quota=%(image_member_quota)s
533image_property_quota=%(image_property_quota)s
534image_tag_quota=%(image_tag_quota)s
535image_location_quota=%(image_location_quota)s
536location_strategy=%(location_strategy)s
537allow_additional_image_properties = True
538enabled_backends=file1:file, file2:file
539[oslo_policy]
540policy_file = %(policy_file)s
541policy_default_rule = %(policy_default_rule)s
542[paste_deploy]
543flavor = %(deployment_flavor)s
544[store_type_location_strategy]
545store_type_preference = %(store_type_location_strategy_preference)s
546[glance_store]
547default_backend = %(default_backend)s
548[file1]
549filesystem_store_datadir=%(image_dir_backend_1)s
550[file2]
551filesystem_store_datadir=%(image_dir_backend_2)s
552"""
553 self.paste_conf_base = """[pipeline:glance-api]
554pipeline =
555 cors
556 healthcheck
557 versionnegotiation
558 gzip
559 unauthenticated-context
560 rootapp
561
562[pipeline:glance-api-caching]
563pipeline = cors healthcheck versionnegotiation gzip unauthenticated-context
564 cache rootapp
565
566[pipeline:glance-api-cachemanagement]
567pipeline =
568 cors
569 healthcheck
570 versionnegotiation
571 gzip
572 unauthenticated-context
573 cache
574 cache_manage
575 rootapp
576
577[pipeline:glance-api-fakeauth]
578pipeline = cors healthcheck versionnegotiation gzip fakeauth context rootapp
579
580[pipeline:glance-api-noauth]
581pipeline = cors healthcheck versionnegotiation gzip context rootapp
582
583[composite:rootapp]
584paste.composite_factory = glance.api:root_app_factory
585/: apiversions
586/v1: apiv1app
587/v2: apiv2app
588
589[app:apiversions]
590paste.app_factory = glance.api.versions:create_resource
591
592[app:apiv1app]
593paste.app_factory = glance.api.v1.router:API.factory
594
595[app:apiv2app]
596paste.app_factory = glance.api.v2.router:API.factory
597
598[filter:healthcheck]
599paste.filter_factory = oslo_middleware:Healthcheck.factory
600backends = disable_by_file
601disable_by_file_path = %(disable_path)s
602
603[filter:versionnegotiation]
604paste.filter_factory =
605 glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
606
607[filter:gzip]
608paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
609
610[filter:cache]
611paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
612
613[filter:cache_manage]
614paste.filter_factory =
615 glance.api.middleware.cache_manage:CacheManageFilter.factory
616
617[filter:context]
618paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
619
620[filter:unauthenticated-context]
621paste.filter_factory =
622 glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
623
624[filter:fakeauth]
625paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
626
627[filter:cors]
628paste.filter_factory = oslo_middleware.cors:filter_factory
629allowed_origin=http://valid.example.com
630"""
631
632
448class RegistryServer(Server): 633class RegistryServer(Server):
449 634
450 """ 635 """
@@ -946,3 +1131,374 @@ class FunctionalTest(test_utils.BaseTestCase):
946 self._attached_server_logs.append(s.log_file) 1131 self._attached_server_logs.append(s.log_file)
947 self.addDetail( 1132 self.addDetail(
948 s.server_name, testtools.content.text_content(s.dump_log())) 1133 s.server_name, testtools.content.text_content(s.dump_log()))
1134
1135
1136class MultipleBackendFunctionalTest(test_utils.BaseTestCase):
1137
1138 """
1139 Base test class for any test that wants to test the actual
1140 servers and clients and not just the stubbed out interfaces
1141 """
1142
1143 inited = False
1144 disabled = False
1145 launched_servers = []
1146
1147 def setUp(self):
1148 super(MultipleBackendFunctionalTest, self).setUp()
1149 self.test_dir = self.useFixture(fixtures.TempDir()).path
1150
1151 self.api_protocol = 'http'
1152 self.api_port, api_sock = test_utils.get_unused_port_and_socket()
1153 self.registry_port, reg_sock = test_utils.get_unused_port_and_socket()
1154 # NOTE: Scrubber is enabled by default for the functional tests.
1155 # Please disbale it by explicitly setting 'self.include_scrubber' to
1156 # False in the test SetUps that do not require Scrubber to run.
1157 self.include_scrubber = True
1158
1159 self.tracecmd = tracecmd_osmap.get(platform.system())
1160
1161 conf_dir = os.path.join(self.test_dir, 'etc')
1162 utils.safe_mkdirs(conf_dir)
1163 self.copy_data_file('schema-image.json', conf_dir)
1164 self.copy_data_file('policy.json', conf_dir)
1165 self.copy_data_file('property-protections.conf', conf_dir)
1166 self.copy_data_file('property-protections-policies.conf', conf_dir)
1167 self.property_file_roles = os.path.join(conf_dir,
1168 'property-protections.conf')
1169 property_policies = 'property-protections-policies.conf'
1170 self.property_file_policies = os.path.join(conf_dir,
1171 property_policies)
1172 self.policy_file = os.path.join(conf_dir, 'policy.json')
1173
1174 self.api_server_multiple_backend = ApiServerForMultipleBackend(
1175 self.test_dir, self.api_port, self.policy_file, sock=api_sock)
1176
1177 self.registry_server = RegistryServer(self.test_dir,
1178 self.registry_port,
1179 self.policy_file,
1180 sock=reg_sock)
1181
1182 self.scrubber_daemon = ScrubberDaemon(self.test_dir, self.policy_file)
1183
1184 self.pid_files = [self.api_server_multiple_backend.pid_file,
1185 self.registry_server.pid_file,
1186 self.scrubber_daemon.pid_file]
1187 self.files_to_destroy = []
1188 self.launched_servers = []
1189 # Keep track of servers we've logged so we don't double-log them.
1190 self._attached_server_logs = []
1191 self.addOnException(self.add_log_details_on_exception)
1192
1193 if not self.disabled:
1194 # We destroy the test data store between each test case,
1195 # and recreate it, which ensures that we have no side-effects
1196 # from the tests
1197 self.addCleanup(
1198 self._reset_database, self.registry_server.sql_connection)
1199 self.addCleanup(
1200 self._reset_database,
1201 self.api_server_multiple_backend.sql_connection)
1202 self.addCleanup(self.cleanup)
1203 self._reset_database(self.registry_server.sql_connection)
1204 self._reset_database(
1205 self.api_server_multiple_backend.sql_connection)
1206
1207 def set_policy_rules(self, rules):
1208 fap = open(self.policy_file, 'w')
1209 fap.write(jsonutils.dumps(rules))
1210 fap.close()
1211
1212 def _reset_database(self, conn_string):
1213 conn_pieces = urlparse.urlparse(conn_string)
1214 if conn_string.startswith('sqlite'):
1215 # We leave behind the sqlite DB for failing tests to aid
1216 # in diagnosis, as the file size is relatively small and
1217 # won't interfere with subsequent tests as it's in a per-
1218 # test directory (which is blown-away if the test is green)
1219 pass
1220 elif conn_string.startswith('mysql'):
1221 # We can execute the MySQL client to destroy and re-create
1222 # the MYSQL database, which is easier and less error-prone
1223 # than using SQLAlchemy to do this via MetaData...trust me.
1224 database = conn_pieces.path.strip('/')
1225 loc_pieces = conn_pieces.netloc.split('@')
1226 host = loc_pieces[1]
1227 auth_pieces = loc_pieces[0].split(':')
1228 user = auth_pieces[0]
1229 password = ""
1230 if len(auth_pieces) > 1:
1231 if auth_pieces[1].strip():
1232 password = "-p%s" % auth_pieces[1]
1233 sql = ("drop database if exists %(database)s; "
1234 "create database %(database)s;") % {'database': database}
1235 cmd = ("mysql -u%(user)s %(password)s -h%(host)s "
1236 "-e\"%(sql)s\"") % {'user': user, 'password': password,
1237 'host': host, 'sql': sql}
1238 exitcode, out, err = execute(cmd)
1239 self.assertEqual(0, exitcode)
1240
1241 def cleanup(self):
1242 """
1243 Makes sure anything we created or started up in the
1244 tests are destroyed or spun down
1245 """
1246
1247 # NOTE(jbresnah) call stop on each of the servers instead of
1248 # checking the pid file. stop() will wait until the child
1249 # server is dead. This eliminates the possibility of a race
1250 # between a child process listening on a port actually dying
1251 # and a new process being started
1252 servers = [self.api_server_multiple_backend,
1253 self.registry_server,
1254 self.scrubber_daemon]
1255 for s in servers:
1256 try:
1257 s.stop()
1258 except Exception:
1259 pass
1260
1261 for f in self.files_to_destroy:
1262 if os.path.exists(f):
1263 os.unlink(f)
1264
1265 def start_server(self,
1266 server,
1267 expect_launch,
1268 expect_exit=True,
1269 expected_exitcode=0,
1270 **kwargs):
1271 """
1272 Starts a server on an unused port.
1273
1274 Any kwargs passed to this method will override the configuration
1275 value in the conf file used in starting the server.
1276
1277 :param server: the server to launch
1278 :param expect_launch: true iff the server is expected to
1279 successfully start
1280 :param expect_exit: true iff the launched process is expected
1281 to exit in a timely fashion
1282 :param expected_exitcode: expected exitcode from the launcher
1283 """
1284 self.cleanup()
1285
1286 # Start up the requested server
1287 exitcode, out, err = server.start(expect_exit=expect_exit,
1288 expected_exitcode=expected_exitcode,
1289 **kwargs)
1290 if expect_exit:
1291 self.assertEqual(expected_exitcode, exitcode,
1292 "Failed to spin up the requested server. "
1293 "Got: %s" % err)
1294
1295 self.launched_servers.append(server)
1296
1297 launch_msg = self.wait_for_servers([server], expect_launch)
1298 self.assertTrue(launch_msg is None, launch_msg)
1299
1300 def start_with_retry(self, server, port_name, max_retries,
1301 expect_launch=True,
1302 **kwargs):
1303 """
1304 Starts a server, with retries if the server launches but
1305 fails to start listening on the expected port.
1306
1307 :param server: the server to launch
1308 :param port_name: the name of the port attribute
1309 :param max_retries: the maximum number of attempts
1310 :param expect_launch: true iff the server is expected to
1311 successfully start
1312 :param expect_exit: true iff the launched process is expected
1313 to exit in a timely fashion
1314 """
1315 launch_msg = None
1316 for i in range(max_retries):
1317 exitcode, out, err = server.start(expect_exit=not expect_launch,
1318 **kwargs)
1319 name = server.server_name
1320 self.assertEqual(0, exitcode,
1321 "Failed to spin up the %s server. "
1322 "Got: %s" % (name, err))
1323 launch_msg = self.wait_for_servers([server], expect_launch)
1324 if launch_msg:
1325 server.stop()
1326 server.bind_port = get_unused_port()
1327 setattr(self, port_name, server.bind_port)
1328 else:
1329 self.launched_servers.append(server)
1330 break
1331 self.assertTrue(launch_msg is None, launch_msg)
1332
1333 def start_servers(self, **kwargs):
1334 """
1335 Starts the API and Registry servers (glance-control api start
1336 & glance-control registry start) on unused ports. glance-control
1337 should be installed into the python path
1338
1339 Any kwargs passed to this method will override the configuration
1340 value in the conf file used in starting the servers.
1341 """
1342 self.cleanup()
1343
1344 # Start up the API and default registry server
1345
1346 # We start the registry server first, as the API server config
1347 # depends on the registry port - this ordering allows for
1348 # retrying the launch on a port clash
1349 self.start_with_retry(self.registry_server, 'registry_port', 3,
1350 **kwargs)
1351 kwargs['registry_port'] = self.registry_server.bind_port
1352
1353 self.start_with_retry(self.api_server_multiple_backend,
1354 'api_port', 3, **kwargs)
1355
1356 if self.include_scrubber:
1357 exitcode, out, err = self.scrubber_daemon.start(**kwargs)
1358 self.assertEqual(0, exitcode,
1359 "Failed to spin up the Scrubber daemon. "
1360 "Got: %s" % err)
1361
1362 def ping_server(self, port):
1363 """
1364 Simple ping on the port. If responsive, return True, else
1365 return False.
1366
1367 :note We use raw sockets, not ping here, since ping uses ICMP and
1368 has no concept of ports...
1369 """
1370 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1371 try:
1372 s.connect(("127.0.0.1", port))
1373 return True
1374 except socket.error:
1375 return False
1376 finally:
1377 s.close()
1378
1379 def ping_server_ipv6(self, port):
1380 """
1381 Simple ping on the port. If responsive, return True, else
1382 return False.
1383
1384 :note We use raw sockets, not ping here, since ping uses ICMP and
1385 has no concept of ports...
1386
1387 The function uses IPv6 (therefore AF_INET6 and ::1).
1388 """
1389 s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
1390 try:
1391 s.connect(("::1", port))
1392 return True
1393 except socket.error:
1394 return False
1395 finally:
1396 s.close()
1397
1398 def wait_for_servers(self, servers, expect_launch=True, timeout=30):
1399 """
1400 Tight loop, waiting for the given server port(s) to be available.
1401 Returns when all are pingable. There is a timeout on waiting
1402 for the servers to come up.
1403
1404 :param servers: Glance server ports to ping
1405 :param expect_launch: Optional, true iff the server(s) are
1406 expected to successfully start
1407 :param timeout: Optional, defaults to 30 seconds
1408 :returns: None if launch expectation is met, otherwise an
1409 assertion message
1410 """
1411 now = datetime.datetime.now()
1412 timeout_time = now + datetime.timedelta(seconds=timeout)
1413 replied = []
1414 while (timeout_time > now):
1415 pinged = 0
1416 for server in servers:
1417 if self.ping_server(server.bind_port):
1418 pinged += 1
1419 if server not in replied:
1420 replied.append(server)
1421 if pinged == len(servers):
1422 msg = 'Unexpected server launch status'
1423 return None if expect_launch else msg
1424 now = datetime.datetime.now()
1425 time.sleep(0.05)
1426
1427 failed = list(set(servers) - set(replied))
1428 msg = 'Unexpected server launch status for: '
1429 for f in failed:
1430 msg += ('%s, ' % f.server_name)
1431 if os.path.exists(f.pid_file):
1432 pid = f.process_pid
1433 trace = f.pid_file.replace('.pid', '.trace')
1434 if self.tracecmd:
1435 cmd = '%s -p %d -o %s' % (self.tracecmd, pid, trace)
1436 try:
1437 execute(cmd, raise_error=False, expect_exit=False)
1438 except OSError as e:
1439 if e.errno == errno.ENOENT:
1440 raise RuntimeError('No executable found for "%s" '
1441 'command.' % self.tracecmd)
1442 else:
1443 raise
1444 time.sleep(0.5)
1445 if os.path.exists(trace):
1446 msg += ('\n%s:\n%s\n' % (self.tracecmd,
1447 open(trace).read()))
1448
1449 self.add_log_details(failed)
1450
1451 return msg if expect_launch else None
1452
1453 def stop_server(self, server):
1454 """
1455 Called to stop a single server in a normal fashion using the
1456 glance-control stop method to gracefully shut the server down.
1457
1458 :param server: the server to stop
1459 """
1460 # Spin down the requested server
1461 server.stop()
1462
1463 def stop_servers(self):
1464 """
1465 Called to stop the started servers in a normal fashion. Note
1466 that cleanup() will stop the servers using a fairly draconian
1467 method of sending a SIGTERM signal to the servers. Here, we use
1468 the glance-control stop method to gracefully shut the server down.
1469 This method also asserts that the shutdown was clean, and so it
1470 is meant to be called during a normal test case sequence.
1471 """
1472
1473 # Spin down the API and default registry server
1474 self.stop_server(self.api_server_multiple_backend)
1475 self.stop_server(self.registry_server)
1476 if self.include_scrubber:
1477 self.stop_server(self.scrubber_daemon)
1478
1479 self._reset_database(self.registry_server.sql_connection)
1480
1481 def run_sql_cmd(self, sql):
1482 """
1483 Provides a crude mechanism to run manual SQL commands for backend
1484 DB verification within the functional tests.
1485 The raw result set is returned.
1486 """
1487 engine = db_api.get_engine()
1488 return engine.execute(sql)
1489
1490 def copy_data_file(self, file_name, dst_dir):
1491 src_file_name = os.path.join('glance/tests/etc', file_name)
1492 shutil.copy(src_file_name, dst_dir)
1493 dst_file_name = os.path.join(dst_dir, file_name)
1494 return dst_file_name
1495
1496 def add_log_details_on_exception(self, *args, **kwargs):
1497 self.add_log_details()
1498
1499 def add_log_details(self, servers=None):
1500 for s in servers or self.launched_servers:
1501 if s.log_file not in self._attached_server_logs:
1502 self._attached_server_logs.append(s.log_file)
1503 self.addDetail(
1504 s.server_name, testtools.content.text_content(s.dump_log()))
diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
index b9eec62..11ce561 100644
--- a/glance/tests/functional/v2/test_images.py
+++ b/glance/tests/functional/v2/test_images.py
@@ -4466,3 +4466,1023 @@ class TestQuotasWithRegistry(TestQuotas):
4466 self.api_server.data_api = ( 4466 self.api_server.data_api = (
4467 'glance.tests.functional.v2.registry_data_api') 4467 'glance.tests.functional.v2.registry_data_api')
4468 self.registry_server.deployment_flavor = 'trusted-auth' 4468 self.registry_server.deployment_flavor = 'trusted-auth'
4469
4470
4471class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4472
4473 def setUp(self):
4474 super(TestImagesMultipleBackend, self).setUp()
4475 self.cleanup()
4476 self.include_scrubber = False
4477 self.api_server_multiple_backend.deployment_flavor = 'noauth'
4478 self.api_server_multiple_backend.data_api = 'glance.db.sqlalchemy.api'
4479 for i in range(3):
4480 ret = test_utils.start_http_server("foo_image_id%d" % i,
4481 "foo_image%d" % i)
4482 setattr(self, 'http_server%d_pid' % i, ret[0])
4483 setattr(self, 'http_port%d' % i, ret[1])
4484
4485 def tearDown(self):
4486 for i in range(3):
4487 pid = getattr(self, 'http_server%d_pid' % i, None)
4488 if pid:
4489 os.kill(pid, signal.SIGKILL)
4490
4491 super(TestImagesMultipleBackend, self).tearDown()
4492
4493 def _url(self, path):
4494 return 'http://127.0.0.1:%d%s' % (self.api_port, path)
4495
4496 def _headers(self, custom_headers=None):
4497 base_headers = {
4498 'X-Identity-Status': 'Confirmed',
4499 'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
4500 'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
4501 'X-Tenant-Id': TENANT1,
4502 'X-Roles': 'member',
4503 }
4504 base_headers.update(custom_headers or {})
4505 return base_headers
4506
4507 def test_image_import_using_glance_direct(self):
4508 self.start_servers(**self.__dict__.copy())
4509
4510 # Image list should be empty
4511 path = self._url('/v2/images')
4512 response = requests.get(path, headers=self._headers())
4513 self.assertEqual(http.OK, response.status_code)
4514 images = jsonutils.loads(response.text)['images']
4515 self.assertEqual(0, len(images))
4516
4517 # glance-direct should be available in discovery response
4518 path = self._url('/v2/info/import')
4519 response = requests.get(path, headers=self._headers())
4520 self.assertEqual(http.OK, response.status_code)
4521 discovery_calls = jsonutils.loads(
4522 response.text)['import-methods']['value']
4523 self.assertIn("glance-direct", discovery_calls)
4524
4525 # file1 and file2 should be available in discovery response
4526 available_stores = ['file1', 'file2']
4527 path = self._url('/v2/info/stores')
4528 response = requests.get(path, headers=self._headers())
4529 self.assertEqual(http.OK, response.status_code)
4530 discovery_calls = jsonutils.loads(
4531 response.text)['stores']
4532 for stores in discovery_calls:
4533 self.assertIn('id', stores)
4534 self.assertIn(stores['id'], available_stores)
4535
4536 # Create an image
4537 path = self._url('/v2/images')
4538 headers = self._headers({'content-type': 'application/json'})
4539 data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
4540 'disk_format': 'aki',
4541 'container_format': 'aki'})
4542 response = requests.post(path, headers=headers, data=data)
4543 self.assertEqual(http.CREATED, response.status_code)
4544
4545 # Check 'OpenStack-image-store-ids' header present in response
4546 self.assertIn('OpenStack-image-store-ids', response.headers)
4547 for store in available_stores:
4548 self.assertIn(store, response.headers['OpenStack-image-store-ids'])
4549
4550 # Returned image entity should have a generated id and status
4551 image = jsonutils.loads(response.text)
4552 image_id = image['id']
4553 checked_keys = set([
4554 u'status',
4555 u'name',
4556 u'tags',
4557 u'created_at',
4558 u'updated_at',
4559 u'visibility',
4560 u'self',
4561 u'protected',
4562 u'id',
4563 u'file',
4564 u'min_disk',
4565 u'type',
4566 u'min_ram',
4567 u'schema',
4568 u'disk_format',
4569 u'container_format',
4570 u'owner',
4571 u'checksum',
4572 u'size',
4573 u'virtual_size',
4574 u'os_hidden',
4575 u'os_hash_algo',
4576 u'os_hash_value'
4577
4578 ])
4579 self.assertEqual(checked_keys, set(image.keys()))
4580 expected_image = {
4581 'status': 'queued',
4582 'name': 'image-1',
4583 'tags': [],
4584 'visibility': 'shared',
4585 'self': '/v2/images/%s' % image_id,
4586 'protected': False,
4587 'file': '/v2/images/%s/file' % image_id,
4588 'min_disk': 0,
4589 'type': 'kernel',
4590 'min_ram': 0,
4591 'schema': '/v2/schemas/image',
4592 }
4593 for key, value in expected_image.items():
4594 self.assertEqual(value, image[key], key)
4595
4596 # Image list should now have one entry
4597 path = self._url('/v2/images')
4598 response = requests.get(path, headers=self._headers())
4599 self.assertEqual(http.OK, response.status_code)
4600 images = jsonutils.loads(response.text)['images']
4601 self.assertEqual(1, len(images))
4602 self.assertEqual(image_id, images[0]['id'])
4603
4604 def _verify_image_checksum_and_status(checksum=None, status=None):
4605 # Checksum should be populated and status should be active
4606 path = self._url('/v2/images/%s' % image_id)
4607 response = requests.get(path, headers=self._headers())
4608 self.assertEqual(http.OK, response.status_code)
4609 image = jsonutils.loads(response.text)
4610 self.assertEqual(checksum, image['checksum'])
4611 self.assertEqual(status, image['status'])
4612
4613 # Upload some image data to staging area
4614 path = self._url('/v2/images/%s/stage' % image_id)
4615 headers = self._headers({'Content-Type': 'application/octet-stream'})
4616 response = requests.put(path, headers=headers, data='ZZZZZ')
4617 self.assertEqual(http.NO_CONTENT, response.status_code)
4618
4619 # Verify image is in uploading state and checksum is None
4620 _verify_image_checksum_and_status(status='uploading')
4621
4622 # Import image to store
4623 path = self._url('/v2/images/%s/import' % image_id)
4624 headers = self._headers({
4625 'content-type': 'application/json',
4626 'X-Roles': 'admin',
4627 })
4628 data = jsonutils.dumps({'method': {
4629 'name': 'glance-direct'
4630 }})
4631 response = requests.post(path, headers=headers, data=data)
4632 self.assertEqual(http.ACCEPTED, response.status_code)
4633
4634 # Verify image is in active state and checksum is set
4635 # NOTE(abhishekk): As import is a async call we need to provide
4636 # some timelap to complete the call.
4637 path = self._url('/v2/images/%s' % image_id)
4638 func_utils.wait_for_status(request_path=path,
4639 request_headers=self._headers(),
4640 status='active',
4641 max_sec=2,
4642 delay_sec=0.2)
4643 _verify_image_checksum_and_status(
4644 checksum='8f113e38d28a79a5a451b16048cc2b72',
4645 status='active')
4646
4647 # Ensure the size is updated to reflect the data uploaded
4648 path = self._url('/v2/images/%s' % image_id)
4649 response = requests.get(path, headers=self._headers())
4650 self.assertEqual(http.OK, response.status_code)
4651 self.assertEqual(5, jsonutils.loads(response.text)['size'])
4652
4653 # Ensure image is created in default backend
4654 self.assertIn('file1', jsonutils.loads(response.text)['stores'])
4655
4656 # Deleting image should work
4657 path = self._url('/v2/images/%s' % image_id)
4658 response = requests.delete(path, headers=self._headers())
4659 self.assertEqual(http.NO_CONTENT, response.status_code)
4660
4661 # Image list should now be empty
4662 path = self._url('/v2/images')
4663 response = requests.get(path, headers=self._headers())
4664 self.assertEqual(http.OK, response.status_code)
4665 images = jsonutils.loads(response.text)['images']
4666 self.assertEqual(0, len(images))
4667
4668 self.stop_servers()
4669
4670 def test_image_import_using_glance_direct_different_backend(self):
4671 self.start_servers(**self.__dict__.copy())
4672
4673 # Image list should be empty
4674 path = self._url('/v2/images')
4675 response = requests.get(path, headers=self._headers())
4676 self.assertEqual(http.OK, response.status_code)
4677 images = jsonutils.loads(response.text)['images']
4678 self.assertEqual(0, len(images))
4679
4680 # glance-direct should be available in discovery response
4681 path = self._url('/v2/info/import')
4682 response = requests.get(path, headers=self._headers())
4683 self.assertEqual(http.OK, response.status_code)
4684 discovery_calls = jsonutils.loads(
4685 response.text)['import-methods']['value']
4686 self.assertIn("glance-direct", discovery_calls)
4687
4688 # file1 and file2 should be available in discovery response
4689 available_stores = ['file1', 'file2']
4690 path = self._url('/v2/info/stores')
4691 response = requests.get(path, headers=self._headers())
4692 self.assertEqual(http.OK, response.status_code)
4693 discovery_calls = jsonutils.loads(
4694 response.text)['stores']
4695 for stores in discovery_calls:
4696 self.assertIn('id', stores)
4697 self.assertIn(stores['id'], available_stores)
4698
4699 # Create an image
4700 path = self._url('/v2/images')
4701 headers = self._headers({'content-type': 'application/json'})
4702 data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
4703 'disk_format': 'aki',
4704 'container_format': 'aki'})
4705 response = requests.post(path, headers=headers, data=data)
4706 self.assertEqual(http.CREATED, response.status_code)
4707
4708 # Check 'OpenStack-image-store-ids' header present in response
4709 self.assertIn('OpenStack-image-store-ids', response.headers)
4710 for store in available_stores:
4711 self.assertIn(store, response.headers['OpenStack-image-store-ids'])
4712
4713 # Returned image entity should have a generated id and status
4714 image = jsonutils.loads(response.text)
4715 image_id = image['id']
4716 checked_keys = set([
4717 u'status',
4718 u'name',
4719 u'tags',
4720 u'created_at',
4721 u'updated_at',
4722 u'visibility',
4723 u'self',
4724 u'protected',
4725 u'id',
4726 u'file',
4727 u'min_disk',
4728 u'type',
4729 u'min_ram',
4730 u'schema',
4731 u'disk_format',
4732 u'container_format',
4733 u'owner',
4734 u'checksum',
4735 u'size',
4736 u'virtual_size',
4737 u'os_hidden',
4738 u'os_hash_algo',
4739 u'os_hash_value'
4740 ])
4741 self.assertEqual(checked_keys, set(image.keys()))
4742 expected_image = {
4743 'status': 'queued',
4744 'name': 'image-1',
4745 'tags': [],
4746 'visibility': 'shared',
4747 'self': '/v2/images/%s' % image_id,
4748 'protected': False,
4749 'file': '/v2/images/%s/file' % image_id,
4750 'min_disk': 0,
4751 'type': 'kernel',
4752 'min_ram': 0,
4753 'schema': '/v2/schemas/image',
4754 }
4755 for key, value in expected_image.items():
4756 self.assertEqual(value, image[key], key)
4757
4758 # Image list should now have one entry
4759 path = self._url('/v2/images')
4760 response = requests.get(path, headers=self._headers())
4761 self.assertEqual(http.OK, response.status_code)
4762 images = jsonutils.loads(response.text)['images']
4763 self.assertEqual(1, len(images))
4764 self.assertEqual(image_id, images[0]['id'])
4765
4766 def _verify_image_checksum_and_status(checksum=None, status=None):
4767 # Checksum should be populated and status should be active
4768 path = self._url('/v2/images/%s' % image_id)
4769 response = requests.get(path, headers=self._headers())
4770 self.assertEqual(http.OK, response.status_code)
4771 image = jsonutils.loads(response.text)
4772 self.assertEqual(checksum, image['checksum'])
4773 self.assertEqual(status, image['status'])
4774
4775 # Upload some image data to staging area
4776 path = self._url('/v2/images/%s/stage' % image_id)
4777 headers = self._headers({'Content-Type': 'application/octet-stream'})
4778 response = requests.put(path, headers=headers, data='ZZZZZ')
4779 self.assertEqual(http.NO_CONTENT, response.status_code)
4780
4781 # Verify image is in uploading state and checksum is None
4782 _verify_image_checksum_and_status(status='uploading')
4783
4784 # Import image to file2 store (other than default backend)
4785 path = self._url('/v2/images/%s/import' % image_id)
4786 headers = self._headers({
4787 'content-type': 'application/json',
4788 'X-Roles': 'admin',
4789 'X-Image-Meta-Store': 'file2'
4790 })
4791 data = jsonutils.dumps({'method': {
4792 'name': 'glance-direct'
4793 }})
4794 response = requests.post(path, headers=headers, data=data)
4795 self.assertEqual(http.ACCEPTED, response.status_code)
4796
4797 # Verify image is in active state and checksum is set
4798 # NOTE(abhishekk): As import is a async call we need to provide
4799 # some timelap to complete the call.
4800 path = self._url('/v2/images/%s' % image_id)
4801 func_utils.wait_for_status(request_path=path,
4802 request_headers=self._headers(),
4803 status='active',
4804 max_sec=2,
4805 delay_sec=0.2)
4806 _verify_image_checksum_and_status(
4807 checksum='8f113e38d28a79a5a451b16048cc2b72',
4808 status='active')
4809
4810 # Ensure the size is updated to reflect the data uploaded
4811 path = self._url('/v2/images/%s' % image_id)
4812 response = requests.get(path, headers=self._headers())
4813 self.assertEqual(http.OK, response.status_code)
4814 self.assertEqual(5, jsonutils.loads(response.text)['size'])
4815
4816 # Ensure image is created in different backend
4817 self.assertIn('file2', jsonutils.loads(response.text)['stores'])
4818
4819 # Deleting image should work
4820 path = self._url('/v2/images/%s' % image_id)
4821 response = requests.delete(path, headers=self._headers())
4822 self.assertEqual(http.NO_CONTENT, response.status_code)
4823
4824 # Image list should now be empty
4825 path = self._url('/v2/images')
4826 response = requests.get(path, headers=self._headers())
4827 self.assertEqual(http.OK, response.status_code)
4828 images = jsonutils.loads(response.text)['images']
4829 self.assertEqual(0, len(images))
4830
4831 self.stop_servers()
4832
4833 def test_image_import_using_web_download(self):
4834 self.config(node_staging_uri="file:///tmp/staging/")
4835 self.start_servers(**self.__dict__.copy())
4836
4837 # Image list should be empty
4838 path = self._url('/v2/images')
4839 response = requests.get(path, headers=self._headers())
4840 self.assertEqual(http.OK, response.status_code)
4841 images = jsonutils.loads(response.text)['images']
4842 self.assertEqual(0, len(images))
4843
4844 # web-download should be available in discovery response
4845 path = self._url('/v2/info/import')
4846 response = requests.get(path, headers=self._headers())
4847 self.assertEqual(http.OK, response.status_code)
4848 discovery_calls = jsonutils.loads(
4849 response.text)['import-methods']['value']
4850 self.assertIn("web-download", discovery_calls)
4851
4852 # file1 and file2 should be available in discovery response
4853 available_stores = ['file1', 'file2']
4854 path = self._url('/v2/info/stores')
4855 response = requests.get(path, headers=self._headers())
4856 self.assertEqual(http.OK, response.status_code)
4857 discovery_calls = jsonutils.loads(
4858 response.text)['stores']
4859 for stores in discovery_calls:
4860 self.assertIn('id', stores)
4861 self.assertIn(stores['id'], available_stores)
4862
4863 # Create an image
4864 path = self._url('/v2/images')
4865 headers = self._headers({'content-type': 'application/json'})
4866 data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
4867 'disk_format': 'aki',
4868 'container_format': 'aki'})
4869 response = requests.post(path, headers=headers, data=data)
4870 self.assertEqual(http.CREATED, response.status_code)
4871
4872 # Check 'OpenStack-image-store-ids' header present in response
4873 self.assertIn('OpenStack-image-store-ids', response.headers)
4874 for store in available_stores:
4875 self.assertIn(store, response.headers['OpenStack-image-store-ids'])
4876
4877 # Returned image entity should have a generated id and status
4878 image = jsonutils.loads(response.text)
4879 image_id = image['id']
4880 checked_keys = set([
4881 u'status',
4882 u'name',
4883 u'tags',
4884 u'created_at',
4885 u'updated_at',
4886 u'visibility',
4887 u'self',
4888 u'protected',
4889 u'id',
4890 u'file',
4891 u'min_disk',
4892 u'type',
4893 u'min_ram',
4894 u'schema',
4895 u'disk_format',
4896 u'container_format',
4897 u'owner',
4898 u'checksum',
4899 u'size',
4900 u'virtual_size',
4901 u'os_hidden',
4902 u'os_hash_algo',
4903 u'os_hash_value'
4904 ])
4905 self.assertEqual(checked_keys, set(image.keys()))
4906 expected_image = {
4907 'status': 'queued',
4908 'name': 'image-1',
4909 'tags': [],
4910 'visibility': 'shared',
4911 'self': '/v2/images/%s' % image_id,
4912 'protected': False,
4913 'file': '/v2/images/%s/file' % image_id,
4914 'min_disk': 0,
4915 'type': 'kernel',
4916 'min_ram': 0,
4917 'schema': '/v2/schemas/image',
4918 }
4919 for key, value in expected_image.items():
4920 self.assertEqual(value, image[key], key)
4921
4922 # Image list should now have one entry
4923 path = self._url('/v2/images')
4924 response = requests.get(path, headers=self._headers())
4925 self.assertEqual(http.OK, response.status_code)
4926 images = jsonutils.loads(response.text)['images']
4927 self.assertEqual(1, len(images))
4928 self.assertEqual(image_id, images[0]['id'])
4929
4930 def _verify_image_checksum_and_status(checksum=None, status=None):
4931 # Checksum should be populated and status should be active
4932 path = self._url('/v2/images/%s' % image_id)
4933 response = requests.get(path, headers=self._headers())
4934 self.assertEqual(http.OK, response.status_code)
4935 image = jsonutils.loads(response.text)
4936 self.assertEqual(checksum, image['checksum'])
4937 self.assertEqual(status, image['status'])
4938
4939 # Verify image is in queued state and checksum is None
4940 _verify_image_checksum_and_status(status='queued')
4941
4942 # Import image to store
4943 path = self._url('/v2/images/%s/import' % image_id)
4944 headers = self._headers({
4945 'content-type': 'application/json',
4946 'X-Roles': 'admin',
4947 })
4948 data = jsonutils.dumps({'method': {
4949 'name': 'web-download',
4950 'uri': 'https://www.openstack.org/assets/openstack-logo/'
4951 '2016R/OpenStack-Logo-Horizontal.eps.zip'
4952 }})
4953 response = requests.post(path, headers=headers, data=data)
4954 self.assertEqual(http.ACCEPTED, response.status_code)
4955
4956 # Verify image is in active state and checksum is set
4957 # NOTE(abhishekk): As import is a async call we need to provide
4958 # some timelap to complete the call.
4959 path = self._url('/v2/images/%s' % image_id)
4960 func_utils.wait_for_status(request_path=path,
4961 request_headers=self._headers(),
4962 status='active',
4963 max_sec=20,
4964 delay_sec=0.2,
4965 start_delay_sec=1)
4966 _verify_image_checksum_and_status(
4967 checksum='bcd65f8922f61a9e6a20572ad7aa2bdd',
4968 status='active')
4969
4970 # Ensure image is created in default backend
4971 path = self._url('/v2/images/%s' % image_id)
4972 response = requests.get(path, headers=self._headers())
4973 self.assertEqual(http.OK, response.status_code)
4974 self.assertIn('file1', jsonutils.loads(response.text)['stores'])
4975
4976 # Deleting image should work
4977 path = self._url('/v2/images/%s' % image_id)
4978 response = requests.delete(path, headers=self._headers())
4979 self.assertEqual(http.NO_CONTENT, response.status_code)
4980
4981 # Image list should now be empty
4982 path = self._url('/v2/images')
4983 response = requests.get(path, headers=self._headers())
4984 self.assertEqual(http.OK, response.status_code)
4985 images = jsonutils.loads(response.text)['images']
4986 self.assertEqual(0, len(images))
4987
4988 self.stop_servers()
4989
4990 def test_image_import_using_web_download_different_backend(self):
4991 self.config(node_staging_uri="file:///tmp/staging/")
4992 self.start_servers(**self.__dict__.copy())
4993
4994 # Image list should be empty
4995 path = self._url('/v2/images')
4996 response = requests.get(path, headers=self._headers())
4997 self.assertEqual(http.OK, response.status_code)
4998 images = jsonutils.loads(response.text)['images']
4999 self.assertEqual(0, len(images))
5000
5001 # web-download should be available in discovery response
5002 path = self._url('/v2/info/import')
5003 response = requests.get(path, headers=self._headers())
5004 self.assertEqual(http.OK, response.status_code)
5005 discovery_calls = jsonutils.loads(
5006 response.text)['import-methods']['value']
5007 self.assertIn("web-download", discovery_calls)
5008
5009 # file1 and file2 should be available in discovery response
5010 available_stores = ['file1', 'file2']
5011 path = self._url('/v2/info/stores')
5012 response = requests.get(path, headers=self._headers())
5013 self.assertEqual(http.OK, response.status_code)
5014 discovery_calls = jsonutils.loads(
5015 response.text)['stores']
5016 for stores in discovery_calls:
5017 self.assertIn('id', stores)
5018 self.assertIn(stores['id'], available_stores)
5019
5020 # Create an image
5021 path = self._url('/v2/images')
5022 headers = self._headers({'content-type': 'application/json'})
5023 data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
5024 'disk_format': 'aki',
5025 'container_format': 'aki'})
5026 response = requests.post(path, headers=headers, data=data)
5027 self.assertEqual(http.CREATED, response.status_code)
5028
5029 # Check 'OpenStack-image-store-ids' header present in response
5030 self.assertIn('OpenStack-image-store-ids', response.headers)
5031 for store in available_stores:
5032 self.assertIn(store, response.headers['OpenStack-image-store-ids'])
5033
5034 # Returned image entity should have a generated id and status
5035 image = jsonutils.loads(response.text)
5036 image_id = image['id']
5037 checked_keys = set([
5038 u'status',
5039 u'name',
5040 u'tags',
5041 u'created_at',
5042 u'updated_at',
5043 u'visibility',
5044 u'self',
5045 u'protected',
5046 u'id',
5047 u'file',
5048 u'min_disk',
5049 u'type',
5050 u'min_ram',
5051 u'schema',
5052 u'disk_format',
5053 u'container_format',
5054 u'owner',
5055 u'checksum',
5056 u'size',
5057 u'virtual_size',
5058 u'os_hidden',
5059 u'os_hash_algo',
5060 u'os_hash_value'
5061 ])
5062 self.assertEqual(checked_keys, set(image.keys()))
5063 expected_image = {
5064 'status': 'queued',
5065 'name': 'image-1',
5066 'tags': [],
5067 'visibility': 'shared',
5068 'self': '/v2/images/%s' % image_id,
5069 'protected': False,
5070 'file': '/v2/images/%s/file' % image_id,
5071 'min_disk': 0,
5072 'type': 'kernel',
5073 'min_ram': 0,
5074 'schema': '/v2/schemas/image',
5075 }
5076 for key, value in expected_image.items():
5077 self.assertEqual(value, image[key], key)
5078
5079 # Image list should now have one entry
5080 path = self._url('/v2/images')
5081 response = requests.get(path, headers=self._headers())
5082 self.assertEqual(http.OK, response.status_code)
5083 images = jsonutils.loads(response.text)['images']
5084 self.assertEqual(1, len(images))
5085 self.assertEqual(image_id, images[0]['id'])
5086
5087 def _verify_image_checksum_and_status(checksum=None, status=None):
5088 # Checksum should be populated and status should be active
5089 path = self._url('/v2/images/%s' % image_id)
5090 response = requests.get(path, headers=self._headers())
5091 self.assertEqual(http.OK, response.status_code)
5092 image = jsonutils.loads(response.text)
5093 self.assertEqual(checksum, image['checksum'])
5094 self.assertEqual(status, image['status'])
5095
5096 # Verify image is in queued state and checksum is None
5097 _verify_image_checksum_and_status(status='queued')
5098
5099 # Import image to store
5100 path = self._url('/v2/images/%s/import' % image_id)
5101 headers = self._headers({
5102 'content-type': 'application/json',
5103 'X-Roles': 'admin',
5104 'X-Image-Meta-Store': 'file2'
5105 })
5106 data = jsonutils.dumps({'method': {
5107 'name': 'web-download',
5108 'uri': 'https://www.openstack.org/assets/openstack-logo/'
5109 '2016R/OpenStack-Logo-Horizontal.eps.zip'
5110 }})
5111 response = requests.post(path, headers=headers, data=data)
5112 self.assertEqual(http.ACCEPTED, response.status_code)
5113
5114 # Verify image is in active state and checksum is set
5115 # NOTE(abhishekk): As import is a async call we need to provide
5116 # some timelap to complete the call.
5117 path = self._url('/v2/images/%s' % image_id)
5118 func_utils.wait_for_status(request_path=path,
5119 request_headers=self._headers(),
5120 status='active',
5121 max_sec=20,
5122 delay_sec=0.2,
5123 start_delay_sec=1)
5124 _verify_image_checksum_and_status(
5125 checksum='bcd65f8922f61a9e6a20572ad7aa2bdd',
5126 status='active')
5127
5128 # Ensure image is created in different backend
5129 path = self._url('/v2/images/%s' % image_id)
5130 response = requests.get(path, headers=self._headers())
5131 self.assertEqual(http.OK, response.status_code)
5132 self.assertIn('file2', jsonutils.loads(response.text)['stores'])
5133
5134 # Deleting image should work
5135 path = self._url('/v2/images/%s' % image_id)
5136 response = requests.delete(path, headers=self._headers())
5137 self.assertEqual(http.NO_CONTENT, response.status_code)
5138
5139 # Image list should now be empty
5140 path = self._url('/v2/images')
5141 response = requests.get(path, headers=self._headers())
5142 self.assertEqual(http.OK, response.status_code)
5143 images = jsonutils.loads(response.text)['images']
5144 self.assertEqual(0, len(images))
5145
5146 self.stop_servers()
5147
5148 def test_image_lifecycle(self):
5149 # Image list should be empty
5150 self.start_servers(**self.__dict__.copy())
5151 path = self._url('/v2/images')
5152 response = requests.get(path, headers=self._headers())
5153 self.assertEqual(http.OK, response.status_code)
5154 images = jsonutils.loads(response.text)['images']
5155 self.assertEqual(0, len(images))
5156
5157 # file1 and file2 should be available in discovery response
5158 available_stores = ['file1', 'file2']
5159 path = self._url('/v2/info/stores')
5160 response = requests.get(path, headers=self._headers())
5161 self.assertEqual(http.OK, response.status_code)
5162 discovery_calls = jsonutils.loads(
5163 response.text)['stores']
5164 for stores in discovery_calls:
5165 self.assertIn('id', stores)
5166 self.assertIn(stores['id'], available_stores)
5167
5168 # Create an image (with two deployer-defined properties)
5169 path = self._url('/v2/images')
5170 headers = self._headers({'content-type': 'application/json'})
5171 data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
5172 'foo': 'bar', 'disk_format': 'aki',
5173 'container_format': 'aki', 'abc': 'xyz',
5174 'protected': True})
5175 response = requests.post(path, headers=headers, data=data)
5176 self.assertEqual(http.CREATED, response.status_code)
5177
5178 # Check 'OpenStack-image-store-ids' header present in response
5179 self.assertIn('OpenStack-image-store-ids', response.headers)
5180 for store in available_stores:
5181 self.assertIn(store, response.headers['OpenStack-image-store-ids'])
5182
5183 # Returned image entity should have a generated id and status
5184 image = jsonutils.loads(response.text)
5185 image_id = image['id']
5186 checked_keys = set([
5187 u'status',
5188 u'name',
5189 u'tags',
5190 u'created_at',
5191 u'updated_at',
5192 u'visibility',
5193 u'self',
5194 u'protected',
5195 u'id',
5196 u'file',
5197 u'min_disk',
5198 u'foo',
5199 u'abc',
5200 u'type',
5201 u'min_ram',
5202 u'schema',
5203 u'disk_format',
5204 u'container_format',
5205 u'owner',
5206 u'checksum',
5207 u'size',
5208 u'virtual_size',
5209 u'os_hidden',
5210 u'os_hash_algo',
5211 u'os_hash_value'
5212 ])
5213 self.assertEqual(checked_keys, set(image.keys()))
5214 expected_image = {
5215 'status': 'queued',
5216 'name': 'image-1',
5217 'tags': [],
5218 'visibility': 'shared',
5219 'self': '/v2/images/%s' % image_id,
5220 'protected': True,
5221 'file': '/v2/images/%s/file' % image_id,
5222 'min_disk': 0,
5223 'foo': 'bar',
5224 'abc': 'xyz',
5225 'type': 'kernel',
5226 'min_ram': 0,
5227 'schema': '/v2/schemas/image',
5228 }
5229 for key, value in expected_image.items():
5230 self.assertEqual(value, image[key], key)
5231
5232 # Image list should now have one entry
5233 path = self._url('/v2/images')
5234 response = requests.get(path, headers=self._headers())
5235 self.assertEqual(http.OK, response.status_code)
5236 images = jsonutils.loads(response.text)['images']
5237 self.assertEqual(1, len(images))
5238 self.assertEqual(image_id, images[0]['id'])
5239
5240 # Try to download data before its uploaded
5241 path = self._url('/v2/images/%s/file' % image_id)
5242 headers = self._headers()
5243 response = requests.get(path, headers=headers)
5244 self.assertEqual(http.NO_CONTENT, response.status_code)
5245
5246 def _verify_image_checksum_and_status(checksum, status):
5247 # Checksum should be populated and status should be active
5248 path = self._url('/v2/images/%s' % image_id)
5249 response = requests.get(path, headers=self._headers())
5250 self.assertEqual(http.OK, response.status_code)
5251 image = jsonutils.loads(response.text)
5252 self.assertEqual(checksum, image['checksum'])
5253 self.assertEqual(status, image['status'])
5254
5255 # Upload some image data
5256 path = self._url('/v2/images/%s/file' % image_id)
5257 headers = self._headers({'Content-Type': 'application/octet-stream'})
5258 response = requests.put(path, headers=headers, data='ZZZZZ')
5259 self.assertEqual(http.NO_CONTENT, response.status_code)
5260
5261 expected_checksum = '8f113e38d28a79a5a451b16048cc2b72'
5262 _verify_image_checksum_and_status(expected_checksum, 'active')
5263
5264 # Ensure image is created in default backend
5265 path = self._url('/v2/images/%s' % image_id)
5266 response = requests.get(path, headers=self._headers())
5267 self.assertEqual(http.OK, response.status_code)
5268 self.assertIn('file1', jsonutils.loads(response.text)['stores'])
5269
5270 # Try to download the data that was just uploaded
5271 path = self._url('/v2/images/%s/file' % image_id)
5272 response = requests.get(path, headers=self._headers())
5273 self.assertEqual(http.OK, response.status_code)
5274 self.assertEqual(expected_checksum, response.headers['Content-MD5'])
5275 self.assertEqual('ZZZZZ', response.text)
5276
5277 # Ensure the size is updated to reflect the data uploaded
5278 path = self._url('/v2/images/%s' % image_id)
5279 response = requests.get(path, headers=self._headers())
5280 self.assertEqual(http.OK, response.status_code)
5281 self.assertEqual(5, jsonutils.loads(response.text)['size'])
5282
5283 # Unprotect image for deletion
5284 path = self._url('/v2/images/%s' % image_id)
5285 media_type = 'application/openstack-images-v2.1-json-patch'
5286 headers = self._headers({'content-type': media_type})
5287 doc = [{'op': 'replace', 'path': '/protected', 'value': False}]
5288 data = jsonutils.dumps(doc)
5289 response = requests.patch(path, headers=headers, data=data)
5290 self.assertEqual(http.OK, response.status_code, response.text)
5291
5292 # Deletion should work. Deleting image
5293 path = self._url('/v2/images/%s' % image_id)
5294 response = requests.delete(path, headers=self._headers())
5295 self.assertEqual(http.NO_CONTENT, response.status_code)
5296
5297 # This image should be no longer be directly accessible
5298 path = self._url('/v2/images/%s' % image_id)
5299 response = requests.get(path, headers=self._headers())
5300 self.assertEqual(http.NOT_FOUND, response.status_code)
5301
5302 # And neither should its data
5303 path = self._url('/v2/images/%s/file' % image_id)
5304 headers = self._headers()
5305 response = requests.get(path, headers=headers)
5306 self.assertEqual(http.NOT_FOUND, response.status_code)
5307
5308 # Image list should now be empty
5309 path = self._url('/v2/images')
5310 response = requests.get(path, headers=self._headers())
5311 self.assertEqual(http.OK, response.status_code)
5312 images = jsonutils.loads(response.text)['images']
5313 self.assertEqual(0, len(images))
5314
5315 self.stop_servers()
5316
5317 def test_image_lifecycle_different_backend(self):
5318 # Image list should be empty
5319 self.start_servers(**self.__dict__.copy())
5320 path = self._url('/v2/images')
5321 response = requests.get(path, headers=self._headers())
5322 self.assertEqual(http.OK, response.status_code)
5323 images = jsonutils.loads(response.text)['images']
5324 self.assertEqual(0, len(images))
5325
5326 # file1 and file2 should be available in discovery response
5327 available_stores = ['file1', 'file2']
5328 path = self._url('/v2/info/stores')
5329 response = requests.get(path, headers=self._headers())
5330 self.assertEqual(http.OK, response.status_code)
5331 discovery_calls = jsonutils.loads(
5332 response.text)['stores']
5333 for stores in discovery_calls:
5334 self.assertIn('id', stores)
5335 self.assertIn(stores['id'], available_stores)
5336
5337 # Create an image (with two deployer-defined properties)
5338 path = self._url('/v2/images')
5339 headers = self._headers({'content-type': 'application/json'})
5340 data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
5341 'foo': 'bar', 'disk_format': 'aki',
5342 'container_format': 'aki', 'abc': 'xyz',
5343 'protected': True})
5344 response = requests.post(path, headers=headers, data=data)
5345 self.assertEqual(http.CREATED, response.status_code)
5346
5347 # Check 'OpenStack-image-store-ids' header present in response
5348 self.assertIn('OpenStack-image-store-ids', response.headers)
5349 for store in available_stores:
5350 self.assertIn(store, response.headers['OpenStack-image-store-ids'])
5351
5352 # Returned image entity should have a generated id and status
5353 image = jsonutils.loads(response.text)
5354 image_id = image['id']
5355 checked_keys = set([
5356 u'status',
5357 u'name',
5358 u'tags',
5359 u'created_at',
5360 u'updated_at',
5361 u'visibility',
5362 u'self',
5363 u'protected',
5364 u'id',
5365 u'file',
5366 u'min_disk',
5367 u'foo',
5368 u'abc',
5369 u'type',
5370 u'min_ram',
5371 u'schema',
5372 u'disk_format',
5373 u'container_format',
5374 u'owner',
5375 u'checksum',
5376 u'size',
5377 u'virtual_size',
5378 u'os_hidden',
5379 u'os_hash_algo',
5380 u'os_hash_value'
5381
5382 ])
5383 self.assertEqual(checked_keys, set(image.keys()))
5384 expected_image = {
5385 'status': 'queued',
5386 'name': 'image-1',
5387 'tags': [],
5388 'visibility': 'shared',
5389 'self': '/v2/images/%s' % image_id,
5390 'protected': True,
5391 'file': '/v2/images/%s/file' % image_id,
5392 'min_disk': 0,
5393 'foo': 'bar',
5394 'abc': 'xyz',
5395 'type': 'kernel',
5396 'min_ram': 0,
5397 'schema': '/v2/schemas/image',
5398 }
5399 for key, value in expected_image.items():
5400 self.assertEqual(value, image[key], key)
5401
5402 # Image list should now have one entry
5403 path = self._url('/v2/images')
5404 response = requests.get(path, headers=self._headers())
5405 self.assertEqual(http.OK, response.status_code)
5406 images = jsonutils.loads(response.text)['images']
5407 self.assertEqual(1, len(images))
5408 self.assertEqual(image_id, images[0]['id'])
5409
5410 # Try to download data before its uploaded
5411 path = self._url('/v2/images/%s/file' % image_id)
5412 headers = self._headers()
5413 response = requests.get(path, headers=headers)
5414 self.assertEqual(http.NO_CONTENT, response.status_code)
5415
5416 def _verify_image_checksum_and_status(checksum, status):
5417 # Checksum should be populated and status should be active
5418 path = self._url('/v2/images/%s' % image_id)
5419 response = requests.get(path, headers=self._headers())
5420 self.assertEqual(http.OK, response.status_code)
5421 image = jsonutils.loads(response.text)
5422 self.assertEqual(checksum, image['checksum'])
5423 self.assertEqual(status, image['status'])
5424
5425 # Upload some image data
5426 path = self._url('/v2/images/%s/file' % image_id)
5427 headers = self._headers({
5428 'Content-Type': 'application/octet-stream',
5429 'X-Image-Meta-Store': 'file2'
5430 })
5431 response = requests.put(path, headers=headers, data='ZZZZZ')
5432 self.assertEqual(http.NO_CONTENT, response.status_code)
5433
5434 expected_checksum = '8f113e38d28a79a5a451b16048cc2b72'
5435 _verify_image_checksum_and_status(expected_checksum, 'active')
5436
5437 # Ensure image is created in different backend
5438 path = self._url('/v2/images/%s' % image_id)
5439 response = requests.get(path, headers=self._headers())
5440 self.assertEqual(http.OK, response.status_code)
5441 self.assertIn('file2', jsonutils.loads(response.text)['stores'])
5442
5443 # Try to download the data that was just uploaded
5444 path = self._url('/v2/images/%s/file' % image_id)
5445 response = requests.get(path, headers=self._headers())
5446 self.assertEqual(http.OK, response.status_code)
5447 self.assertEqual(expected_checksum, response.headers['Content-MD5'])
5448 self.assertEqual('ZZZZZ', response.text)
5449
5450 # Ensure the size is updated to reflect the data uploaded
5451 path = self._url('/v2/images/%s' % image_id)
5452 response = requests.get(path, headers=self._headers())
5453 self.assertEqual(http.OK, response.status_code)
5454 self.assertEqual(5, jsonutils.loads(response.text)['size'])
5455
5456 # Unprotect image for deletion
5457 path = self._url('/v2/images/%s' % image_id)
5458 media_type = 'application/openstack-images-v2.1-json-patch'
5459 headers = self._headers({'content-type': media_type})
5460 doc = [{'op': 'replace', 'path': '/protected', 'value': False}]
5461 data = jsonutils.dumps(doc)
5462 response = requests.patch(path, headers=headers, data=data)
5463 self.assertEqual(http.OK, response.status_code, response.text)
5464
5465 # Deletion should work. Deleting image
5466 path = self._url('/v2/images/%s' % image_id)
5467 response = requests.delete(path, headers=self._headers())
5468 self.assertEqual(http.NO_CONTENT, response.status_code)
5469
5470 # This image should be no longer be directly accessible
5471 path = self._url('/v2/images/%s' % image_id)
5472 response = requests.get(path, headers=self._headers())
5473 self.assertEqual(http.NOT_FOUND, response.status_code)
5474
5475 # And neither should its data
5476 path = self._url('/v2/images/%s/file' % image_id)
5477 headers = self._headers()
5478 response = requests.get(path, headers=headers)
5479 self.assertEqual(http.NOT_FOUND, response.status_code)
5480
5481 # Image list should now be empty
5482 path = self._url('/v2/images')
5483 response = requests.get(path, headers=self._headers())
5484 self.assertEqual(http.OK, response.status_code)
5485 images = jsonutils.loads(response.text)['images']
5486 self.assertEqual(0, len(images))
5487
5488 self.stop_servers()
diff --git a/glance/tests/unit/base.py b/glance/tests/unit/base.py
index 2777a77..1cd992b 100644
--- a/glance/tests/unit/base.py
+++ b/glance/tests/unit/base.py
@@ -54,6 +54,34 @@ class StoreClearingUnitTest(test_utils.BaseTestCase):
54 store.create_stores(CONF) 54 store.create_stores(CONF)
55 55
56 56
57class MultiStoreClearingUnitTest(test_utils.BaseTestCase):
58
59 def setUp(self):
60 super(MultiStoreClearingUnitTest, self).setUp()
61 # Ensure stores + locations cleared
62 location.SCHEME_TO_CLS_BACKEND_MAP = {}
63
64 self._create_multi_stores()
65 self.addCleanup(setattr, location, 'SCHEME_TO_CLS_MAP', dict())
66
67 def _create_multi_stores(self, passing_config=True):
68 """Create known stores. Mock out sheepdog's subprocess dependency
69 on collie.
70
71 :param passing_config: making store driver passes basic configurations.
72 :returns: the number of how many store drivers been loaded.
73 """
74 self.config(enabled_backends={'file1': 'file', 'ceph1': 'rbd'})
75 store.register_store_opts(CONF)
76
77 self.config(default_backend='file1',
78 group='glance_store')
79
80 self.config(filesystem_store_datadir=self.test_dir,
81 group='file1')
82 store.create_multi_stores(CONF)
83
84
57class IsolatedUnitTest(StoreClearingUnitTest): 85class IsolatedUnitTest(StoreClearingUnitTest):
58 86
59 """ 87 """
@@ -82,3 +110,27 @@ class IsolatedUnitTest(StoreClearingUnitTest):
82 fap = open(CONF.oslo_policy.policy_file, 'w') 110 fap = open(CONF.oslo_policy.policy_file, 'w')
83 fap.write(jsonutils.dumps(rules)) 111 fap.write(jsonutils.dumps(rules))
84 fap.close() 112 fap.close()
113
114
115class MultiIsolatedUnitTest(MultiStoreClearingUnitTest):
116
117 """
118 Unit test case that establishes a mock environment within
119 a testing directory (in isolation)
120 """
121 registry = None
122
123 def setUp(self):
124 super(MultiIsolatedUnitTest, self).setUp()
125 options.set_defaults(CONF, connection='sqlite://')
126 lockutils.set_defaults(os.path.join(self.test_dir))
127
128 self.config(debug=False)
129 stubs.stub_out_registry_and_store_server(self.stubs,
130 self.test_dir,
131 registry=self.registry)
132
133 def set_policy_rules(self, rules):
134 fap = open(CONF.oslo_policy.policy_file, 'w')
135 fap.write(jsonutils.dumps(rules))
136 fap.close()
diff --git a/glance/tests/unit/v2/test_discovery_stores.py b/glance/tests/unit/v2/test_discovery_stores.py
new file mode 100644
index 0000000..44162aa
--- /dev/null
+++ b/glance/tests/unit/v2/test_discovery_stores.py
@@ -0,0 +1,48 @@
1# Copyright (c) 2018-2019 RedHat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15from oslo_config import cfg
16import webob.exc
17
18import glance.api.v2.discovery
19from glance.tests.unit import base
20import glance.tests.unit.utils as unit_test_utils
21
22
23CONF = cfg.CONF
24
25
26class TestInfoControllers(base.MultiStoreClearingUnitTest):
27 def setUp(self):
28 super(TestInfoControllers, self).setUp()
29 self.controller = glance.api.v2.discovery.InfoController()
30
31 def tearDown(self):
32 super(TestInfoControllers, self).tearDown()
33
34 def test_get_stores_with_enabled_backends_empty(self):
35 self.config(enabled_backends={})
36 req = unit_test_utils.get_fake_request()
37 self.assertRaises(webob.exc.HTTPNotFound,
38 self.controller.get_stores,
39 req)
40
41 def test_get_stores(self):
42 available_stores = ['ceph1', 'file1']
43 req = unit_test_utils.get_fake_request()
44 output = self.controller.get_stores(req)
45 self.assertIn('stores', output)
46 for stores in output['stores']:
47 self.assertIn('id', stores)
48 self.assertIn(stores['id'], available_stores)
diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py
index 44ae67e..a6e122b 100644
--- a/glance/tests/unit/v2/test_image_data_resource.py
+++ b/glance/tests/unit/v2/test_image_data_resource.py
@@ -993,3 +993,35 @@ class TestImageDataSerializer(test_utils.BaseTestCase):
993 self.serializer.stage(response, {}) 993 self.serializer.stage(response, {})
994 self.assertEqual(http.NO_CONTENT, response.status_int) 994 self.assertEqual(http.NO_CONTENT, response.status_int)
995 self.assertEqual('0', response.headers['Content-Length']) 995 self.assertEqual('0', response.headers['Content-Length'])
996
997
998class TestMultiBackendImagesController(base.MultiStoreClearingUnitTest):
999
1000 def setUp(self):
1001 super(TestMultiBackendImagesController, self).setUp()
1002
1003 self.config(debug=True)
1004 self.image_repo = FakeImageRepo()
1005 db = unit_test_utils.FakeDB()
1006 policy = unit_test_utils.FakePolicyEnforcer()
1007 notifier = unit_test_utils.FakeNotifier()
1008 store = unit_test_utils.FakeStoreAPI()
1009 self.controller = glance.api.v2.image_data.ImageDataController()
1010 self.controller.gateway = FakeGateway(db, store, notifier, policy,
1011 self.image_repo)
1012
1013 def test_upload(self):
1014 request = unit_test_utils.get_fake_request()
1015 image = FakeImage('abcd')
1016 self.image_repo.result = image
1017 self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
1018 self.assertEqual('YYYY', image.data)
1019 self.assertEqual(4, image.size)
1020
1021 def test_upload_invalid_backend_in_request_header(self):
1022 request = unit_test_utils.get_fake_request()
1023 request.headers['x-image-meta-store'] = 'dummy'
1024 image = FakeImage('abcd')
1025 self.image_repo.result = image
1026 self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
1027 request, unit_test_utils.UUID2, 'YYYY', 4)
diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py
index 0b6b486..1786be2 100644
--- a/glance/tests/unit/v2/test_images_resource.py
+++ b/glance/tests/unit/v2/test_images_resource.py
@@ -4267,3 +4267,112 @@ class TestImageSchemaDeterminePropertyBasis(test_utils.BaseTestCase):
4267 def test_base_property_marked_as_base(self): 4267 def test_base_property_marked_as_base(self):
4268 schema = glance.api.v2.images.get_schema() 4268 schema = glance.api.v2.images.get_schema()
4269 self.assertTrue(schema.properties['disk_format'].get('is_base', True)) 4269 self.assertTrue(schema.properties['disk_format'].get('is_base', True))
4270
4271
4272class TestMultiImagesController(base.MultiIsolatedUnitTest):
4273
4274 def setUp(self):
4275 super(TestMultiImagesController, self).setUp()
4276 self.db = unit_test_utils.FakeDB(initialize=False)
4277 self.policy = unit_test_utils.FakePolicyEnforcer()
4278 self.notifier = unit_test_utils.FakeNotifier()
4279 self.store = store
4280 self._create_images()
4281 self._create_image_members()
4282 self.controller = glance.api.v2.images.ImagesController(self.db,
4283 self.policy,
4284 self.notifier,
4285 self.store)
4286
4287 def _create_images(self):
4288 self.images = [
4289 _db_fixture(UUID1, owner=TENANT1, checksum=CHKSUM,
4290 name='1', size=256, virtual_size=1024,
4291 visibility='public',
4292 locations=[{'url': '%s/%s' % (BASE_URI, UUID1),
4293 'metadata': {}, 'status': 'active'}],
4294 disk_format='raw',
4295 container_format='bare',
4296 status='active'),
4297 _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
4298 name='2', size=512, virtual_size=2048,
4299 visibility='public',
4300 disk_format='raw',
4301 container_format='bare',
4302 status='active',
4303 tags=['redhat', '64bit', 'power'],
4304 properties={'hypervisor_type': 'kvm', 'foo': 'bar',
4305 'bar': 'foo'}),
4306 _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
4307 name='3', size=512, virtual_size=2048,
4308 visibility='public', tags=['windows', '64bit', 'x86']),
4309 _db_fixture(UUID4, owner=TENANT4, name='4',
4310 size=1024, virtual_size=3072),
4311 ]
4312 [self.db.image_create(None, image) for image in self.images]
4313
4314 self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
4315
4316 def _create_image_members(self):
4317 self.image_members = [
4318 _db_image_member_fixture(UUID4, TENANT2),
4319 _db_image_member_fixture(UUID4, TENANT3,
4320 status='accepted'),
4321 ]
4322 [self.db.image_member_create(None, image_member)
4323 for image_member in self.image_members]
4324
4325 def test_image_import_image_not_exist(self):
4326 request = unit_test_utils.get_fake_request()
4327 self.assertRaises(webob.exc.HTTPNotFound,
4328 self.controller.import_image,
4329 request, 'invalid_image',
4330 {'method': {'name': 'glance-direct'}})
4331
4332 def test_image_import_with_active_image(self):
4333 request = unit_test_utils.get_fake_request()
4334 self.assertRaises(webob.exc.HTTPConflict,
4335 self.controller.import_image,
4336 request, UUID1,
4337 {'method': {'name': 'glance-direct'}})
4338
4339 def test_image_import_invalid_backend_in_request_header(self):
4340 request = unit_test_utils.get_fake_request()
4341 request.headers['x-image-meta-store'] = 'dummy'
4342 with mock.patch.object(
4343 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
4344 mock_get.return_value = FakeImage(status='uploading')
4345 self.assertRaises(webob.exc.HTTPConflict,
4346 self.controller.import_image,
4347 request, UUID4,
4348 {'method': {'name': 'glance-direct'}})
4349
4350 def test_image_import_raises_conflict_if_disk_format_is_none(self):
4351 request = unit_test_utils.get_fake_request()
4352
4353 with mock.patch.object(
4354 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
4355 mock_get.return_value = FakeImage(disk_format=None)
4356 self.assertRaises(webob.exc.HTTPConflict,
4357 self.controller.import_image, request, UUID4,
4358 {'method': {'name': 'glance-direct'}})
4359
4360 def test_image_import_raises_conflict(self):
4361 request = unit_test_utils.get_fake_request()
4362
4363 with mock.patch.object(
4364 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
4365 mock_get.return_value = FakeImage(status='queued')
4366 self.assertRaises(webob.exc.HTTPConflict,
4367 self.controller.import_image, request, UUID4,
4368 {'method': {'name': 'glance-direct'}})
4369
4370 def test_image_import_raises_conflict_for_web_download(self):
4371 request = unit_test_utils.get_fake_request()
4372
4373 with mock.patch.object(
4374 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
4375 mock_get.return_value = FakeImage()
4376 self.assertRaises(webob.exc.HTTPConflict,
4377 self.controller.import_image, request, UUID4,
4378 {'method': {'name': 'web-download'}})