glance-16.0.1/ 0000775 0001750 0001750 00000000000 13267672475 013075 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/.coveragerc 0000666 0001750 0001750 00000000131 13267672245 015206 0 ustar zuul zuul 0000000 0000000 [run]
branch = True
source = glance
omit = glance/tests/*
[report]
ignore_errors = True
glance-16.0.1/LICENSE 0000666 0001750 0001750 00000023637 13267672245 014112 0 ustar zuul zuul 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
glance-16.0.1/setup.py 0000666 0001750 0001750 00000002006 13267672245 014602 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)
glance-16.0.1/PKG-INFO 0000664 0001750 0001750 00000007334 13267672475 014201 0 ustar zuul zuul 0000000 0000000 Metadata-Version: 1.1
Name: glance
Version: 16.0.1
Summary: OpenStack Image Service
Home-page: https://docs.openstack.org/glance/latest/
Author: OpenStack
Author-email: openstack-dev@lists.openstack.org
License: UNKNOWN
Description: ========================
Team and repository tags
========================
.. image:: http://governance.openstack.org/badges/glance.svg
:target: http://governance.openstack.org/reference/tags/index.html
:alt: The following tags have been asserted for the Glance project:
"project:official",
"tc:approved-release",
"stable:follows-policy",
"tc:starter-kit:compute",
"vulnerability:managed",
"team:diverse-affiliation",
"assert:supports-upgrade",
"assert:follows-standard-deprecation".
Follow the link for an explanation of these tags.
.. NOTE(rosmaita): the alt text above will have to be updated when
additional tags are asserted for Glance. (The SVG in the
governance repo is updated automatically.)
.. Change things from this point on
======
Glance
======
Glance is a project that provides services and associated libraries
to store, browse, share, distribute and manage bootable disk images,
other data closely associated with initializing compute resources,
and metadata definitions.
Use the following resources to learn more:
API
---
To learn how to use Glance's API, consult the documentation available
online at:
* `Image Service APIs `_
Developers
----------
For information on how to contribute to Glance, please see the contents
of the CONTRIBUTING.rst in this repository.
Any new code must follow the development guidelines detailed in the
HACKING.rst file, and pass all unit tests.
Further developer focused documentation is available at:
* `Official Glance documentation `_
* `Official Client documentation `_
Operators
---------
To learn how to deploy and configure OpenStack Glance, consult the
documentation available online at:
* `Openstack Glance `_
In the unfortunate event that bugs are discovered, they should be
reported to the appropriate bug tracker. You can raise bugs here:
* `Bug Tracker `_
Other Information
-----------------
During each design summit, we agree on what the whole community wants
to focus on for the upcoming release. You can see image service plans:
* `Image Service Plans `_
For more information about the Glance project please see:
* `Glance Project `_
Platform: UNKNOWN
Classifier: Environment :: OpenStack
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
glance-16.0.1/requirements.txt 0000666 0001750 0001750 00000003234 13267672254 016360 0 ustar zuul zuul 0000000 0000000 # The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
defusedxml>=0.5.0 # PSF
# < 0.8.0/0.8 does not work, see https://bugs.launchpad.net/bugs/1153983
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT
PasteDeploy>=1.5.0 # MIT
Routes>=2.3.1 # MIT
WebOb>=1.7.1 # MIT
sqlalchemy-migrate>=0.11.0 # Apache-2.0
sqlparse>=0.2.2 # BSD
alembic>=0.8.10 # MIT
httplib2>=0.9.1 # MIT
oslo.config>=5.1.0 # Apache-2.0
oslo.concurrency>=3.25.0 # Apache-2.0
oslo.context>=2.19.2 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
stevedore>=1.20.0 # Apache-2.0
futurist>=1.2.0 # Apache-2.0
taskflow>=2.16.0 # Apache-2.0
keystoneauth1>=3.3.0 # Apache-2.0
keystonemiddleware>=4.17.0 # Apache-2.0
WSME>=0.8.0 # MIT
PrettyTable<0.8,>=0.7.1 # BSD
# For paste.util.template used in keystone.common.template
Paste>=2.0.2 # MIT
jsonschema<3.0.0,>=2.6.0 # MIT
python-keystoneclient>=3.8.0 # Apache-2.0
pyOpenSSL>=16.2.0 # Apache-2.0
# Required by openstack.common libraries
six>=1.10.0 # MIT
oslo.db>=4.27.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
oslo.messaging>=5.29.0 # Apache-2.0
oslo.middleware>=3.31.0 # Apache-2.0
oslo.policy>=1.30.0 # Apache-2.0
retrying!=1.3.0,>=1.2.3 # Apache-2.0
osprofiler>=1.4.0 # Apache-2.0
# Glance Store
glance-store>=0.22.0 # Apache-2.0
debtcollector>=1.2.0 # Apache-2.0
cryptography!=2.0,>=1.9 # BSD/Apache-2.0
cursive>=0.2.1 # Apache-2.0
# timeutils
iso8601>=0.1.11 # MIT
monotonic>=0.6 # Apache-2.0
glance-16.0.1/glance.egg-info/ 0000775 0001750 0001750 00000000000 13267672475 016020 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance.egg-info/PKG-INFO 0000664 0001750 0001750 00000007334 13267672474 017123 0 ustar zuul zuul 0000000 0000000 Metadata-Version: 1.1
Name: glance
Version: 16.0.1
Summary: OpenStack Image Service
Home-page: https://docs.openstack.org/glance/latest/
Author: OpenStack
Author-email: openstack-dev@lists.openstack.org
License: UNKNOWN
Description: ========================
Team and repository tags
========================
.. image:: http://governance.openstack.org/badges/glance.svg
:target: http://governance.openstack.org/reference/tags/index.html
:alt: The following tags have been asserted for the Glance project:
"project:official",
"tc:approved-release",
"stable:follows-policy",
"tc:starter-kit:compute",
"vulnerability:managed",
"team:diverse-affiliation",
"assert:supports-upgrade",
"assert:follows-standard-deprecation".
Follow the link for an explanation of these tags.
.. NOTE(rosmaita): the alt text above will have to be updated when
additional tags are asserted for Glance. (The SVG in the
governance repo is updated automatically.)
.. Change things from this point on
======
Glance
======
Glance is a project that provides services and associated libraries
to store, browse, share, distribute and manage bootable disk images,
other data closely associated with initializing compute resources,
and metadata definitions.
Use the following resources to learn more:
API
---
To learn how to use Glance's API, consult the documentation available
online at:
* `Image Service APIs `_
Developers
----------
For information on how to contribute to Glance, please see the contents
of the CONTRIBUTING.rst in this repository.
Any new code must follow the development guidelines detailed in the
HACKING.rst file, and pass all unit tests.
Further developer focused documentation is available at:
* `Official Glance documentation `_
* `Official Client documentation `_
Operators
---------
To learn how to deploy and configure OpenStack Glance, consult the
documentation available online at:
* `Openstack Glance `_
In the unfortunate event that bugs are discovered, they should be
reported to the appropriate bug tracker. You can raise bugs here:
* `Bug Tracker `_
Other Information
-----------------
During each design summit, we agree on what the whole community wants
to focus on for the upcoming release. You can see image service plans:
* `Image Service Plans `_
For more information about the Glance project please see:
* `Glance Project `_
Platform: UNKNOWN
Classifier: Environment :: OpenStack
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
glance-16.0.1/glance.egg-info/requires.txt 0000664 0001750 0001750 00000001510 13267672474 020414 0 ustar zuul zuul 0000000 0000000 pbr!=2.1.0,>=2.0.0
defusedxml>=0.5.0
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10
eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2
PasteDeploy>=1.5.0
Routes>=2.3.1
WebOb>=1.7.1
sqlalchemy-migrate>=0.11.0
sqlparse>=0.2.2
alembic>=0.8.10
httplib2>=0.9.1
oslo.config>=5.1.0
oslo.concurrency>=3.25.0
oslo.context>=2.19.2
oslo.utils>=3.33.0
stevedore>=1.20.0
futurist>=1.2.0
taskflow>=2.16.0
keystoneauth1>=3.3.0
keystonemiddleware>=4.17.0
WSME>=0.8.0
PrettyTable<0.8,>=0.7.1
Paste>=2.0.2
jsonschema<3.0.0,>=2.6.0
python-keystoneclient>=3.8.0
pyOpenSSL>=16.2.0
six>=1.10.0
oslo.db>=4.27.0
oslo.i18n>=3.15.3
oslo.log>=3.36.0
oslo.messaging>=5.29.0
oslo.middleware>=3.31.0
oslo.policy>=1.30.0
retrying!=1.3.0,>=1.2.3
osprofiler>=1.4.0
glance-store>=0.22.0
debtcollector>=1.2.0
cryptography!=2.0,>=1.9
cursive>=0.2.1
iso8601>=0.1.11
monotonic>=0.6
glance-16.0.1/glance.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 13267672474 022065 0 ustar zuul zuul 0000000 0000000
glance-16.0.1/glance.egg-info/pbr.json 0000664 0001750 0001750 00000000056 13267672474 017476 0 ustar zuul zuul 0000000 0000000 {"git_version": "42fe71f", "is_release": true} glance-16.0.1/glance.egg-info/top_level.txt 0000664 0001750 0001750 00000000007 13267672474 020546 0 ustar zuul zuul 0000000 0000000 glance
glance-16.0.1/glance.egg-info/not-zip-safe 0000664 0001750 0001750 00000000001 13267672427 020243 0 ustar zuul zuul 0000000 0000000
glance-16.0.1/glance.egg-info/entry_points.txt 0000664 0001750 0001750 00000003474 13267672474 021325 0 ustar zuul zuul 0000000 0000000 [console_scripts]
glance-api = glance.cmd.api:main
glance-cache-cleaner = glance.cmd.cache_cleaner:main
glance-cache-manage = glance.cmd.cache_manage:main
glance-cache-prefetcher = glance.cmd.cache_prefetcher:main
glance-cache-pruner = glance.cmd.cache_pruner:main
glance-control = glance.cmd.control:main
glance-manage = glance.cmd.manage:main
glance-registry = glance.cmd.registry:main
glance-replicator = glance.cmd.replicator:main
glance-scrubber = glance.cmd.scrubber:main
[glance.common.image_location_strategy.modules]
location_order_strategy = glance.common.location_strategy.location_order
store_type_strategy = glance.common.location_strategy.store_type
[glance.database.metadata_backend]
sqlalchemy = glance.db.sqlalchemy.metadata
[glance.database.migration_backend]
sqlalchemy = oslo_db.sqlalchemy.migration
[glance.flows]
api_image_import = glance.async.flows.api_image_import:get_flow
import = glance.async.flows.base_import:get_flow
[glance.flows.import]
convert = glance.async.flows.convert:get_flow
introspect = glance.async.flows.introspect:get_flow
ovf_process = glance.async.flows.ovf_process:get_flow
[glance.image_import.internal_plugins]
web_download = glance.async.flows._internal_plugins.web_download:get_flow
[glance.image_import.plugins]
inject_image_metadata = glance.async.flows.plugins.inject_image_metadata:get_flow
no_op = glance.async.flows.plugins.no_op:get_flow
[oslo.config.opts]
glance = glance.opts:list_image_import_opts
glance.api = glance.opts:list_api_opts
glance.cache = glance.opts:list_cache_opts
glance.manage = glance.opts:list_manage_opts
glance.registry = glance.opts:list_registry_opts
glance.scrubber = glance.opts:list_scrubber_opts
[oslo.config.opts.defaults]
glance.api = glance.common.config:set_cors_middleware_defaults
[wsgi_scripts]
glance-wsgi-api = glance.common.wsgi_app:init_app
glance-16.0.1/glance.egg-info/SOURCES.txt 0000664 0001750 0001750 00000101020 13267672475 017676 0 ustar zuul zuul 0000000 0000000 .coveragerc
.mailmap
.stestr.conf
.zuul.yaml
AUTHORS
CONTRIBUTING.rst
ChangeLog
HACKING.rst
LICENSE
README.rst
babel.cfg
bandit.yaml
bindep.txt
pylintrc
requirements.txt
setup.cfg
setup.py
test-requirements.txt
tox.ini
api-ref/source/conf.py
api-ref/source/heading-level-guide.txt
api-ref/source/index.rst
api-ref/source/v1/images-images-v1.inc
api-ref/source/v1/images-sharing-v1.inc
api-ref/source/v1/index.rst
api-ref/source/v1/parameters.yaml
api-ref/source/v1/samples/image-member-add-request.json
api-ref/source/v1/samples/image-members-add-request.json
api-ref/source/v1/samples/image-memberships-list-response.json
api-ref/source/v1/samples/image-update-response.json
api-ref/source/v1/samples/images-create-reserve-response.json
api-ref/source/v1/samples/images-create-with-data-response.json
api-ref/source/v1/samples/images-list-details-response.json
api-ref/source/v1/samples/images-list-response.json
api-ref/source/v1/samples/shared-images-list-response.json
api-ref/source/v2/images-data.inc
api-ref/source/v2/images-images-v2.inc
api-ref/source/v2/images-import.inc
api-ref/source/v2/images-parameters-descriptions.inc
api-ref/source/v2/images-parameters.yaml
api-ref/source/v2/images-schemas.inc
api-ref/source/v2/images-sharing-v2.inc
api-ref/source/v2/images-tags.inc
api-ref/source/v2/index.rst
api-ref/source/v2/metadefs-index.rst
api-ref/source/v2/metadefs-namespaces-objects.inc
api-ref/source/v2/metadefs-namespaces-properties.inc
api-ref/source/v2/metadefs-namespaces-tags.inc
api-ref/source/v2/metadefs-namespaces.inc
api-ref/source/v2/metadefs-parameters.yaml
api-ref/source/v2/metadefs-resourcetypes.inc
api-ref/source/v2/metadefs-schemas.inc
api-ref/source/v2/tasks-parameters.yaml
api-ref/source/v2/tasks-schemas.inc
api-ref/source/v2/tasks.inc
api-ref/source/v2/samples/image-create-request.json
api-ref/source/v2/samples/image-create-response.json
api-ref/source/v2/samples/image-details-deactivate-response.json
api-ref/source/v2/samples/image-import-g-d-request.json
api-ref/source/v2/samples/image-import-w-d-request.json
api-ref/source/v2/samples/image-info-import-response.json
api-ref/source/v2/samples/image-member-create-request.json
api-ref/source/v2/samples/image-member-create-response.json
api-ref/source/v2/samples/image-member-details-response.json
api-ref/source/v2/samples/image-member-update-request.json
api-ref/source/v2/samples/image-member-update-response.json
api-ref/source/v2/samples/image-members-list-response.json
api-ref/source/v2/samples/image-show-response.json
api-ref/source/v2/samples/image-update-request.json
api-ref/source/v2/samples/image-update-response.json
api-ref/source/v2/samples/images-list-response.json
api-ref/source/v2/samples/metadef-namespace-create-request-simple.json
api-ref/source/v2/samples/metadef-namespace-create-request.json
api-ref/source/v2/samples/metadef-namespace-create-response-simple.json
api-ref/source/v2/samples/metadef-namespace-create-response.json
api-ref/source/v2/samples/metadef-namespace-details-response.json
api-ref/source/v2/samples/metadef-namespace-details-with-rt-response.json
api-ref/source/v2/samples/metadef-namespace-update-request.json
api-ref/source/v2/samples/metadef-namespace-update-response.json
api-ref/source/v2/samples/metadef-namespaces-list-response.json
api-ref/source/v2/samples/metadef-object-create-request.json
api-ref/source/v2/samples/metadef-object-create-response.json
api-ref/source/v2/samples/metadef-object-details-response.json
api-ref/source/v2/samples/metadef-object-update-request.json
api-ref/source/v2/samples/metadef-object-update-response.json
api-ref/source/v2/samples/metadef-objects-list-response.json
api-ref/source/v2/samples/metadef-properties-list-response.json
api-ref/source/v2/samples/metadef-property-create-request.json
api-ref/source/v2/samples/metadef-property-create-response.json
api-ref/source/v2/samples/metadef-property-details-response.json
api-ref/source/v2/samples/metadef-property-update-request.json
api-ref/source/v2/samples/metadef-property-update-response.json
api-ref/source/v2/samples/metadef-resource-type-assoc-create-response.json
api-ref/source/v2/samples/metadef-resource-type-create-request.json
api-ref/source/v2/samples/metadef-resource-types-list-response.json
api-ref/source/v2/samples/metadef-tag-create-response.json
api-ref/source/v2/samples/metadef-tag-details-response.json
api-ref/source/v2/samples/metadef-tag-update-request.json
api-ref/source/v2/samples/metadef-tag-update-response.json
api-ref/source/v2/samples/metadef-tags-create-request.json
api-ref/source/v2/samples/metadef-tags-create-response.json
api-ref/source/v2/samples/metadef-tags-list-response.json
api-ref/source/v2/samples/schemas-image-member-show-response.json
api-ref/source/v2/samples/schemas-image-members-list-response.json
api-ref/source/v2/samples/schemas-image-show-response.json
api-ref/source/v2/samples/schemas-images-list-response.json
api-ref/source/v2/samples/schemas-metadef-namespace-show-response.json
api-ref/source/v2/samples/schemas-metadef-namespaces-list-response.json
api-ref/source/v2/samples/schemas-metadef-object-show-response.json
api-ref/source/v2/samples/schemas-metadef-objects-list-response.json
api-ref/source/v2/samples/schemas-metadef-properties-list-response.json
api-ref/source/v2/samples/schemas-metadef-property-show-response.json
api-ref/source/v2/samples/schemas-metadef-resource-type-association-show-response.json
api-ref/source/v2/samples/schemas-metadef-resource-type-associations-list-response.json
api-ref/source/v2/samples/schemas-metadef-tag-show-response.json
api-ref/source/v2/samples/schemas-metadef-tags-list-response.json
api-ref/source/v2/samples/schemas-task-show-response.json
api-ref/source/v2/samples/schemas-tasks-list-response.json
api-ref/source/v2/samples/task-create-request.json
api-ref/source/v2/samples/task-create-response.json
api-ref/source/v2/samples/task-show-failure-response.json
api-ref/source/v2/samples/task-show-processing-response.json
api-ref/source/v2/samples/task-show-success-response.json
api-ref/source/v2/samples/tasks-list-response.json
api-ref/source/versions/index.rst
api-ref/source/versions/versions.inc
api-ref/source/versions/samples/image-versions-response.json
doc/source/conf.py
doc/source/deprecate-registry.inc
doc/source/deprecation-note.inc
doc/source/glossary.rst
doc/source/index.rst
doc/source/_static/.placeholder
doc/source/admin/apache-httpd.rst
doc/source/admin/authentication.rst
doc/source/admin/cache.rst
doc/source/admin/controllingservers.rst
doc/source/admin/db-sqlalchemy-migrate.rst
doc/source/admin/db.rst
doc/source/admin/flows.rst
doc/source/admin/index.rst
doc/source/admin/interoperable-image-import.rst
doc/source/admin/manage-images.rst
doc/source/admin/notifications.rst
doc/source/admin/policies.rst
doc/source/admin/property-protections.rst
doc/source/admin/requirements.rst
doc/source/admin/rollingupgrades.rst
doc/source/admin/tasks.rst
doc/source/admin/troubleshooting.rst
doc/source/admin/zero-downtime-db-upgrade.rst
doc/source/cli/footer.txt
doc/source/cli/general_options.txt
doc/source/cli/glanceapi.rst
doc/source/cli/glancecachecleaner.rst
doc/source/cli/glancecachemanage.rst
doc/source/cli/glancecacheprefetcher.rst
doc/source/cli/glancecachepruner.rst
doc/source/cli/glancecontrol.rst
doc/source/cli/glancemanage.rst
doc/source/cli/glanceregistry.rst
doc/source/cli/glancereplicator.rst
doc/source/cli/glancescrubber.rst
doc/source/cli/header.txt
doc/source/cli/index.rst
doc/source/cli/openstack_options.txt
doc/source/configuration/configuring.rst
doc/source/configuration/glance_api.rst
doc/source/configuration/glance_cache.rst
doc/source/configuration/glance_manage.rst
doc/source/configuration/glance_registry.rst
doc/source/configuration/glance_scrubber.rst
doc/source/configuration/index.rst
doc/source/configuration/sample-configuration.rst
doc/source/contributor/architecture.rst
doc/source/contributor/blueprints.rst
doc/source/contributor/database_architecture.rst
doc/source/contributor/database_migrations.rst
doc/source/contributor/documentation.rst
doc/source/contributor/domain_implementation.rst
doc/source/contributor/domain_model.rst
doc/source/contributor/index.rst
doc/source/contributor/minor-code-changes.rst
doc/source/contributor/modules.rst
doc/source/contributor/refreshing-configs.rst
doc/source/contributor/release-cpl.rst
doc/source/images/architecture.png
doc/source/images/glance_db.png
doc/source/images/glance_layers.png
doc/source/images/image_status_transition.png
doc/source/images/instance-life-1.png
doc/source/images/instance-life-2.png
doc/source/images/instance-life-3.png
doc/source/images_src/architecture.graphml
doc/source/images_src/glance_db.graphml
doc/source/images_src/glance_layers.graphml
doc/source/images_src/image_status_transition.dot
doc/source/install/get-started.rst
doc/source/install/index.rst
doc/source/install/install-debian.rst
doc/source/install/install-obs.rst
doc/source/install/install-rdo.rst
doc/source/install/install-ubuntu.rst
doc/source/install/install.rst
doc/source/install/note_configuration_vary_by_distribution.txt
doc/source/install/verify.rst
doc/source/user/common-image-properties.rst
doc/source/user/formats.rst
doc/source/user/glanceapi.rst
doc/source/user/glanceclient.rst
doc/source/user/glancemetadefcatalogapi.rst
doc/source/user/identifiers.rst
doc/source/user/index.rst
doc/source/user/metadefs-concepts.rst
doc/source/user/signature.rst
doc/source/user/statuses.rst
etc/glance-api-paste.ini
etc/glance-api.conf
etc/glance-cache.conf
etc/glance-image-import.conf.sample
etc/glance-manage.conf
etc/glance-registry-paste.ini
etc/glance-registry.conf
etc/glance-scrubber.conf
etc/glance-swift.conf.sample
etc/ovf-metadata.json.sample
etc/policy.json
etc/property-protections-policies.conf.sample
etc/property-protections-roles.conf.sample
etc/rootwrap.conf
etc/schema-image.json
etc/metadefs/README
etc/metadefs/cim-processor-allocation-setting-data.json
etc/metadefs/cim-resource-allocation-setting-data.json
etc/metadefs/cim-storage-allocation-setting-data.json
etc/metadefs/cim-virtual-system-setting-data.json
etc/metadefs/compute-aggr-disk-filter.json
etc/metadefs/compute-aggr-iops-filter.json
etc/metadefs/compute-aggr-num-instances.json
etc/metadefs/compute-cpu-pinning.json
etc/metadefs/compute-guest-memory-backing.json
etc/metadefs/compute-guest-shutdown.json
etc/metadefs/compute-host-capabilities.json
etc/metadefs/compute-hypervisor.json
etc/metadefs/compute-instance-data.json
etc/metadefs/compute-libvirt-image.json
etc/metadefs/compute-libvirt.json
etc/metadefs/compute-quota.json
etc/metadefs/compute-randomgen.json
etc/metadefs/compute-trust.json
etc/metadefs/compute-vcputopology.json
etc/metadefs/compute-vmware-flavor.json
etc/metadefs/compute-vmware-quota-flavor.json
etc/metadefs/compute-vmware.json
etc/metadefs/compute-watchdog.json
etc/metadefs/compute-xenapi.json
etc/metadefs/glance-common-image-props.json
etc/metadefs/image-signature-verification.json
etc/metadefs/operating-system.json
etc/metadefs/software-databases.json
etc/metadefs/software-runtimes.json
etc/metadefs/software-webservers.json
etc/metadefs/storage-volume-type.json
etc/oslo-config-generator/glance-api.conf
etc/oslo-config-generator/glance-cache.conf
etc/oslo-config-generator/glance-image-import.conf
etc/oslo-config-generator/glance-manage.conf
etc/oslo-config-generator/glance-registry.conf
etc/oslo-config-generator/glance-scrubber.conf
glance/__init__.py
glance/context.py
glance/gateway.py
glance/i18n.py
glance/location.py
glance/notifier.py
glance/opts.py
glance/schema.py
glance/scrubber.py
glance/version.py
glance.egg-info/PKG-INFO
glance.egg-info/SOURCES.txt
glance.egg-info/dependency_links.txt
glance.egg-info/entry_points.txt
glance.egg-info/not-zip-safe
glance.egg-info/pbr.json
glance.egg-info/requires.txt
glance.egg-info/top_level.txt
glance/api/__init__.py
glance/api/authorization.py
glance/api/cached_images.py
glance/api/common.py
glance/api/policy.py
glance/api/property_protections.py
glance/api/versions.py
glance/api/middleware/__init__.py
glance/api/middleware/cache.py
glance/api/middleware/cache_manage.py
glance/api/middleware/context.py
glance/api/middleware/gzip.py
glance/api/middleware/version_negotiation.py
glance/api/v1/__init__.py
glance/api/v1/controller.py
glance/api/v1/filters.py
glance/api/v1/images.py
glance/api/v1/members.py
glance/api/v1/router.py
glance/api/v1/upload_utils.py
glance/api/v2/__init__.py
glance/api/v2/discovery.py
glance/api/v2/image_actions.py
glance/api/v2/image_data.py
glance/api/v2/image_members.py
glance/api/v2/image_tags.py
glance/api/v2/images.py
glance/api/v2/metadef_namespaces.py
glance/api/v2/metadef_objects.py
glance/api/v2/metadef_properties.py
glance/api/v2/metadef_resource_types.py
glance/api/v2/metadef_tags.py
glance/api/v2/router.py
glance/api/v2/schemas.py
glance/api/v2/tasks.py
glance/api/v2/model/__init__.py
glance/api/v2/model/metadef_namespace.py
glance/api/v2/model/metadef_object.py
glance/api/v2/model/metadef_property_item_type.py
glance/api/v2/model/metadef_property_type.py
glance/api/v2/model/metadef_resource_type.py
glance/api/v2/model/metadef_tag.py
glance/async/__init__.py
glance/async/taskflow_executor.py
glance/async/utils.py
glance/async/flows/__init__.py
glance/async/flows/api_image_import.py
glance/async/flows/base_import.py
glance/async/flows/convert.py
glance/async/flows/introspect.py
glance/async/flows/ovf_process.py
glance/async/flows/_internal_plugins/__init__.py
glance/async/flows/_internal_plugins/web_download.py
glance/async/flows/plugins/__init__.py
glance/async/flows/plugins/inject_image_metadata.py
glance/async/flows/plugins/no_op.py
glance/async/flows/plugins/plugin_opts.py
glance/cmd/__init__.py
glance/cmd/api.py
glance/cmd/cache_cleaner.py
glance/cmd/cache_manage.py
glance/cmd/cache_prefetcher.py
glance/cmd/cache_pruner.py
glance/cmd/control.py
glance/cmd/manage.py
glance/cmd/registry.py
glance/cmd/replicator.py
glance/cmd/scrubber.py
glance/common/__init__.py
glance/common/auth.py
glance/common/client.py
glance/common/config.py
glance/common/crypt.py
glance/common/exception.py
glance/common/property_utils.py
glance/common/rpc.py
glance/common/store_utils.py
glance/common/swift_store_utils.py
glance/common/timeutils.py
glance/common/trust_auth.py
glance/common/utils.py
glance/common/wsgi.py
glance/common/wsgi_app.py
glance/common/wsme_utils.py
glance/common/location_strategy/__init__.py
glance/common/location_strategy/location_order.py
glance/common/location_strategy/store_type.py
glance/common/scripts/__init__.py
glance/common/scripts/utils.py
glance/common/scripts/api_image_import/__init__.py
glance/common/scripts/api_image_import/main.py
glance/common/scripts/image_import/__init__.py
glance/common/scripts/image_import/main.py
glance/db/__init__.py
glance/db/metadata.py
glance/db/migration.py
glance/db/utils.py
glance/db/registry/__init__.py
glance/db/registry/api.py
glance/db/simple/__init__.py
glance/db/simple/api.py
glance/db/sqlalchemy/__init__.py
glance/db/sqlalchemy/api.py
glance/db/sqlalchemy/metadata.py
glance/db/sqlalchemy/models.py
glance/db/sqlalchemy/models_metadef.py
glance/db/sqlalchemy/alembic_migrations/README
glance/db/sqlalchemy/alembic_migrations/__init__.py
glance/db/sqlalchemy/alembic_migrations/add_artifacts_tables.py
glance/db/sqlalchemy/alembic_migrations/add_images_tables.py
glance/db/sqlalchemy/alembic_migrations/add_metadefs_tables.py
glance/db/sqlalchemy/alembic_migrations/add_tasks_tables.py
glance/db/sqlalchemy/alembic_migrations/alembic.ini
glance/db/sqlalchemy/alembic_migrations/env.py
glance/db/sqlalchemy/alembic_migrations/migrate.cfg
glance/db/sqlalchemy/alembic_migrations/script.py.mako
glance/db/sqlalchemy/alembic_migrations/data_migrations/__init__.py
glance/db/sqlalchemy/alembic_migrations/data_migrations/ocata_migrate01_community_images.py
glance/db/sqlalchemy/alembic_migrations/data_migrations/pike_migrate01_empty.py
glance/db/sqlalchemy/alembic_migrations/data_migrations/queens_migrate01_empty.py
glance/db/sqlalchemy/alembic_migrations/versions/__init__.py
glance/db/sqlalchemy/alembic_migrations/versions/liberty_initial.py
glance/db/sqlalchemy/alembic_migrations/versions/mitaka01_add_image_created_updated_idx.py
glance/db/sqlalchemy/alembic_migrations/versions/mitaka02_update_metadef_os_nova_server.py
glance/db/sqlalchemy/alembic_migrations/versions/ocata_contract01_drop_is_public.py
glance/db/sqlalchemy/alembic_migrations/versions/ocata_expand01_add_visibility.py
glance/db/sqlalchemy/alembic_migrations/versions/pike_contract01_drop_artifacts_tables.py
glance/db/sqlalchemy/alembic_migrations/versions/pike_expand01_empty.py
glance/db/sqlalchemy/alembic_migrations/versions/queens_contract01_empty.py
glance/db/sqlalchemy/alembic_migrations/versions/queens_expand01_empty.py
glance/db/sqlalchemy/metadef_api/__init__.py
glance/db/sqlalchemy/metadef_api/namespace.py
glance/db/sqlalchemy/metadef_api/object.py
glance/db/sqlalchemy/metadef_api/property.py
glance/db/sqlalchemy/metadef_api/resource_type.py
glance/db/sqlalchemy/metadef_api/resource_type_association.py
glance/db/sqlalchemy/metadef_api/tag.py
glance/db/sqlalchemy/metadef_api/utils.py
glance/db/sqlalchemy/migrate_repo/README
glance/db/sqlalchemy/migrate_repo/__init__.py
glance/db/sqlalchemy/migrate_repo/manage.py
glance/db/sqlalchemy/migrate_repo/migrate.cfg
glance/db/sqlalchemy/migrate_repo/schema.py
glance/db/sqlalchemy/migrate_repo/versions/001_add_images_table.py
glance/db/sqlalchemy/migrate_repo/versions/002_add_image_properties_table.py
glance/db/sqlalchemy/migrate_repo/versions/003_add_disk_format.py
glance/db/sqlalchemy/migrate_repo/versions/003_sqlite_upgrade.sql
glance/db/sqlalchemy/migrate_repo/versions/004_add_checksum.py
glance/db/sqlalchemy/migrate_repo/versions/005_size_big_integer.py
glance/db/sqlalchemy/migrate_repo/versions/006_key_to_name.py
glance/db/sqlalchemy/migrate_repo/versions/006_mysql_upgrade.sql
glance/db/sqlalchemy/migrate_repo/versions/006_sqlite_upgrade.sql
glance/db/sqlalchemy/migrate_repo/versions/007_add_owner.py
glance/db/sqlalchemy/migrate_repo/versions/008_add_image_members_table.py
glance/db/sqlalchemy/migrate_repo/versions/009_add_mindisk_and_minram.py
glance/db/sqlalchemy/migrate_repo/versions/010_default_update_at.py
glance/db/sqlalchemy/migrate_repo/versions/011_make_mindisk_and_minram_notnull.py
glance/db/sqlalchemy/migrate_repo/versions/011_sqlite_upgrade.sql
glance/db/sqlalchemy/migrate_repo/versions/012_id_to_uuid.py
glance/db/sqlalchemy/migrate_repo/versions/013_add_protected.py
glance/db/sqlalchemy/migrate_repo/versions/014_add_image_tags_table.py
glance/db/sqlalchemy/migrate_repo/versions/015_quote_swift_credentials.py
glance/db/sqlalchemy/migrate_repo/versions/016_add_status_image_member.py
glance/db/sqlalchemy/migrate_repo/versions/017_quote_encrypted_swift_credentials.py
glance/db/sqlalchemy/migrate_repo/versions/018_add_image_locations_table.py
glance/db/sqlalchemy/migrate_repo/versions/019_migrate_image_locations.py
glance/db/sqlalchemy/migrate_repo/versions/020_drop_images_table_location.py
glance/db/sqlalchemy/migrate_repo/versions/021_set_engine_mysql_innodb.py
glance/db/sqlalchemy/migrate_repo/versions/022_image_member_index.py
glance/db/sqlalchemy/migrate_repo/versions/023_placeholder.py
glance/db/sqlalchemy/migrate_repo/versions/024_placeholder.py
glance/db/sqlalchemy/migrate_repo/versions/025_placeholder.py
glance/db/sqlalchemy/migrate_repo/versions/026_add_location_storage_information.py
glance/db/sqlalchemy/migrate_repo/versions/027_checksum_index.py
glance/db/sqlalchemy/migrate_repo/versions/028_owner_index.py
glance/db/sqlalchemy/migrate_repo/versions/029_location_meta_data_pickle_to_string.py
glance/db/sqlalchemy/migrate_repo/versions/030_add_tasks_table.py
glance/db/sqlalchemy/migrate_repo/versions/031_remove_duplicated_locations.py
glance/db/sqlalchemy/migrate_repo/versions/032_add_task_info_table.py
glance/db/sqlalchemy/migrate_repo/versions/033_add_location_status.py
glance/db/sqlalchemy/migrate_repo/versions/034_add_virtual_size.py
glance/db/sqlalchemy/migrate_repo/versions/035_add_metadef_tables.py
glance/db/sqlalchemy/migrate_repo/versions/036_rename_metadef_schema_columns.py
glance/db/sqlalchemy/migrate_repo/versions/037_add_changes_to_satisfy_models.py
glance/db/sqlalchemy/migrate_repo/versions/037_sqlite_upgrade.sql
glance/db/sqlalchemy/migrate_repo/versions/038_add_metadef_tags_table.py
glance/db/sqlalchemy/migrate_repo/versions/039_add_changes_to_satisfy_models_metadef.py
glance/db/sqlalchemy/migrate_repo/versions/040_add_changes_to_satisfy_metadefs_tags.py
glance/db/sqlalchemy/migrate_repo/versions/041_add_artifact_tables.py
glance/db/sqlalchemy/migrate_repo/versions/042_add_changes_to_reinstall_unique_metadef_constraints.py
glance/db/sqlalchemy/migrate_repo/versions/043_add_image_created_updated_idx.py
glance/db/sqlalchemy/migrate_repo/versions/044_update_metadef_os_nova_server.py
glance/db/sqlalchemy/migrate_repo/versions/045_add_visibility.py
glance/db/sqlalchemy/migrate_repo/versions/045_sqlite_upgrade.sql
glance/db/sqlalchemy/migrate_repo/versions/__init__.py
glance/domain/__init__.py
glance/domain/proxy.py
glance/hacking/__init__.py
glance/hacking/checks.py
glance/image_cache/__init__.py
glance/image_cache/base.py
glance/image_cache/cleaner.py
glance/image_cache/client.py
glance/image_cache/prefetcher.py
glance/image_cache/pruner.py
glance/image_cache/drivers/__init__.py
glance/image_cache/drivers/base.py
glance/image_cache/drivers/sqlite.py
glance/image_cache/drivers/xattr.py
glance/locale/de/LC_MESSAGES/glance.po
glance/locale/en_GB/LC_MESSAGES/glance.po
glance/locale/es/LC_MESSAGES/glance.po
glance/locale/fr/LC_MESSAGES/glance.po
glance/locale/it/LC_MESSAGES/glance.po
glance/locale/ja/LC_MESSAGES/glance.po
glance/locale/ko_KR/LC_MESSAGES/glance.po
glance/locale/pt_BR/LC_MESSAGES/glance.po
glance/locale/ru/LC_MESSAGES/glance.po
glance/locale/tr_TR/LC_MESSAGES/glance.po
glance/locale/zh_CN/LC_MESSAGES/glance.po
glance/locale/zh_TW/LC_MESSAGES/glance.po
glance/quota/__init__.py
glance/registry/__init__.py
glance/registry/api/__init__.py
glance/registry/api/v1/__init__.py
glance/registry/api/v1/images.py
glance/registry/api/v1/members.py
glance/registry/api/v2/__init__.py
glance/registry/api/v2/rpc.py
glance/registry/client/__init__.py
glance/registry/client/v1/__init__.py
glance/registry/client/v1/api.py
glance/registry/client/v1/client.py
glance/registry/client/v2/__init__.py
glance/registry/client/v2/api.py
glance/registry/client/v2/client.py
glance/tests/__init__.py
glance/tests/stubs.py
glance/tests/test_hacking.py
glance/tests/utils.py
glance/tests/etc/glance-swift.conf
glance/tests/etc/policy.json
glance/tests/etc/property-protections-policies.conf
glance/tests/etc/property-protections.conf
glance/tests/etc/schema-image.json
glance/tests/functional/__init__.py
glance/tests/functional/store_utils.py
glance/tests/functional/test_api.py
glance/tests/functional/test_bin_glance_cache_manage.py
glance/tests/functional/test_cache_middleware.py
glance/tests/functional/test_client_exceptions.py
glance/tests/functional/test_client_redirects.py
glance/tests/functional/test_cors_middleware.py
glance/tests/functional/test_glance_manage.py
glance/tests/functional/test_glance_replicator.py
glance/tests/functional/test_gzip_middleware.py
glance/tests/functional/test_healthcheck_middleware.py
glance/tests/functional/test_logging.py
glance/tests/functional/test_reload.py
glance/tests/functional/test_scrubber.py
glance/tests/functional/test_sqlite.py
glance/tests/functional/test_ssl.py
glance/tests/functional/test_wsgi.py
glance/tests/functional/db/__init__.py
glance/tests/functional/db/base.py
glance/tests/functional/db/base_metadef.py
glance/tests/functional/db/test_migrations.py
glance/tests/functional/db/test_registry.py
glance/tests/functional/db/test_rpc_endpoint.py
glance/tests/functional/db/test_simple.py
glance/tests/functional/db/test_sqlalchemy.py
glance/tests/functional/db/migrations/__init__.py
glance/tests/functional/db/migrations/test_mitaka01.py
glance/tests/functional/db/migrations/test_mitaka02.py
glance/tests/functional/db/migrations/test_ocata_contract01.py
glance/tests/functional/db/migrations/test_ocata_expand01.py
glance/tests/functional/db/migrations/test_ocata_migrate01.py
glance/tests/functional/db/migrations/test_pike_contract01.py
glance/tests/functional/db/migrations/test_pike_expand01.py
glance/tests/functional/db/migrations/test_pike_migrate01.py
glance/tests/functional/v1/__init__.py
glance/tests/functional/v1/test_api.py
glance/tests/functional/v1/test_copy_to_file.py
glance/tests/functional/v1/test_misc.py
glance/tests/functional/v1/test_multiprocessing.py
glance/tests/functional/v2/__init__.py
glance/tests/functional/v2/registry_data_api.py
glance/tests/functional/v2/test_images.py
glance/tests/functional/v2/test_metadef_namespaces.py
glance/tests/functional/v2/test_metadef_objects.py
glance/tests/functional/v2/test_metadef_properties.py
glance/tests/functional/v2/test_metadef_resourcetypes.py
glance/tests/functional/v2/test_metadef_tags.py
glance/tests/functional/v2/test_schemas.py
glance/tests/functional/v2/test_tasks.py
glance/tests/integration/__init__.py
glance/tests/integration/legacy_functional/__init__.py
glance/tests/integration/legacy_functional/base.py
glance/tests/integration/legacy_functional/test_v1_api.py
glance/tests/integration/v2/__init__.py
glance/tests/integration/v2/base.py
glance/tests/integration/v2/test_property_quota_violations.py
glance/tests/integration/v2/test_tasks_api.py
glance/tests/unit/__init__.py
glance/tests/unit/base.py
glance/tests/unit/fake_rados.py
glance/tests/unit/fixtures.py
glance/tests/unit/test_auth.py
glance/tests/unit/test_cache_middleware.py
glance/tests/unit/test_cached_images.py
glance/tests/unit/test_context.py
glance/tests/unit/test_context_middleware.py
glance/tests/unit/test_data_migration_framework.py
glance/tests/unit/test_db.py
glance/tests/unit/test_db_metadef.py
glance/tests/unit/test_domain.py
glance/tests/unit/test_domain_proxy.py
glance/tests/unit/test_glance_manage.py
glance/tests/unit/test_glance_replicator.py
glance/tests/unit/test_image_cache.py
glance/tests/unit/test_image_cache_client.py
glance/tests/unit/test_manage.py
glance/tests/unit/test_misc.py
glance/tests/unit/test_notifier.py
glance/tests/unit/test_policy.py
glance/tests/unit/test_quota.py
glance/tests/unit/test_schema.py
glance/tests/unit/test_scrubber.py
glance/tests/unit/test_store_image.py
glance/tests/unit/test_store_location.py
glance/tests/unit/test_versions.py
glance/tests/unit/utils.py
glance/tests/unit/api/__init__.py
glance/tests/unit/api/test_cmd.py
glance/tests/unit/api/test_cmd_cache_manage.py
glance/tests/unit/api/test_common.py
glance/tests/unit/api/test_property_protections.py
glance/tests/unit/api/middleware/__init__.py
glance/tests/unit/api/middleware/test_cache_manage.py
glance/tests/unit/async/__init__.py
glance/tests/unit/async/test_async.py
glance/tests/unit/async/test_taskflow_executor.py
glance/tests/unit/async/flows/__init__.py
glance/tests/unit/async/flows/test_api_image_import.py
glance/tests/unit/async/flows/test_convert.py
glance/tests/unit/async/flows/test_import.py
glance/tests/unit/async/flows/test_introspect.py
glance/tests/unit/async/flows/test_ovf_process.py
glance/tests/unit/async/flows/plugins/__init__.py
glance/tests/unit/async/flows/plugins/test_inject_image_metadata.py
glance/tests/unit/common/__init__.py
glance/tests/unit/common/test_client.py
glance/tests/unit/common/test_config.py
glance/tests/unit/common/test_exception.py
glance/tests/unit/common/test_location_strategy.py
glance/tests/unit/common/test_property_utils.py
glance/tests/unit/common/test_rpc.py
glance/tests/unit/common/test_scripts.py
glance/tests/unit/common/test_swift_store_utils.py
glance/tests/unit/common/test_timeutils.py
glance/tests/unit/common/test_utils.py
glance/tests/unit/common/test_wsgi.py
glance/tests/unit/common/scripts/__init__.py
glance/tests/unit/common/scripts/test_scripts_utils.py
glance/tests/unit/common/scripts/image_import/__init__.py
glance/tests/unit/common/scripts/image_import/test_main.py
glance/tests/unit/image_cache/__init__.py
glance/tests/unit/image_cache/drivers/__init__.py
glance/tests/unit/image_cache/drivers/test_sqlite.py
glance/tests/unit/v1/__init__.py
glance/tests/unit/v1/test_api.py
glance/tests/unit/v1/test_registry_api.py
glance/tests/unit/v1/test_registry_client.py
glance/tests/unit/v1/test_upload_utils.py
glance/tests/unit/v2/__init__.py
glance/tests/unit/v2/test_discovery_image_import.py
glance/tests/unit/v2/test_image_actions_resource.py
glance/tests/unit/v2/test_image_data_resource.py
glance/tests/unit/v2/test_image_members_resource.py
glance/tests/unit/v2/test_image_tags_resource.py
glance/tests/unit/v2/test_images_resource.py
glance/tests/unit/v2/test_metadef_resources.py
glance/tests/unit/v2/test_registry_api.py
glance/tests/unit/v2/test_registry_client.py
glance/tests/unit/v2/test_schemas_resource.py
glance/tests/unit/v2/test_tasks_resource.py
glance/tests/var/ca.crt
glance/tests/var/ca.key
glance/tests/var/certificate.crt
glance/tests/var/privatekey.key
glance/tests/var/testserver-bad-ovf.ova
glance/tests/var/testserver-no-disk.ova
glance/tests/var/testserver-no-ovf.ova
glance/tests/var/testserver-not-tar.ova
glance/tests/var/testserver.ova
httpd/README
httpd/glance-api-uwsgi.ini
httpd/uwsgi-glance-api.conf
rally-jobs/README.rst
rally-jobs/glance.yaml
rally-jobs/extra/README.rst
rally-jobs/extra/fake.img
rally-jobs/plugins/README.rst
releasenotes/notes/.placeholder
releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
releasenotes/notes/add-cpu-thread-pinning-metadata-09b1866b875c4647.yaml
releasenotes/notes/add-ploop-format-fdd583849504ab15.yaml
releasenotes/notes/add-processlimits-to-qemu-img-c215f5d90f741d8a.yaml
releasenotes/notes/add-vhdx-format-2be99354ad320cca.yaml
releasenotes/notes/alembic-migrations-902b31edae7a5d7d.yaml
releasenotes/notes/api-2-6-current-9eeb83b7ecc0a562.yaml
releasenotes/notes/api-minor-ver-bump-2-6-aa3591fc58f08055.yaml
releasenotes/notes/api-minor-version-bump-bbd69dc457fc731c.yaml
releasenotes/notes/bp-inject-image-metadata-0a08af539bcce7f2.yaml
releasenotes/notes/bug-1537903-54b2822eac6cfc09.yaml
releasenotes/notes/bug-1593177-8ef35458d29ec93c.yaml
releasenotes/notes/bug-1719252-name-validation-443a2e2a36be2cec.yaml
releasenotes/notes/bump-api-2-4-efa266aef0928e04.yaml
releasenotes/notes/clean-up-acceptable-values-store_type_preference-39081e4045894731.yaml
releasenotes/notes/consistent-store-names-57374b9505d530d0.yaml
releasenotes/notes/deprecate-glance-api-opts-23bdbd1ad7625999.yaml
releasenotes/notes/deprecate-registry-ff286df90df793f0.yaml
releasenotes/notes/deprecate-show-multiple-location-9890a1e961def2f6.yaml
releasenotes/notes/deprecate-v1-api-6c7dbefb90fd8772.yaml
releasenotes/notes/exp-emc-mig-fix-a7e28d547ac38f9e.yaml
releasenotes/notes/glare-ectomy-72a1f80f306f2e3b.yaml
releasenotes/notes/image-visibility-changes-fa5aa18dc67244c4.yaml
releasenotes/notes/implement-lite-spec-db-sync-check-3e2e147aec0ae82b.yaml
releasenotes/notes/improved-config-options-221c58a8c37602ba.yaml
releasenotes/notes/location-add-status-checks-b70db66100bc96b7.yaml
releasenotes/notes/lock_path_config_option-2771feaa649e4563.yaml
releasenotes/notes/make-task-api-admin-only-by-default-7def996262e18f7a.yaml
releasenotes/notes/new_image_filters-c888361e6ecf495c.yaml
releasenotes/notes/newton-1-release-065334d464f78fc5.yaml
releasenotes/notes/newton-bugs-06ed3727b973c271.yaml
releasenotes/notes/oslo-log-use-stderr-changes-07f5daf3e6abdcd6.yaml
releasenotes/notes/pike-metadefs-changes-95b54e0bf8bbefd6.yaml
releasenotes/notes/pike-rc-1-a5d3f6e8877b52c6.yaml
releasenotes/notes/pike-rc-2-acc173005045e16a.yaml
releasenotes/notes/queens-16.0.1-e6b03a36e4df16b2.yaml
releasenotes/notes/queens-metadefs-changes-daf02bef18d049f4.yaml
releasenotes/notes/queens-release-b6a9f9882c794c24.yaml
releasenotes/notes/queens-uwsgi-issues-4cee9e4fdf62c646.yaml
releasenotes/notes/range-header-request-83cf11eebf865fb1.yaml
releasenotes/notes/remove-db-downgrade-0d1cc45b97605775.yaml
releasenotes/notes/remove-osprofiler-paste-ini-options-c620dedc8f9728ff.yaml
releasenotes/notes/remove-s3-driver-639c60b71761eb6f.yaml
releasenotes/notes/reordered-store-config-opts-newton-3a6575b5908c0e0f.yaml
releasenotes/notes/restrict_location_updates-05454bb765a8c92c.yaml
releasenotes/notes/scrubber-exit-e5d77f6f1a38ffb7.yaml
releasenotes/notes/scrubber-refactor-73ddbd61ebbf1e86.yaml
releasenotes/notes/soft_delete-tasks-43ea983695faa565.yaml
releasenotes/notes/trust-support-registry-cfd17a6a9ab21d70.yaml
releasenotes/notes/update-show_multiple_locations-helptext-7fa692642b6b6d52.yaml
releasenotes/notes/use-cursive-c6b15d94845232da.yaml
releasenotes/notes/virtuozzo-hypervisor-fada477b64ae829d.yaml
releasenotes/notes/wsgi-containerization-369880238a5e793d.yaml
releasenotes/source/conf.py
releasenotes/source/index.rst
releasenotes/source/liberty.rst
releasenotes/source/mitaka.rst
releasenotes/source/newton.rst
releasenotes/source/ocata.rst
releasenotes/source/pike.rst
releasenotes/source/unreleased.rst
releasenotes/source/_static/.placeholder
releasenotes/source/_templates/.placeholder
tools/test-setup.sh glance-16.0.1/.mailmap 0000666 0001750 0001750 00000002267 13267672245 014522 0 ustar zuul zuul 0000000 0000000 # Format is:
#
#
Zhongyue Luo
Zhenguo Niu
David Koo
glance-16.0.1/setup.cfg 0000666 0001750 0001750 00000006414 13267672475 014725 0 ustar zuul zuul 0000000 0000000 [metadata]
name = glance
summary = OpenStack Image Service
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = https://docs.openstack.org/glance/latest/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
[files]
data_files =
etc/glance =
etc/glance-api.conf
etc/glance-cache.conf
etc/glance-manage.conf
etc/glance-registry.conf
etc/glance-scrubber.conf
etc/glance-api-paste.ini
etc/glance-registry-paste.ini
etc/policy.json
etc/rootwrap.conf
etc/glance/metadefs = etc/metadefs/*
packages =
glance
[entry_points]
console_scripts =
glance-api = glance.cmd.api:main
glance-cache-prefetcher = glance.cmd.cache_prefetcher:main
glance-cache-pruner = glance.cmd.cache_pruner:main
glance-cache-manage = glance.cmd.cache_manage:main
glance-cache-cleaner = glance.cmd.cache_cleaner:main
glance-control = glance.cmd.control:main
glance-manage = glance.cmd.manage:main
glance-registry = glance.cmd.registry:main
glance-replicator = glance.cmd.replicator:main
glance-scrubber = glance.cmd.scrubber:main
wsgi_scripts =
glance-wsgi-api = glance.common.wsgi_app:init_app
glance.common.image_location_strategy.modules =
location_order_strategy = glance.common.location_strategy.location_order
store_type_strategy = glance.common.location_strategy.store_type
oslo.config.opts =
glance.api = glance.opts:list_api_opts
glance.registry = glance.opts:list_registry_opts
glance.scrubber = glance.opts:list_scrubber_opts
glance.cache= glance.opts:list_cache_opts
glance.manage = glance.opts:list_manage_opts
glance = glance.opts:list_image_import_opts
oslo.config.opts.defaults =
glance.api = glance.common.config:set_cors_middleware_defaults
glance.database.migration_backend =
sqlalchemy = oslo_db.sqlalchemy.migration
glance.database.metadata_backend =
sqlalchemy = glance.db.sqlalchemy.metadata
glance.flows =
api_image_import = glance.async.flows.api_image_import:get_flow
import = glance.async.flows.base_import:get_flow
glance.flows.import =
convert = glance.async.flows.convert:get_flow
introspect = glance.async.flows.introspect:get_flow
ovf_process = glance.async.flows.ovf_process:get_flow
glance.image_import.plugins =
no_op = glance.async.flows.plugins.no_op:get_flow
inject_image_metadata=glance.async.flows.plugins.inject_image_metadata:get_flow
glance.image_import.internal_plugins =
web_download = glance.async.flows._internal_plugins.web_download:get_flow
[build_sphinx]
builder = html man
all_files = 1
build-dir = doc/build
source-dir = doc/source
warning-is-error = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[compile_catalog]
directory = glance/locale
domain = glance
[update_catalog]
domain = glance
output_dir = glance/locale
input_file = glance/locale/glance.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = glance/locale/glance.pot
[pbr]
autodoc_index_modules = True
autodoc_exclude_modules =
glance.tests.*
glance.db.sqlalchemy.*
api_doc_dir = contributor/api
glance-16.0.1/HACKING.rst 0000666 0001750 0001750 00000001725 13267672245 014675 0 ustar zuul zuul 0000000 0000000 glance Style Commandments
=========================
- Step 1: Read the OpenStack Style Commandments
http://docs.openstack.org/developer/hacking/
- Step 2: Read on
glance Specific Commandments
----------------------------
- [G316] Change assertTrue(isinstance(A, B)) by optimal assert like
assertIsInstance(A, B)
- [G317] Change assertEqual(type(A), B) by optimal assert like
assertIsInstance(A, B)
- [G318] Change assertEqual(A, None) or assertEqual(None, A) by optimal assert
like assertIsNone(A)
- [G319] Validate that debug level logs are not translated
- [G320] For python 3 compatibility, use six.text_type() instead of unicode()
- [G327] Prevent use of deprecated contextlib.nested
- [G328] Must use a dict comprehension instead of a dict constructor with
a sequence of key-value pairs
- [G329] Python 3: Do not use xrange.
- [G330] Python 3: do not use dict.iteritems.
- [G331] Python 3: do not use dict.iterkeys.
- [G332] Python 3: do not use dict.itervalues.
glance-16.0.1/bandit.yaml 0000666 0001750 0001750 00000025533 13267672245 015227 0 ustar zuul zuul 0000000 0000000 # optional: after how many files to update progress
#show_progress_every: 100
# optional: plugins directory name
#plugins_dir: 'plugins'
# optional: plugins discovery name pattern
plugin_name_pattern: '*.py'
# optional: terminal escape sequences to display colors
#output_colors:
# DEFAULT: '\033[0m'
# HEADER: '\033[95m'
# LOW: '\033[94m'
# MEDIUM: '\033[93m'
# HIGH: '\033[91m'
# optional: log format string
#log_format: "[%(module)s]\t%(levelname)s\t%(message)s"
# globs of files which should be analyzed
include:
- '*.py'
- '*.pyw'
# a list of strings, which if found in the path will cause files to be excluded
# for example /tests/ - to remove all all files in tests directory
exclude_dirs:
- '/tests/'
profiles:
gate:
include:
- any_other_function_with_shell_equals_true
- assert_used
- blacklist_calls
- blacklist_import_func
# One of the blacklisted imports is the subprocess module. Keystone
# has to import the subprocess module in a single module for
# eventlet support so in most cases bandit won't be able to detect
# that subprocess is even being imported. Also, Bandit's
# recommendation is just to check that the use is safe without any
# documentation on what safe or unsafe usage is. So this test is
# skipped.
# - blacklist_imports
- exec_used
- execute_with_run_as_root_equals_true
# - hardcoded_bind_all_interfaces # TODO: enable this test
# Not working because wordlist/default-passwords file not bundled,
# see https://bugs.launchpad.net/bandit/+bug/1451575 :
# - hardcoded_password
# Not used because it's prone to false positives:
# - hardcoded_sql_expressions
# - hardcoded_tmp_directory # TODO: enable this test
- jinja2_autoescape_false
- linux_commands_wildcard_injection
- paramiko_calls
- password_config_option_not_marked_secret
- request_with_no_cert_validation
- set_bad_file_permissions
- subprocess_popen_with_shell_equals_true
# - subprocess_without_shell_equals_true # TODO: enable this test
- start_process_with_a_shell
# - start_process_with_no_shell # TODO: enable this test
- start_process_with_partial_path
- ssl_with_bad_defaults
- ssl_with_bad_version
- ssl_with_no_version
# - try_except_pass # TODO: enable this test
- use_of_mako_templates
blacklist_calls:
bad_name_sets:
# - pickle:
# qualnames: [pickle.loads, pickle.load, pickle.Unpickler,
# cPickle.loads, cPickle.load, cPickle.Unpickler]
# message: "Pickle library appears to be in use, possible security issue."
# TODO: enable this test
- marshal:
qualnames: [marshal.load, marshal.loads]
message: "Deserialization with the marshal module is possibly dangerous."
# - md5:
# qualnames: [hashlib.md5, Crypto.Hash.MD2.new, Crypto.Hash.MD4.new, Crypto.Hash.MD5.new, cryptography.hazmat.primitives.hashes.MD5]
# message: "Use of insecure MD2, MD4, or MD5 hash function."
# TODO: enable this test
- mktemp_q:
qualnames: [tempfile.mktemp]
message: "Use of insecure and deprecated function (mktemp)."
- eval:
qualnames: [eval]
message: "Use of possibly insecure function - consider using safer ast.literal_eval."
- mark_safe:
names: [mark_safe]
message: "Use of mark_safe() may expose cross-site scripting vulnerabilities and should be reviewed."
- httpsconnection:
qualnames: [httplib.HTTPSConnection]
message: "Use of HTTPSConnection does not provide security, see https://wiki.openstack.org/wiki/OSSN/OSSN-0033"
- yaml_load:
qualnames: [yaml.load]
message: "Use of unsafe yaml load. Allows instantiation of arbitrary objects. Consider yaml.safe_load()."
- urllib_urlopen:
qualnames: [urllib.urlopen, urllib.urlretrieve, urllib.URLopener, urllib.FancyURLopener, urllib2.urlopen, urllib2.Request]
message: "Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected."
- random:
qualnames: [random.random, random.randrange, random.randint, random.choice, random.uniform, random.triangular]
message: "Standard pseudo-random generators are not suitable for security/cryptographic purposes."
level: "LOW"
# Most of this is based off of Christian Heimes' work on defusedxml:
# https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
# TODO(jaegerandi): Enable once defusedxml is in global requirements.
#- xml_bad_cElementTree:
# qualnames: [xml.etree.cElementTree.parse,
# xml.etree.cElementTree.iterparse,
# xml.etree.cElementTree.fromstring,
# xml.etree.cElementTree.XMLParser]
# message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
#- xml_bad_ElementTree:
# qualnames: [xml.etree.ElementTree.parse,
# xml.etree.ElementTree.iterparse,
# xml.etree.ElementTree.fromstring,
# xml.etree.ElementTree.XMLParser]
# message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
- xml_bad_expatreader:
qualnames: [xml.sax.expatreader.create_parser]
message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
- xml_bad_expatbuilder:
qualnames: [xml.dom.expatbuilder.parse,
xml.dom.expatbuilder.parseString]
message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
- xml_bad_sax:
qualnames: [xml.sax.parse,
xml.sax.parseString,
xml.sax.make_parser]
message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
- xml_bad_minidom:
qualnames: [xml.dom.minidom.parse,
xml.dom.minidom.parseString]
message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
- xml_bad_pulldom:
qualnames: [xml.dom.pulldom.parse,
xml.dom.pulldom.parseString]
message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
- xml_bad_etree:
qualnames: [lxml.etree.parse,
lxml.etree.fromstring,
lxml.etree.RestrictedElement,
lxml.etree.GlobalParserTLS,
lxml.etree.getDefaultParser,
lxml.etree.check_docinfo]
message: "Using {func} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {func} with it's defusedxml equivilent function."
shell_injection:
# Start a process using the subprocess module, or one of its wrappers.
subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call,
subprocess.check_output, utils.execute, utils.execute_with_timeout]
# Start a process with a function vulnerable to shell injection.
shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4,
popen2.popen2, popen2.popen3, popen2.popen4, popen2.Popen3,
popen2.Popen4, commands.getoutput, commands.getstatusoutput]
# Start a process with a function that is not vulnerable to shell injection.
no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv,os.execve,
os.execvp, os.execvpe, os.spawnl, os.spawnle, os.spawnlp,
os.spawnlpe, os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe,
os.startfile]
blacklist_imports:
bad_import_sets:
- telnet:
imports: [telnetlib]
level: HIGH
message: "Telnet is considered insecure. Use SSH or some other encrypted protocol."
- info_libs:
imports: [pickle, cPickle, subprocess, Crypto]
level: LOW
message: "Consider possible security implications associated with {module} module."
# Most of this is based off of Christian Heimes' work on defusedxml:
# https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
- xml_libs:
imports: [xml.etree.cElementTree,
xml.etree.ElementTree,
xml.sax.expatreader,
xml.sax,
xml.dom.expatbuilder,
xml.dom.minidom,
xml.dom.pulldom,
lxml.etree,
lxml]
message: "Using {module} to parse untrusted XML data is known to be vulnerable to XML attacks. Replace {module} with the equivilent defusedxml package."
level: LOW
- xml_libs_high:
imports: [xmlrpclib]
message: "Using {module} to parse untrusted XML data is known to be vulnerable to XML attacks. Use defused.xmlrpc.monkey_patch() function to monkey-patch xmlrpclib and mitigate XML vulnerabilities."
level: HIGH
hardcoded_tmp_directory:
tmp_dirs: ['/tmp', '/var/tmp', '/dev/shm']
hardcoded_password:
# Support for full path, relative path and special "%(site_data_dir)s"
# substitution (/usr/{local}/share)
word_list: "%(site_data_dir)s/wordlist/default-passwords"
ssl_with_bad_version:
bad_protocol_versions:
- 'PROTOCOL_SSLv2'
- 'SSLv2_METHOD'
- 'SSLv23_METHOD'
- 'PROTOCOL_SSLv3' # strict option
- 'PROTOCOL_TLSv1' # strict option
- 'SSLv3_METHOD' # strict option
- 'TLSv1_METHOD' # strict option
password_config_option_not_marked_secret:
function_names:
- oslo.config.cfg.StrOpt
- oslo_config.cfg.StrOpt
execute_with_run_as_root_equals_true:
function_names:
- ceilometer.utils.execute
- cinder.utils.execute
- neutron.agent.linux.utils.execute
- nova.utils.execute
- nova.utils.trycmd
try_except_pass:
check_typed_exception: True
glance-16.0.1/glance/ 0000775 0001750 0001750 00000000000 13267672475 014326 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/ 0000775 0001750 0001750 00000000000 13267672475 015470 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/test_hacking.py 0000666 0001750 0001750 00000013376 13267672245 020514 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.hacking import checks
from glance.tests import utils
class HackingTestCase(utils.BaseTestCase):
def test_assert_true_instance(self):
self.assertEqual(1, len(list(checks.assert_true_instance(
"self.assertTrue(isinstance(e, "
"exception.BuildAbortException))"))))
self.assertEqual(
0, len(list(checks.assert_true_instance("self.assertTrue()"))))
def test_assert_equal_type(self):
self.assertEqual(1, len(list(checks.assert_equal_type(
"self.assertEqual(type(als['QuicAssist']), list)"))))
self.assertEqual(
0, len(list(checks.assert_equal_type("self.assertTrue()"))))
def test_assert_equal_none(self):
self.assertEqual(1, len(list(checks.assert_equal_none(
"self.assertEqual(A, None)"))))
self.assertEqual(1, len(list(checks.assert_equal_none(
"self.assertEqual(None, A)"))))
self.assertEqual(
0, len(list(checks.assert_equal_none("self.assertIsNone()"))))
def test_no_translate_debug_logs(self):
self.assertEqual(1, len(list(checks.no_translate_debug_logs(
"LOG.debug(_('foo'))", "glance/store/foo.py"))))
self.assertEqual(0, len(list(checks.no_translate_debug_logs(
"LOG.debug('foo')", "glance/store/foo.py"))))
self.assertEqual(0, len(list(checks.no_translate_debug_logs(
"LOG.info(_('foo'))", "glance/store/foo.py"))))
def test_no_direct_use_of_unicode_function(self):
self.assertEqual(1, len(list(checks.no_direct_use_of_unicode_function(
"unicode('the party dont start til the unicode walks in')"))))
self.assertEqual(1, len(list(checks.no_direct_use_of_unicode_function(
"""unicode('something '
'something else"""))))
self.assertEqual(0, len(list(checks.no_direct_use_of_unicode_function(
"six.text_type('party over')"))))
self.assertEqual(0, len(list(checks.no_direct_use_of_unicode_function(
"not_actually_unicode('something completely different')"))))
def test_no_contextlib_nested(self):
self.assertEqual(1, len(list(checks.check_no_contextlib_nested(
"with contextlib.nested("))))
self.assertEqual(1, len(list(checks.check_no_contextlib_nested(
"with nested("))))
self.assertEqual(0, len(list(checks.check_no_contextlib_nested(
"with foo as bar"))))
def test_dict_constructor_with_list_copy(self):
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
" dict([(i, connect_info[i])"))))
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
" attrs = dict([(k, _from_json(v))"))))
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
" type_names = dict((value, key) for key, value in"))))
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
" dict((value, key) for key, value in"))))
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
"foo(param=dict((k, v) for k, v in bar.items()))"))))
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
" dict([[i,i] for i in range(3)])"))))
self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
" dd = dict([i,i] for i in range(3))"))))
self.assertEqual(0, len(list(checks.dict_constructor_with_list_copy(
" create_kwargs = dict(snapshot=snapshot,"))))
self.assertEqual(0, len(list(checks.dict_constructor_with_list_copy(
" self._render_dict(xml, data_el, data.__dict__)"))))
def test_check_python3_xrange(self):
func = checks.check_python3_xrange
self.assertEqual(1, len(list(func('for i in xrange(10)'))))
self.assertEqual(1, len(list(func('for i in xrange (10)'))))
self.assertEqual(0, len(list(func('for i in range(10)'))))
self.assertEqual(0, len(list(func('for i in six.moves.range(10)'))))
self.assertEqual(0, len(list(func('testxrange(10)'))))
def test_dict_iteritems(self):
self.assertEqual(1, len(list(checks.check_python3_no_iteritems(
"obj.iteritems()"))))
self.assertEqual(0, len(list(checks.check_python3_no_iteritems(
"six.iteritems(obj)"))))
self.assertEqual(0, len(list(checks.check_python3_no_iteritems(
"obj.items()"))))
def test_dict_iterkeys(self):
self.assertEqual(1, len(list(checks.check_python3_no_iterkeys(
"obj.iterkeys()"))))
self.assertEqual(0, len(list(checks.check_python3_no_iterkeys(
"six.iterkeys(obj)"))))
self.assertEqual(0, len(list(checks.check_python3_no_iterkeys(
"obj.keys()"))))
def test_dict_itervalues(self):
self.assertEqual(1, len(list(checks.check_python3_no_itervalues(
"obj.itervalues()"))))
self.assertEqual(0, len(list(checks.check_python3_no_itervalues(
"six.itervalues(ob)"))))
self.assertEqual(0, len(list(checks.check_python3_no_itervalues(
"obj.values()"))))
glance-16.0.1/glance/tests/__init__.py 0000666 0001750 0001750 00000002770 13267672254 017604 0 ustar zuul zuul 0000000 0000000 # Copyright 2010-2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables tests to work with i18n _() blocks
import six.moves.builtins as __builtin__
setattr(__builtin__, '_', lambda x: x)
# Set up logging to output debugging
import logging
logger = logging.getLogger()
hdlr = logging.FileHandler('run_tests.log', 'w')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
import eventlet
# NOTE(jokke): As per the eventlet commit
# b756447bab51046dfc6f1e0e299cc997ab343701 there's circular import happening
# which can be solved making sure the hubs are properly and fully imported
# before calling monkey_patch(). This is solved in eventlet 0.22.0 but we
# need to address it before that is widely used around.
eventlet.hubs.get_hub()
eventlet.patcher.monkey_patch()
glance-16.0.1/glance/tests/etc/ 0000775 0001750 0001750 00000000000 13267672475 016243 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/etc/property-protections.conf 0000666 0001750 0001750 00000002673 13267672245 023352 0 ustar zuul zuul 0000000 0000000 [^x_owner_.*]
create = admin,member
read = admin,member
update = admin,member
delete = admin,member
[spl_create_prop]
create = admin,spl_role
read = admin,spl_role
update = admin
delete = admin
[spl_read_prop]
create = admin,spl_role
read = admin,spl_role
update = admin
delete = admin
[spl_read_only_prop]
create = admin
read = admin,spl_role
update = admin
delete = admin
[spl_update_prop]
create = admin,spl_role
read = admin,spl_role
update = admin,spl_role
delete = admin
[spl_update_only_prop]
create = admin
read = admin
update = admin,spl_role
delete = admin
[spl_delete_prop]
create = admin,spl_role
read = admin,spl_role
update = admin
delete = admin,spl_role
[spl_delete_empty_prop]
create = admin,spl_role
read = admin,spl_role
update = admin
delete = admin,spl_role
[^x_all_permitted.*]
create = @
read = @
update = @
delete = @
[^x_none_permitted.*]
create = !
read = !
update = !
delete = !
[x_none_read]
create = admin,member
read = !
update = !
delete = !
[x_none_update]
create = admin,member
read = admin,member
update = !
delete = admin,member
[x_none_delete]
create = admin,member
read = admin,member
update = admin,member
delete = !
[x_case_insensitive]
create = admin,Member
read = admin,Member
update = admin,Member
delete = admin,Member
[x_foo_matcher]
create = admin
read = admin
update = admin
delete = admin
[x_foo_*]
create = @
read = @
update = @
delete = @
[.*]
create = admin
read = admin
update = admin
delete = admin
glance-16.0.1/glance/tests/etc/glance-swift.conf 0000666 0001750 0001750 00000001004 13267672245 021465 0 ustar zuul zuul 0000000 0000000 [ref1]
user = tenant:user1
key = key1
auth_address = example.com
[ref2]
user = user2
key = key2
auth_address = http://example.com
[store_2]
user = tenant:user1
key = key1
auth_address= https://localhost:8080
[store_3]
user= tenant:user2
key= key2
auth_address= https://localhost:8080
[store_4]
user = tenant:user1
key = key1
auth_address = http://localhost:80
[store_5]
user = tenant:user1
key = key1
auth_address = http://localhost
[store_6]
user = tenant:user1
key = key1
auth_address = https://localhost/v1
glance-16.0.1/glance/tests/etc/schema-image.json 0000666 0001750 0001750 00000000003 13267672245 021444 0 ustar zuul zuul 0000000 0000000 {}
glance-16.0.1/glance/tests/etc/policy.json 0000666 0001750 0001750 00000002604 13267672245 020434 0 ustar zuul zuul 0000000 0000000 {
"context_is_admin": "role:admin",
"default": "",
"glance_creator": "role:admin or role:spl_role",
"add_image": "",
"delete_image": "",
"get_image": "",
"get_images": "",
"modify_image": "",
"publicize_image": "",
"communitize_image": "",
"copy_from": "",
"download_image": "",
"upload_image": "",
"delete_image_location": "",
"get_image_location": "",
"set_image_location": "",
"add_member": "",
"delete_member": "",
"get_member": "",
"get_members": "",
"modify_member": "",
"manage_image_cache": "",
"get_task": "role:admin",
"get_tasks": "role:admin",
"add_task": "role:admin",
"modify_task": "role:admin",
"get_metadef_namespace": "",
"get_metadef_namespaces":"",
"modify_metadef_namespace":"",
"add_metadef_namespace":"",
"get_metadef_object":"",
"get_metadef_objects":"",
"modify_metadef_object":"",
"add_metadef_object":"",
"list_metadef_resource_types":"",
"get_metadef_resource_type":"",
"add_metadef_resource_type_association":"",
"get_metadef_property":"",
"get_metadef_properties":"",
"modify_metadef_property":"",
"add_metadef_property":"",
"get_metadef_tag":"",
"get_metadef_tags":"",
"modify_metadef_tag":"",
"add_metadef_tag":"",
"add_metadef_tags":"",
"deactivate": "",
"reactivate": ""
}
glance-16.0.1/glance/tests/etc/property-protections-policies.conf 0000666 0001750 0001750 00000001633 13267672245 025152 0 ustar zuul zuul 0000000 0000000 [spl_creator_policy]
create = glance_creator
read = glance_creator
update = context_is_admin
delete = context_is_admin
[spl_default_policy]
create = context_is_admin
read = default
update = context_is_admin
delete = context_is_admin
[^x_all_permitted.*]
create = @
read = @
update = @
delete = @
[^x_none_permitted.*]
create = !
read = !
update = !
delete = !
[x_none_read]
create = context_is_admin
read = !
update = !
delete = !
[x_none_update]
create = context_is_admin
read = context_is_admin
update = !
delete = context_is_admin
[x_none_delete]
create = context_is_admin
read = context_is_admin
update = context_is_admin
delete = !
[x_foo_matcher]
create = context_is_admin
read = context_is_admin
update = context_is_admin
delete = context_is_admin
[x_foo_*]
create = @
read = @
update = @
delete = @
[.*]
create = context_is_admin
read = context_is_admin
update = context_is_admin
delete = context_is_admin
glance-16.0.1/glance/tests/functional/ 0000775 0001750 0001750 00000000000 13267672475 017632 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/test_cache_middleware.py 0000666 0001750 0001750 00000130777 13267672245 024517 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests a Glance API server which uses the caching middleware that
uses the default SQLite cache driver. We use the filesystem store,
but that is really not relevant, as the image cache is transparent
to the backend store.
"""
import hashlib
import os
import shutil
import sys
import time
import uuid
import httplib2
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests import functional
from glance.tests.functional.store_utils import get_http_uri
from glance.tests.functional.store_utils import setup_http
from glance.tests.utils import execute
from glance.tests.utils import minimal_headers
from glance.tests.utils import skip_if_disabled
from glance.tests.utils import xattr_writes_supported
FIVE_KB = 5 * units.Ki
class BaseCacheMiddlewareTest(object):
@skip_if_disabled
def test_cache_middleware_transparent_v1(self):
"""
We test that putting the cache middleware into the
application pipeline gives us transparent image caching
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
image_id = data['image']['id']
# Verify image not in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
# Grab the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Verify image now in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
# You might wonder why the heck this is here... well, it's here
# because it took me forever to figure out that the disk write
# cache in Linux was causing random failures of the os.path.exists
# assert directly below this. Basically, since the cache is writing
# the image file to disk in a different process, the write buffers
# don't flush the cache file during an os.rename() properly, resulting
# in a false negative on the file existence check below. This little
# loop pauses the execution of this process for no more than 1.5
# seconds. If after that time the cached image file still doesn't
# appear on disk, something really is wrong, and the assert should
# trigger...
i = 0
while not os.path.exists(image_cached_path) and i < 30:
time.sleep(0.05)
i = i + 1
self.assertTrue(os.path.exists(image_cached_path))
# Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
self.assertFalse(os.path.exists(image_cached_path))
self.stop_servers()
@skip_if_disabled
def test_cache_middleware_transparent_v2(self):
"""Ensure the v2 API image transfer calls trigger caching"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http()
headers = {'content-type': 'application/json'}
image_entity = {
'name': 'Image1',
'visibility': 'public',
'container_format': 'bare',
'disk_format': 'raw',
}
response, content = http.request(path, 'POST',
headers=headers,
body=jsonutils.dumps(image_entity))
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
image_id)
headers = {'content-type': 'application/octet-stream'}
image_data = "*" * FIVE_KB
response, content = http.request(path, 'PUT',
headers=headers,
body=image_data)
self.assertEqual(http_client.NO_CONTENT, response.status)
# Verify image not in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
# Grab the image
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Verify image now in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertTrue(os.path.exists(image_cached_path))
# Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image
path = "http://%s:%d/v2/images/%s" % ("0.0.0.0", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.NO_CONTENT, response.status)
self.assertFalse(os.path.exists(image_cached_path))
self.stop_servers()
@skip_if_disabled
def test_partially_downloaded_images_are_not_cached_v2_api(self):
"""
Verify that we do not cache images that were downloaded partially
using v2 images API.
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http()
headers = {'content-type': 'application/json'}
image_entity = {
'name': 'Image1',
'visibility': 'public',
'container_format': 'bare',
'disk_format': 'raw',
}
response, content = http.request(path, 'POST',
headers=headers,
body=jsonutils.dumps(image_entity))
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
image_id)
headers = {'content-type': 'application/octet-stream'}
image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
response, content = http.request(path, 'PUT',
headers=headers,
body=image_data)
self.assertEqual(http_client.NO_CONTENT, response.status)
# Verify that this image is not in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
# partially download this image and verify status 206
http = httplib2.Http()
# range download request
range_ = 'bytes=3-5'
headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': str(uuid.uuid4()),
'X-Roles': 'member',
'Range': range_
}
response, content = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.PARTIAL_CONTENT, response.status)
self.assertEqual(b'DEF', content)
# content-range download request
# NOTE(dharinic): Glance incorrectly supports Content-Range for partial
# image downloads in requests. This test is included to ensure that
# we prevent regression.
content_range = 'bytes 3-5/*'
headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': str(uuid.uuid4()),
'X-Roles': 'member',
'Content-Range': content_range
}
response, content = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.PARTIAL_CONTENT, response.status)
self.assertEqual(b'DEF', content)
# verify that we do not cache the partial image
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
self.stop_servers()
@skip_if_disabled
def test_partial_download_of_cached_images_v2_api(self):
"""
Verify that partial download requests for a fully cached image
succeeds; we do not serve it from cache.
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http()
headers = {'content-type': 'application/json'}
image_entity = {
'name': 'Image1',
'visibility': 'public',
'container_format': 'bare',
'disk_format': 'raw',
}
response, content = http.request(path, 'POST',
headers=headers,
body=jsonutils.dumps(image_entity))
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
image_id)
headers = {'content-type': 'application/octet-stream'}
image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
response, content = http.request(path, 'PUT',
headers=headers,
body=image_data)
self.assertEqual(http_client.NO_CONTENT, response.status)
# Verify that this image is not in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
# Download the entire image
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ', content)
# Verify that the image is now in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertTrue(os.path.exists(image_cached_path))
# Modify the data in cache so we can verify the partially downloaded
# content was not from cache indeed.
with open(image_cached_path, 'w') as cache_file:
cache_file.write('0123456789')
# Partially attempt a download of this image and verify that is not
# from cache
# range download request
range_ = 'bytes=3-5'
headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': str(uuid.uuid4()),
'X-Roles': 'member',
'Range': range_,
'content-type': 'application/json'
}
response, content = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.PARTIAL_CONTENT, response.status)
self.assertEqual(b'DEF', content)
self.assertNotEqual(b'345', content)
self.assertNotEqual(image_data, content)
# content-range download request
# NOTE(dharinic): Glance incorrectly supports Content-Range for partial
# image downloads in requests. This test is included to ensure that
# we prevent regression.
content_range = 'bytes 3-5/*'
headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': str(uuid.uuid4()),
'X-Roles': 'member',
'Content-Range': content_range,
'content-type': 'application/json'
}
response, content = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.PARTIAL_CONTENT, response.status)
self.assertEqual(b'DEF', content)
self.assertNotEqual(b'345', content)
self.assertNotEqual(image_data, content)
self.stop_servers()
@skip_if_disabled
def test_cache_remote_image(self):
"""
We test that caching is no longer broken for remote images
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
setup_http(self)
# Add a remote image and verify a 201 Created is returned
remote_uri = get_http_uri(self, '2')
headers = {'X-Image-Meta-Name': 'Image2',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Location': remote_uri}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(FIVE_KB, data['image']['size'])
image_id = data['image']['id']
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
# Grab the image
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Grab the image again to ensure it can be served out from
# cache with the correct size
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(FIVE_KB, int(response['content-length']))
self.stop_servers()
@skip_if_disabled
def test_cache_middleware_trans_v1_without_download_image_policy(self):
"""
Ensure the image v1 API image transfer applied 'download_image'
policy enforcement.
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
image_id = data['image']['id']
# Verify image not in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
rules = {"context_is_admin": "role:admin", "default": "",
"download_image": "!"}
self.set_policy_rules(rules)
# Grab the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.FORBIDDEN, response.status)
# Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
self.assertFalse(os.path.exists(image_cached_path))
self.stop_servers()
@skip_if_disabled
def test_cache_middleware_trans_v2_without_download_image_policy(self):
"""
Ensure the image v2 API image transfer applied 'download_image'
policy enforcement.
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port)
http = httplib2.Http()
headers = {'content-type': 'application/json'}
image_entity = {
'name': 'Image1',
'visibility': 'public',
'container_format': 'bare',
'disk_format': 'raw',
}
response, content = http.request(path, 'POST',
headers=headers,
body=jsonutils.dumps(image_entity))
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port,
image_id)
headers = {'content-type': 'application/octet-stream'}
image_data = "*" * FIVE_KB
response, content = http.request(path, 'PUT',
headers=headers,
body=image_data)
self.assertEqual(http_client.NO_CONTENT, response.status)
# Verify image not in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertFalse(os.path.exists(image_cached_path))
rules = {"context_is_admin": "role:admin", "default": "",
"download_image": "!"}
self.set_policy_rules(rules)
# Grab the image
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.FORBIDDEN, response.status)
# Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image
path = "http://%s:%d/v2/images/%s" % ("0.0.0.0", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.NO_CONTENT, response.status)
self.assertFalse(os.path.exists(image_cached_path))
self.stop_servers()
@skip_if_disabled
def test_cache_middleware_trans_with_deactivated_image(self):
"""
Ensure the image v1/v2 API image transfer forbids downloading
deactivated images.
Image deactivation is not available in v1. So, we'll deactivate the
image using v2 but test image transfer with both v1 and v2.
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Add an image and verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
image_id = data['image']['id']
# Grab the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Verify image in cache
image_cached_path = os.path.join(self.api_server.image_cache_dir,
image_id)
self.assertTrue(os.path.exists(image_cached_path))
# Deactivate the image using v2
path = "http://%s:%d/v2/images/%s/actions/deactivate"
path = path % ("127.0.0.1", self.api_port, image_id)
http = httplib2.Http()
response, content = http.request(path, 'POST')
self.assertEqual(http_client.NO_CONTENT, response.status)
# Download the image with v1. Ensure it is forbidden
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.FORBIDDEN, response.status)
# Download the image with v2. This succeeds because
# we are in admin context.
path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Reactivate the image using v2
path = "http://%s:%d/v2/images/%s/actions/reactivate"
path = path % ("127.0.0.1", self.api_port, image_id)
http = httplib2.Http()
response, content = http.request(path, 'POST')
self.assertEqual(http_client.NO_CONTENT, response.status)
# Download the image with v1. Ensure it is allowed
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Download the image with v2. Ensure it is allowed
path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
self.assertFalse(os.path.exists(image_cached_path))
self.stop_servers()
class BaseCacheManageMiddlewareTest(object):
"""Base test class for testing cache management middleware"""
def verify_no_images(self):
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('images', data)
self.assertEqual(0, len(data['images']))
def add_image(self, name):
"""
Adds an image and returns the newly-added image
identifier
"""
image_data = b"*" * FIVE_KB
headers = minimal_headers('%s' % name)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual(name, data['image']['name'])
self.assertTrue(data['image']['is_public'])
return data['image']['id']
def verify_no_cached_images(self):
"""
Verify no images in the image cache
"""
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
self.assertEqual([], data['cached_images'])
@skip_if_disabled
def test_user_not_authorized(self):
self.cleanup()
self.start_servers(**self.__dict__.copy())
self.verify_no_images()
image_id1 = self.add_image("Image1")
image_id2 = self.add_image("Image2")
# Verify image does not yet show up in cache (we haven't "hit"
# it yet using a GET /images/1 ...
self.verify_no_cached_images()
# Grab the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id1)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Verify image now in cache
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
cached_images = data['cached_images']
self.assertEqual(1, len(cached_images))
self.assertEqual(image_id1, cached_images[0]['image_id'])
# Set policy to disallow access to cache management
rules = {"manage_image_cache": '!'}
self.set_policy_rules(rules)
# Verify an unprivileged user cannot see cached images
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.FORBIDDEN, response.status)
# Verify an unprivileged user cannot delete images from the cache
path = "http://%s:%d/v1/cached_images/%s" % ("127.0.0.1",
self.api_port, image_id1)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.FORBIDDEN, response.status)
# Verify an unprivileged user cannot delete all cached images
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.FORBIDDEN, response.status)
# Verify an unprivileged user cannot queue an image
path = "http://%s:%d/v1/queued_images/%s" % ("127.0.0.1",
self.api_port, image_id2)
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.FORBIDDEN, response.status)
self.stop_servers()
@skip_if_disabled
def test_cache_manage_get_cached_images(self):
"""
Tests that cached images are queryable
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
self.verify_no_images()
image_id = self.add_image("Image1")
# Verify image does not yet show up in cache (we haven't "hit"
# it yet using a GET /images/1 ...
self.verify_no_cached_images()
# Grab the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Verify image now in cache
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
# Verify the last_modified/last_accessed values are valid floats
for cached_image in data['cached_images']:
for time_key in ('last_modified', 'last_accessed'):
time_val = cached_image[time_key]
try:
float(time_val)
except ValueError:
self.fail('%s time %s for cached image %s not a valid '
'float' % (time_key, time_val,
cached_image['image_id']))
cached_images = data['cached_images']
self.assertEqual(1, len(cached_images))
self.assertEqual(image_id, cached_images[0]['image_id'])
self.assertEqual(0, cached_images[0]['hits'])
# Hit the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
# Verify image hits increased in output of manage GET
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
cached_images = data['cached_images']
self.assertEqual(1, len(cached_images))
self.assertEqual(image_id, cached_images[0]['image_id'])
self.assertEqual(1, cached_images[0]['hits'])
self.stop_servers()
@skip_if_disabled
def test_cache_manage_delete_cached_images(self):
"""
Tests that cached images may be deleted
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
self.verify_no_images()
ids = {}
# Add a bunch of images...
for x in range(4):
ids[x] = self.add_image("Image%s" % str(x))
# Verify no images in cached_images because no image has been hit
# yet using a GET /images/ ...
self.verify_no_cached_images()
# Grab the images, essentially caching them...
for x in range(4):
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
ids[x])
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status,
"Failed to find image %s" % ids[x])
# Verify images now in cache
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
cached_images = data['cached_images']
self.assertEqual(4, len(cached_images))
for x in range(4, 0): # Cached images returned last modified order
self.assertEqual(ids[x], cached_images[x]['image_id'])
self.assertEqual(0, cached_images[x]['hits'])
# Delete third image of the cached images and verify no longer in cache
path = "http://%s:%d/v1/cached_images/%s" % ("127.0.0.1",
self.api_port, ids[2])
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
cached_images = data['cached_images']
self.assertEqual(3, len(cached_images))
self.assertNotIn(ids[2], [x['image_id'] for x in cached_images])
# Delete all cached images and verify nothing in cache
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
cached_images = data['cached_images']
self.assertEqual(0, len(cached_images))
self.stop_servers()
@skip_if_disabled
def test_cache_manage_delete_queued_images(self):
"""
Tests that all queued images may be deleted at once
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
self.verify_no_images()
ids = {}
NUM_IMAGES = 4
# Add and then queue some images
for x in range(NUM_IMAGES):
ids[x] = self.add_image("Image%s" % str(x))
path = "http://%s:%d/v1/queued_images/%s" % ("127.0.0.1",
self.api_port, ids[x])
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.OK, response.status)
# Delete all queued images
path = "http://%s:%d/v1/queued_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
num_deleted = data['num_deleted']
self.assertEqual(NUM_IMAGES, num_deleted)
# Verify a second delete now returns num_deleted=0
path = "http://%s:%d/v1/queued_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
num_deleted = data['num_deleted']
self.assertEqual(0, num_deleted)
self.stop_servers()
@skip_if_disabled
def test_queue_and_prefetch(self):
"""
Tests that images may be queued and prefetched
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
cache_config_filepath = os.path.join(self.test_dir, 'etc',
'glance-cache.conf')
cache_file_options = {
'image_cache_dir': self.api_server.image_cache_dir,
'image_cache_driver': self.image_cache_driver,
'registry_port': self.registry_server.bind_port,
'log_file': os.path.join(self.test_dir, 'cache.log'),
'lock_path': self.test_dir,
'metadata_encryption_key': "012345678901234567890123456789ab",
'filesystem_store_datadir': self.test_dir
}
with open(cache_config_filepath, 'w') as cache_file:
cache_file.write("""[DEFAULT]
debug = True
lock_path = %(lock_path)s
image_cache_dir = %(image_cache_dir)s
image_cache_driver = %(image_cache_driver)s
registry_host = 127.0.0.1
registry_port = %(registry_port)s
metadata_encryption_key = %(metadata_encryption_key)s
log_file = %(log_file)s
[glance_store]
filesystem_store_datadir=%(filesystem_store_datadir)s
""" % cache_file_options)
self.verify_no_images()
ids = {}
# Add a bunch of images...
for x in range(4):
ids[x] = self.add_image("Image%s" % str(x))
# Queue the first image, verify no images still in cache after queueing
# then run the prefetcher and verify that the image is then in the
# cache
path = "http://%s:%d/v1/queued_images/%s" % ("127.0.0.1",
self.api_port, ids[0])
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.OK, response.status)
self.verify_no_cached_images()
cmd = ("%s -m glance.cmd.cache_prefetcher --config-file %s" %
(sys.executable, cache_config_filepath))
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertEqual(b'', out.strip(), out)
# Verify first image now in cache
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertIn('cached_images', data)
cached_images = data['cached_images']
self.assertEqual(1, len(cached_images))
self.assertIn(ids[0], [r['image_id']
for r in data['cached_images']])
self.stop_servers()
class TestImageCacheXattr(functional.FunctionalTest,
BaseCacheMiddlewareTest):
"""Functional tests that exercise the image cache using the xattr driver"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-xattr installed and xattr support on the
filesystem)
"""
if getattr(self, 'disabled', False):
return
if not getattr(self, 'inited', False):
try:
import xattr # noqa
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-xattr not installed.")
return
self.inited = True
self.disabled = False
self.image_cache_driver = "xattr"
super(TestImageCacheXattr, self).setUp()
self.api_server.deployment_flavor = "caching"
if not xattr_writes_supported(self.test_dir):
self.inited = True
self.disabled = True
self.disabled_message = ("filesystem does not support xattr")
return
def tearDown(self):
super(TestImageCacheXattr, self).tearDown()
if os.path.exists(self.api_server.image_cache_dir):
shutil.rmtree(self.api_server.image_cache_dir)
class TestImageCacheManageXattr(functional.FunctionalTest,
BaseCacheManageMiddlewareTest):
"""
Functional tests that exercise the image cache management
with the Xattr cache driver
"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-xattr installed and xattr support on the
filesystem)
"""
if getattr(self, 'disabled', False):
return
if not getattr(self, 'inited', False):
try:
import xattr # noqa
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-xattr not installed.")
return
self.inited = True
self.disabled = False
self.image_cache_driver = "xattr"
super(TestImageCacheManageXattr, self).setUp()
self.api_server.deployment_flavor = "cachemanagement"
if not xattr_writes_supported(self.test_dir):
self.inited = True
self.disabled = True
self.disabled_message = ("filesystem does not support xattr")
return
def tearDown(self):
super(TestImageCacheManageXattr, self).tearDown()
if os.path.exists(self.api_server.image_cache_dir):
shutil.rmtree(self.api_server.image_cache_dir)
class TestImageCacheSqlite(functional.FunctionalTest,
BaseCacheMiddlewareTest):
"""
Functional tests that exercise the image cache using the
SQLite driver
"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-xattr installed and xattr support on the
filesystem)
"""
if getattr(self, 'disabled', False):
return
if not getattr(self, 'inited', False):
try:
import sqlite3 # noqa
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-sqlite3 not installed.")
return
self.inited = True
self.disabled = False
super(TestImageCacheSqlite, self).setUp()
self.api_server.deployment_flavor = "caching"
def tearDown(self):
super(TestImageCacheSqlite, self).tearDown()
if os.path.exists(self.api_server.image_cache_dir):
shutil.rmtree(self.api_server.image_cache_dir)
class TestImageCacheManageSqlite(functional.FunctionalTest,
BaseCacheManageMiddlewareTest):
"""
Functional tests that exercise the image cache management using the
SQLite driver
"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-xattr installed and xattr support on the
filesystem)
"""
if getattr(self, 'disabled', False):
return
if not getattr(self, 'inited', False):
try:
import sqlite3 # noqa
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-sqlite3 not installed.")
return
self.inited = True
self.disabled = False
self.image_cache_driver = "sqlite"
super(TestImageCacheManageSqlite, self).setUp()
self.api_server.deployment_flavor = "cachemanagement"
def tearDown(self):
super(TestImageCacheManageSqlite, self).tearDown()
if os.path.exists(self.api_server.image_cache_dir):
shutil.rmtree(self.api_server.image_cache_dir)
glance-16.0.1/glance/tests/functional/test_sqlite.py 0000666 0001750 0001750 00000002504 13267672245 022542 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test cases for sqlite-specific logic"""
from glance.tests import functional
from glance.tests.utils import depends_on_exe
from glance.tests.utils import execute
from glance.tests.utils import skip_if_disabled
class TestSqlite(functional.FunctionalTest):
"""Functional tests for sqlite-specific logic"""
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_big_int_mapping(self):
"""Ensure BigInteger not mapped to BIGINT"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
cmd = "sqlite3 tests.sqlite '.schema'"
exitcode, out, err = execute(cmd, raise_error=True)
self.assertNotIn('BIGINT', out)
self.stop_servers()
glance-16.0.1/glance/tests/functional/db/ 0000775 0001750 0001750 00000000000 13267672475 020217 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/db/test_migrations.py 0000666 0001750 0001750 00000016240 13267672245 024004 0 ustar zuul zuul 0000000 0000000 # Copyright 2016 Rackspace
# Copyright 2016 Intel Corporation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from alembic import command as alembic_command
from alembic import script as alembic_script
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import test_fixtures
from oslo_db.sqlalchemy import test_migrations
import sqlalchemy.types as types
from glance.db.sqlalchemy import alembic_migrations
from glance.db.sqlalchemy.alembic_migrations import versions
from glance.db.sqlalchemy import models
from glance.db.sqlalchemy import models_metadef
import glance.tests.utils as test_utils
class AlembicMigrationsMixin(object):
def setUp(self):
super(AlembicMigrationsMixin, self).setUp()
self.engine = enginefacade.writer.get_engine()
def _get_revisions(self, config, head=None):
head = head or 'heads'
scripts_dir = alembic_script.ScriptDirectory.from_config(config)
revisions = list(scripts_dir.walk_revisions(base='base',
head=head))
revisions = list(reversed(revisions))
revisions = [rev.revision for rev in revisions]
return revisions
def _migrate_up(self, config, engine, revision, with_data=False):
if with_data:
data = None
pre_upgrade = getattr(self, '_pre_upgrade_%s' % revision, None)
if pre_upgrade:
data = pre_upgrade(engine)
alembic_command.upgrade(config, revision)
if with_data:
check = getattr(self, '_check_%s' % revision, None)
if check:
check(engine, data)
def test_walk_versions(self):
alembic_config = alembic_migrations.get_alembic_config(self.engine)
for revision in self._get_revisions(alembic_config):
self._migrate_up(alembic_config, self.engine, revision,
with_data=True)
class TestMysqlMigrations(test_fixtures.OpportunisticDBTestMixin,
AlembicMigrationsMixin,
test_utils.BaseTestCase):
FIXTURE = test_fixtures.MySQLOpportunisticFixture
def test_mysql_innodb_tables(self):
test_utils.db_sync(engine=self.engine)
total = self.engine.execute(
"SELECT COUNT(*) "
"FROM information_schema.TABLES "
"WHERE TABLE_SCHEMA='%s'"
% self.engine.url.database)
self.assertGreater(total.scalar(), 0, "No tables found. Wrong schema?")
noninnodb = self.engine.execute(
"SELECT count(*) "
"FROM information_schema.TABLES "
"WHERE TABLE_SCHEMA='%s' "
"AND ENGINE!='InnoDB' "
"AND TABLE_NAME!='migrate_version'"
% self.engine.url.database)
count = noninnodb.scalar()
self.assertEqual(0, count, "%d non InnoDB tables created" % count)
class TestPostgresqlMigrations(test_fixtures.OpportunisticDBTestMixin,
AlembicMigrationsMixin,
test_utils.BaseTestCase):
FIXTURE = test_fixtures.PostgresqlOpportunisticFixture
class TestSqliteMigrations(test_fixtures.OpportunisticDBTestMixin,
AlembicMigrationsMixin,
test_utils.BaseTestCase):
pass
class TestMigrations(test_fixtures.OpportunisticDBTestMixin,
test_utils.BaseTestCase):
def test_no_downgrade(self):
migrate_file = versions.__path__[0]
for parent, dirnames, filenames in os.walk(migrate_file):
for filename in filenames:
if filename.split('.')[1] == 'py':
model_name = filename.split('.')[0]
model = __import__(
'glance.db.sqlalchemy.alembic_migrations.versions.' +
model_name)
obj = getattr(getattr(getattr(getattr(getattr(
model, 'db'), 'sqlalchemy'), 'alembic_migrations'),
'versions'), model_name)
func = getattr(obj, 'downgrade', None)
self.assertIsNone(func)
class ModelsMigrationSyncMixin(object):
def setUp(self):
super(ModelsMigrationSyncMixin, self).setUp()
self.engine = enginefacade.writer.get_engine()
def get_metadata(self):
for table in models_metadef.BASE_DICT.metadata.sorted_tables:
models.BASE.metadata._add_table(table.name, table.schema, table)
return models.BASE.metadata
def get_engine(self):
return self.engine
def db_sync(self, engine):
test_utils.db_sync(engine=engine)
# TODO(akamyshikova): remove this method as soon as comparison with Variant
# will be implemented in oslo.db or alembic
def compare_type(self, ctxt, insp_col, meta_col, insp_type, meta_type):
if isinstance(meta_type, types.Variant):
meta_orig_type = meta_col.type
insp_orig_type = insp_col.type
meta_col.type = meta_type.impl
insp_col.type = meta_type.impl
try:
return self.compare_type(ctxt, insp_col, meta_col, insp_type,
meta_type.impl)
finally:
meta_col.type = meta_orig_type
insp_col.type = insp_orig_type
else:
ret = super(ModelsMigrationSyncMixin, self).compare_type(
ctxt, insp_col, meta_col, insp_type, meta_type)
if ret is not None:
return ret
return ctxt.impl.compare_type(insp_col, meta_col)
def include_object(self, object_, name, type_, reflected, compare_to):
if name in ['migrate_version'] and type_ == 'table':
return False
return True
class ModelsMigrationsSyncMysql(ModelsMigrationSyncMixin,
test_migrations.ModelsMigrationsSync,
test_fixtures.OpportunisticDBTestMixin,
test_utils.BaseTestCase):
FIXTURE = test_fixtures.MySQLOpportunisticFixture
class ModelsMigrationsSyncPostgres(ModelsMigrationSyncMixin,
test_migrations.ModelsMigrationsSync,
test_fixtures.OpportunisticDBTestMixin,
test_utils.BaseTestCase):
FIXTURE = test_fixtures.PostgresqlOpportunisticFixture
class ModelsMigrationsSyncSqlite(ModelsMigrationSyncMixin,
test_migrations.ModelsMigrationsSync,
test_fixtures.OpportunisticDBTestMixin,
test_utils.BaseTestCase):
pass
glance-16.0.1/glance/tests/functional/db/base_metadef.py 0000666 0001750 0001750 00000072203 13267672245 023171 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from glance.common import config
from glance.common import exception
from glance import context
import glance.tests.functional.db as db_tests
from glance.tests import utils as test_utils
def build_namespace_fixture(**kwargs):
namespace = {
'namespace': u'MyTestNamespace',
'display_name': u'test-display-name',
'description': u'test-description',
'visibility': u'public',
'protected': 0,
'owner': u'test-owner'
}
namespace.update(kwargs)
return namespace
def build_resource_type_fixture(**kwargs):
resource_type = {
'name': u'MyTestResourceType',
'protected': 0
}
resource_type.update(kwargs)
return resource_type
def build_association_fixture(**kwargs):
association = {
'name': u'MyTestResourceType',
'properties_target': 'test-properties-target',
'prefix': 'test-prefix'
}
association.update(kwargs)
return association
def build_object_fixture(**kwargs):
# Full testing of required and schema done via rest api tests
object = {
'namespace_id': 1,
'name': u'test-object-name',
'description': u'test-object-description',
'required': u'fake-required-properties-list',
'json_schema': u'{fake-schema}'
}
object.update(kwargs)
return object
def build_property_fixture(**kwargs):
# Full testing of required and schema done via rest api tests
property = {
'namespace_id': 1,
'name': u'test-property-name',
'json_schema': u'{fake-schema}'
}
property.update(kwargs)
return property
def build_tag_fixture(**kwargs):
# Full testing of required and schema done via rest api tests
tag = {
'namespace_id': 1,
'name': u'test-tag-name',
}
tag.update(kwargs)
return tag
def build_tags_fixture(tag_name_list):
tag_list = []
for tag_name in tag_name_list:
tag_list.append({'name': tag_name})
return tag_list
class TestMetadefDriver(test_utils.BaseTestCase):
"""Test Driver class for Metadef tests."""
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestMetadefDriver, self).setUp()
config.parse_args(args=[])
context_cls = context.RequestContext
self.adm_context = context_cls(is_admin=True,
auth_token='user:user:admin')
self.context = context_cls(is_admin=False,
auth_token='user:user:user')
self.db_api = db_tests.get_db(self.config)
db_tests.reset_db(self.db_api)
def _assert_saved_fields(self, expected, actual):
for k in expected.keys():
self.assertEqual(expected[k], actual[k])
class MetadefNamespaceTests(object):
def test_namespace_create(self):
fixture = build_namespace_fixture()
created = self.db_api.metadef_namespace_create(self.context, fixture)
self.assertIsNotNone(created)
self._assert_saved_fields(fixture, created)
def test_namespace_create_duplicate(self):
fixture = build_namespace_fixture()
created = self.db_api.metadef_namespace_create(self.context, fixture)
self.assertIsNotNone(created)
self._assert_saved_fields(fixture, created)
self.assertRaises(exception.Duplicate,
self.db_api.metadef_namespace_create,
self.context, fixture)
def test_namespace_get(self):
fixture = build_namespace_fixture()
created = self.db_api.metadef_namespace_create(self.context, fixture)
self.assertIsNotNone(created)
self._assert_saved_fields(fixture, created)
found = self.db_api.metadef_namespace_get(
self.context, created['namespace'])
self.assertIsNotNone(found, "Namespace not found.")
def test_namespace_get_all_with_resource_types_filter(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(
self.context, ns_fixture)
self.assertIsNotNone(ns_created, "Could not create a namespace.")
self._assert_saved_fields(ns_fixture, ns_created)
fixture = build_association_fixture()
created = self.db_api.metadef_resource_type_association_create(
self.context, ns_created['namespace'], fixture)
self.assertIsNotNone(created, "Could not create an association.")
rt_filters = {'resource_types': fixture['name']}
found = self.db_api.metadef_namespace_get_all(
self.context, filters=rt_filters, sort_key='created_at')
self.assertEqual(1, len(found))
for item in found:
self._assert_saved_fields(ns_fixture, item)
def test_namespace_update(self):
delta = {'owner': u'New Owner'}
fixture = build_namespace_fixture()
created = self.db_api.metadef_namespace_create(self.context, fixture)
self.assertIsNotNone(created['namespace'])
self.assertEqual(fixture['namespace'], created['namespace'])
delta_dict = copy.deepcopy(created)
delta_dict.update(delta.copy())
updated = self.db_api.metadef_namespace_update(
self.context, created['id'], delta_dict)
self.assertEqual(delta['owner'], updated['owner'])
def test_namespace_delete(self):
fixture = build_namespace_fixture()
created = self.db_api.metadef_namespace_create(self.context, fixture)
self.assertIsNotNone(created, "Could not create a Namespace.")
self.db_api.metadef_namespace_delete(
self.context, created['namespace'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_namespace_get,
self.context, created['namespace'])
def test_namespace_delete_with_content(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self._assert_saved_fields(fixture_ns, created_ns)
# Create object content for the namespace
fixture_obj = build_object_fixture()
created_obj = self.db_api.metadef_object_create(
self.context, created_ns['namespace'], fixture_obj)
self.assertIsNotNone(created_obj)
# Create property content for the namespace
fixture_prop = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], fixture_prop)
self.assertIsNotNone(created_prop)
# Create associations
fixture_assn = build_association_fixture()
created_assn = self.db_api.metadef_resource_type_association_create(
self.context, created_ns['namespace'], fixture_assn)
self.assertIsNotNone(created_assn)
deleted_ns = self.db_api.metadef_namespace_delete(
self.context, created_ns['namespace'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_namespace_get,
self.context, deleted_ns['namespace'])
class MetadefPropertyTests(object):
def test_property_create(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
fixture_prop = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], fixture_prop)
self._assert_saved_fields(fixture_prop, created_prop)
def test_property_create_duplicate(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
fixture_prop = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], fixture_prop)
self._assert_saved_fields(fixture_prop, created_prop)
self.assertRaises(exception.Duplicate,
self.db_api.metadef_property_create,
self.context, created_ns['namespace'], fixture_prop)
def test_property_get(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture_ns, created_ns)
fixture_prop = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], fixture_prop)
found_prop = self.db_api.metadef_property_get(
self.context, created_ns['namespace'], created_prop['name'])
self._assert_saved_fields(fixture_prop, found_prop)
def test_property_get_all(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(
self.context, ns_fixture)
self.assertIsNotNone(ns_created, "Could not create a namespace.")
self._assert_saved_fields(ns_fixture, ns_created)
fixture1 = build_property_fixture(namespace_id=ns_created['id'])
created_p1 = self.db_api.metadef_property_create(
self.context, ns_created['namespace'], fixture1)
self.assertIsNotNone(created_p1, "Could not create a property.")
fixture2 = build_property_fixture(namespace_id=ns_created['id'],
name='test-prop-2')
created_p2 = self.db_api.metadef_property_create(
self.context, ns_created['namespace'], fixture2)
self.assertIsNotNone(created_p2, "Could not create a property.")
found = self.db_api.metadef_property_get_all(
self.context, ns_created['namespace'])
self.assertEqual(2, len(found))
def test_property_update(self):
delta = {'name': u'New-name', 'json_schema': u'new-schema'}
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
prop_fixture = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], prop_fixture)
self.assertIsNotNone(created_prop, "Could not create a property.")
delta_dict = copy.deepcopy(created_prop)
delta_dict.update(delta.copy())
updated = self.db_api.metadef_property_update(
self.context, created_ns['namespace'],
created_prop['id'], delta_dict)
self.assertEqual(delta['name'], updated['name'])
self.assertEqual(delta['json_schema'], updated['json_schema'])
def test_property_delete(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
prop_fixture = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], prop_fixture)
self.assertIsNotNone(created_prop, "Could not create a property.")
self.db_api.metadef_property_delete(
self.context, created_ns['namespace'], created_prop['name'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_property_get,
self.context, created_ns['namespace'],
created_prop['name'])
def test_property_delete_namespace_content(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
prop_fixture = build_property_fixture(namespace_id=created_ns['id'])
created_prop = self.db_api.metadef_property_create(
self.context, created_ns['namespace'], prop_fixture)
self.assertIsNotNone(created_prop, "Could not create a property.")
self.db_api.metadef_property_delete_namespace_content(
self.context, created_ns['namespace'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_property_get,
self.context, created_ns['namespace'],
created_prop['name'])
class MetadefObjectTests(object):
def test_object_create(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
fixture_object = build_object_fixture(namespace_id=created_ns['id'])
created_object = self.db_api.metadef_object_create(
self.context, created_ns['namespace'], fixture_object)
self._assert_saved_fields(fixture_object, created_object)
def test_object_create_duplicate(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
fixture_object = build_object_fixture(namespace_id=created_ns['id'])
created_object = self.db_api.metadef_object_create(
self.context, created_ns['namespace'], fixture_object)
self._assert_saved_fields(fixture_object, created_object)
self.assertRaises(exception.Duplicate,
self.db_api.metadef_object_create,
self.context, created_ns['namespace'],
fixture_object)
def test_object_get(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture_ns)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture_ns, created_ns)
fixture_object = build_object_fixture(namespace_id=created_ns['id'])
created_object = self.db_api.metadef_object_create(
self.context, created_ns['namespace'], fixture_object)
found_object = self.db_api.metadef_object_get(
self.context, created_ns['namespace'], created_object['name'])
self._assert_saved_fields(fixture_object, found_object)
def test_object_get_all(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(self.context,
ns_fixture)
self.assertIsNotNone(ns_created, "Could not create a namespace.")
self._assert_saved_fields(ns_fixture, ns_created)
fixture1 = build_object_fixture(namespace_id=ns_created['id'])
created_o1 = self.db_api.metadef_object_create(
self.context, ns_created['namespace'], fixture1)
self.assertIsNotNone(created_o1, "Could not create an object.")
fixture2 = build_object_fixture(namespace_id=ns_created['id'],
name='test-object-2')
created_o2 = self.db_api.metadef_object_create(
self.context, ns_created['namespace'], fixture2)
self.assertIsNotNone(created_o2, "Could not create an object.")
found = self.db_api.metadef_object_get_all(
self.context, ns_created['namespace'])
self.assertEqual(2, len(found))
def test_object_update(self):
delta = {'name': u'New-name', 'json_schema': u'new-schema',
'required': u'new-required'}
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
object_fixture = build_object_fixture(namespace_id=created_ns['id'])
created_object = self.db_api.metadef_object_create(
self.context, created_ns['namespace'], object_fixture)
self.assertIsNotNone(created_object, "Could not create an object.")
delta_dict = {}
delta_dict.update(delta.copy())
updated = self.db_api.metadef_object_update(
self.context, created_ns['namespace'],
created_object['id'], delta_dict)
self.assertEqual(delta['name'], updated['name'])
self.assertEqual(delta['json_schema'], updated['json_schema'])
def test_object_delete(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
object_fixture = build_object_fixture(namespace_id=created_ns['id'])
created_object = self.db_api.metadef_object_create(
self.context, created_ns['namespace'], object_fixture)
self.assertIsNotNone(created_object, "Could not create an object.")
self.db_api.metadef_object_delete(
self.context, created_ns['namespace'], created_object['name'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_object_get,
self.context, created_ns['namespace'],
created_object['name'])
class MetadefResourceTypeTests(object):
def test_resource_type_get_all(self):
resource_types_orig = self.db_api.metadef_resource_type_get_all(
self.context)
fixture = build_resource_type_fixture()
self.db_api.metadef_resource_type_create(self.context, fixture)
resource_types = self.db_api.metadef_resource_type_get_all(
self.context)
test_len = len(resource_types_orig) + 1
self.assertEqual(test_len, len(resource_types))
class MetadefResourceTypeAssociationTests(object):
def test_association_create(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(
self.context, ns_fixture)
self.assertIsNotNone(ns_created)
self._assert_saved_fields(ns_fixture, ns_created)
assn_fixture = build_association_fixture()
assn_created = self.db_api.metadef_resource_type_association_create(
self.context, ns_created['namespace'], assn_fixture)
self.assertIsNotNone(assn_created)
self._assert_saved_fields(assn_fixture, assn_created)
def test_association_create_duplicate(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(
self.context, ns_fixture)
self.assertIsNotNone(ns_created)
self._assert_saved_fields(ns_fixture, ns_created)
assn_fixture = build_association_fixture()
assn_created = self.db_api.metadef_resource_type_association_create(
self.context, ns_created['namespace'], assn_fixture)
self.assertIsNotNone(assn_created)
self._assert_saved_fields(assn_fixture, assn_created)
self.assertRaises(exception.Duplicate,
self.db_api.
metadef_resource_type_association_create,
self.context, ns_created['namespace'], assn_fixture)
def test_association_delete(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(
self.context, ns_fixture)
self.assertIsNotNone(ns_created, "Could not create a namespace.")
self._assert_saved_fields(ns_fixture, ns_created)
fixture = build_association_fixture()
created = self.db_api.metadef_resource_type_association_create(
self.context, ns_created['namespace'], fixture)
self.assertIsNotNone(created, "Could not create an association.")
created_resource = self.db_api.metadef_resource_type_get(
self.context, fixture['name'])
self.assertIsNotNone(created_resource, "resource_type not created")
self.db_api.metadef_resource_type_association_delete(
self.context, ns_created['namespace'], created_resource['name'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_resource_type_association_get,
self.context, ns_created['namespace'],
created_resource['name'])
def test_association_get_all_by_namespace(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(
self.context, ns_fixture)
self.assertIsNotNone(ns_created, "Could not create a namespace.")
self._assert_saved_fields(ns_fixture, ns_created)
fixture = build_association_fixture()
created = self.db_api.metadef_resource_type_association_create(
self.context, ns_created['namespace'], fixture)
self.assertIsNotNone(created, "Could not create an association.")
found = (
self.db_api.metadef_resource_type_association_get_all_by_namespace(
self.context, ns_created['namespace']))
self.assertEqual(1, len(found))
for item in found:
self._assert_saved_fields(fixture, item)
class MetadefTagTests(object):
def test_tag_create(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
fixture_tag = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], fixture_tag)
self._assert_saved_fields(fixture_tag, created_tag)
def test_tag_create_duplicate(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
fixture_tag = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], fixture_tag)
self._assert_saved_fields(fixture_tag, created_tag)
self.assertRaises(exception.Duplicate,
self.db_api.metadef_tag_create,
self.context, created_ns['namespace'],
fixture_tag)
def test_tag_create_tags(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
tags = build_tags_fixture(['Tag1', 'Tag2', 'Tag3'])
created_tags = self.db_api.metadef_tag_create_tags(
self.context, created_ns['namespace'], tags)
actual = set([tag['name'] for tag in created_tags])
expected = set(['Tag1', 'Tag2', 'Tag3'])
self.assertEqual(expected, actual)
def test_tag_create_duplicate_tags_1(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
tags = build_tags_fixture(['Tag1', 'Tag2', 'Tag3', 'Tag2'])
self.assertRaises(exception.Duplicate,
self.db_api.metadef_tag_create_tags,
self.context, created_ns['namespace'],
tags)
def test_tag_create_duplicate_tags_2(self):
fixture = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture, created_ns)
tags = build_tags_fixture(['Tag1', 'Tag2', 'Tag3'])
self.db_api.metadef_tag_create_tags(self.context,
created_ns['namespace'], tags)
dup_tag = build_tag_fixture(namespace_id=created_ns['id'],
name='Tag3')
self.assertRaises(exception.Duplicate,
self.db_api.metadef_tag_create,
self.context, created_ns['namespace'], dup_tag)
def test_tag_get(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture_ns)
self.assertIsNotNone(created_ns)
self._assert_saved_fields(fixture_ns, created_ns)
fixture_tag = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], fixture_tag)
found_tag = self.db_api.metadef_tag_get(
self.context, created_ns['namespace'], created_tag['name'])
self._assert_saved_fields(fixture_tag, found_tag)
def test_tag_get_all(self):
ns_fixture = build_namespace_fixture()
ns_created = self.db_api.metadef_namespace_create(self.context,
ns_fixture)
self.assertIsNotNone(ns_created, "Could not create a namespace.")
self._assert_saved_fields(ns_fixture, ns_created)
fixture1 = build_tag_fixture(namespace_id=ns_created['id'])
created_tag1 = self.db_api.metadef_tag_create(
self.context, ns_created['namespace'], fixture1)
self.assertIsNotNone(created_tag1, "Could not create tag 1.")
fixture2 = build_tag_fixture(namespace_id=ns_created['id'],
name='test-tag-2')
created_tag2 = self.db_api.metadef_tag_create(
self.context, ns_created['namespace'], fixture2)
self.assertIsNotNone(created_tag2, "Could not create tag 2.")
found = self.db_api.metadef_tag_get_all(
self.context, ns_created['namespace'], sort_key='created_at')
self.assertEqual(2, len(found))
def test_tag_update(self):
delta = {'name': u'New-name'}
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
tag_fixture = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], tag_fixture)
self.assertIsNotNone(created_tag, "Could not create a tag.")
delta_dict = {}
delta_dict.update(delta.copy())
updated = self.db_api.metadef_tag_update(
self.context, created_ns['namespace'],
created_tag['id'], delta_dict)
self.assertEqual(delta['name'], updated['name'])
def test_tag_delete(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
tag_fixture = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], tag_fixture)
self.assertIsNotNone(created_tag, "Could not create a tag.")
self.db_api.metadef_tag_delete(
self.context, created_ns['namespace'], created_tag['name'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_tag_get,
self.context, created_ns['namespace'],
created_tag['name'])
class MetadefDriverTests(MetadefNamespaceTests,
MetadefResourceTypeTests,
MetadefResourceTypeAssociationTests,
MetadefPropertyTests,
MetadefObjectTests,
MetadefTagTests):
# collection class
pass
glance-16.0.1/glance/tests/functional/db/__init__.py 0000666 0001750 0001750 00000002065 13267672245 022330 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# NOTE(markwash): These functions are used in the base tests cases to
# set up the db api implementation under test. Rather than accessing them
# directly, test modules should use the load and reset functions below.
get_db = None
reset_db = None
def load(get_db_fn, reset_db_fn):
global get_db, reset_db
get_db = get_db_fn
reset_db = reset_db_fn
def reset():
global get_db, reset_db
get_db = None
reset_db = None
glance-16.0.1/glance/tests/functional/db/base.py 0000666 0001750 0001750 00000331557 13267672245 021516 0 ustar zuul zuul 0000000 0000000 # Copyright 2010-2012 OpenStack Foundation
# Copyright 2012 Justin Santa Barbara
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import datetime
import uuid
import mock
from oslo_db import exception as db_exception
from oslo_db.sqlalchemy import utils as sqlalchemyutils
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from six.moves import reduce
from sqlalchemy.dialects import sqlite
from glance.common import exception
from glance.common import timeutils
from glance import context
from glance.db.sqlalchemy import api as db_api
from glance.tests import functional
import glance.tests.functional.db as db_tests
from glance.tests import utils as test_utils
# The default sort order of results is whatever sort key is specified,
# plus created_at and id for ties. When we're not specifying a sort_key,
# we get the default (created_at). Some tests below expect the fixtures to be
# returned in array-order, so if the created_at timestamps are the same,
# these tests rely on the UUID* values being in order
UUID1, UUID2, UUID3 = sorted([str(uuid.uuid4()) for x in range(3)])
def build_image_fixture(**kwargs):
default_datetime = timeutils.utcnow()
image = {
'id': str(uuid.uuid4()),
'name': 'fake image #2',
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'is_public': True,
'created_at': default_datetime,
'updated_at': default_datetime,
'deleted_at': None,
'deleted': False,
'checksum': None,
'min_disk': 5,
'min_ram': 256,
'size': 19,
'locations': [{'url': "file:///tmp/glance-tests/2",
'metadata': {}, 'status': 'active'}],
'properties': {},
}
if 'visibility' in kwargs:
image.pop('is_public')
image.update(kwargs)
return image
def build_task_fixture(**kwargs):
default_datetime = timeutils.utcnow()
task = {
'id': str(uuid.uuid4()),
'type': 'import',
'status': 'pending',
'input': {'ping': 'pong'},
'owner': str(uuid.uuid4()),
'message': None,
'expires_at': None,
'created_at': default_datetime,
'updated_at': default_datetime,
}
task.update(kwargs)
return task
class FunctionalInitWrapper(functional.FunctionalTest):
def setUp(self):
super(FunctionalInitWrapper, self).setUp()
self.config(policy_file=self.policy_file, group='oslo_policy')
class TestDriver(test_utils.BaseTestCase):
def setUp(self):
super(TestDriver, self).setUp()
context_cls = context.RequestContext
self.adm_context = context_cls(is_admin=True,
auth_token='user:user:admin')
self.context = context_cls(is_admin=False,
auth_token='user:user:user')
self.db_api = db_tests.get_db(self.config)
db_tests.reset_db(self.db_api)
self.fixtures = self.build_image_fixtures()
self.create_images(self.fixtures)
def build_image_fixtures(self):
dt1 = timeutils.utcnow()
dt2 = dt1 + datetime.timedelta(microseconds=5)
fixtures = [
{
'id': UUID1,
'created_at': dt1,
'updated_at': dt1,
'properties': {'foo': 'bar', 'far': 'boo'},
'protected': True,
'size': 13,
},
{
'id': UUID2,
'created_at': dt1,
'updated_at': dt2,
'size': 17,
},
{
'id': UUID3,
'created_at': dt2,
'updated_at': dt2,
},
]
return [build_image_fixture(**fixture) for fixture in fixtures]
def create_images(self, images):
for fixture in images:
self.db_api.image_create(self.adm_context, fixture)
class DriverTests(object):
def test_image_create_requires_status(self):
fixture = {'name': 'mark', 'size': 12}
self.assertRaises(exception.Invalid,
self.db_api.image_create, self.context, fixture)
fixture = {'name': 'mark', 'size': 12, 'status': 'queued'}
self.db_api.image_create(self.context, fixture)
@mock.patch.object(timeutils, 'utcnow')
def test_image_create_defaults(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime.utcnow()
create_time = timeutils.utcnow()
values = {'status': 'queued',
'created_at': create_time,
'updated_at': create_time}
image = self.db_api.image_create(self.context, values)
self.assertIsNone(image['name'])
self.assertIsNone(image['container_format'])
self.assertEqual(0, image['min_ram'])
self.assertEqual(0, image['min_disk'])
self.assertIsNone(image['owner'])
self.assertEqual('shared', image['visibility'])
self.assertIsNone(image['size'])
self.assertIsNone(image['checksum'])
self.assertIsNone(image['disk_format'])
self.assertEqual([], image['locations'])
self.assertFalse(image['protected'])
self.assertFalse(image['deleted'])
self.assertIsNone(image['deleted_at'])
self.assertEqual([], image['properties'])
self.assertEqual(create_time, image['created_at'])
self.assertEqual(create_time, image['updated_at'])
# Image IDs aren't predictable, but they should be populated
self.assertTrue(uuid.UUID(image['id']))
# NOTE(bcwaldon): the tags attribute should not be returned as a part
# of a core image entity
self.assertNotIn('tags', image)
def test_image_create_duplicate_id(self):
self.assertRaises(exception.Duplicate,
self.db_api.image_create,
self.context, {'id': UUID1, 'status': 'queued'})
def test_image_create_with_locations(self):
locations = [{'url': 'a', 'metadata': {}, 'status': 'active'},
{'url': 'b', 'metadata': {}, 'status': 'active'}]
fixture = {'status': 'queued',
'locations': locations}
image = self.db_api.image_create(self.context, fixture)
actual = [{'url': l['url'], 'metadata': l['metadata'],
'status': l['status']}
for l in image['locations']]
self.assertEqual(locations, actual)
def test_image_create_without_locations(self):
locations = []
fixture = {'status': 'queued',
'locations': locations}
self.db_api.image_create(self.context, fixture)
def test_image_create_with_location_data(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'},
{'url': 'b', 'metadata': {},
'status': 'active'}]
fixture = {'status': 'queued', 'locations': location_data}
image = self.db_api.image_create(self.context, fixture)
actual = [{'url': l['url'], 'metadata': l['metadata'],
'status': l['status']}
for l in image['locations']]
self.assertEqual(location_data, actual)
def test_image_create_properties(self):
fixture = {'status': 'queued', 'properties': {'ping': 'pong'}}
image = self.db_api.image_create(self.context, fixture)
expected = [{'name': 'ping', 'value': 'pong'}]
actual = [{'name': p['name'], 'value': p['value']}
for p in image['properties']]
self.assertEqual(expected, actual)
def test_image_create_unknown_attributes(self):
fixture = {'ping': 'pong'}
self.assertRaises(exception.Invalid,
self.db_api.image_create, self.context, fixture)
def test_image_create_bad_name(self):
bad_name = u'A name with forbidden symbol \U0001f62a'
fixture = {'name': bad_name, 'size': 12, 'status': 'queued'}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_checksum(self):
# checksum should be no longer than 32 characters
bad_checksum = "42" * 42
fixture = {'checksum': bad_checksum}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
# if checksum is not longer than 32 characters but non-ascii ->
# still raise 400
fixture = {'checksum': u'\u042f' * 32}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_int_params(self):
int_too_long = 2 ** 31 + 42
for param in ['min_disk', 'min_ram']:
fixture = {param: int_too_long}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_property(self):
# bad value
fixture = {'status': 'queued',
'properties': {'bad': u'Bad \U0001f62a'}}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
# bad property names are also not allowed
fixture = {'status': 'queued', 'properties': {u'Bad \U0001f62a': 'ok'}}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_create_bad_location(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'},
{'url': u'Bad \U0001f60a', 'metadata': {},
'status': 'active'}]
fixture = {'status': 'queued', 'locations': location_data}
self.assertRaises(exception.Invalid, self.db_api.image_create,
self.context, fixture)
def test_image_update_core_attribute(self):
fixture = {'status': 'queued'}
image = self.db_api.image_update(self.adm_context, UUID3, fixture)
self.assertEqual('queued', image['status'])
self.assertNotEqual(image['created_at'], image['updated_at'])
def test_image_update_with_locations(self):
locations = [{'url': 'a', 'metadata': {}, 'status': 'active'},
{'url': 'b', 'metadata': {}, 'status': 'active'}]
fixture = {'locations': locations}
image = self.db_api.image_update(self.adm_context, UUID3, fixture)
self.assertEqual(2, len(image['locations']))
self.assertIn('id', image['locations'][0])
self.assertIn('id', image['locations'][1])
image['locations'][0].pop('id')
image['locations'][1].pop('id')
self.assertEqual(locations, image['locations'])
def test_image_update_with_location_data(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'},
{'url': 'b', 'metadata': {}, 'status': 'active'}]
fixture = {'locations': location_data}
image = self.db_api.image_update(self.adm_context, UUID3, fixture)
self.assertEqual(2, len(image['locations']))
self.assertIn('id', image['locations'][0])
self.assertIn('id', image['locations'][1])
image['locations'][0].pop('id')
image['locations'][1].pop('id')
self.assertEqual(location_data, image['locations'])
def test_image_update(self):
fixture = {'status': 'queued', 'properties': {'ping': 'pong'}}
image = self.db_api.image_update(self.adm_context, UUID3, fixture)
expected = [{'name': 'ping', 'value': 'pong'}]
actual = [{'name': p['name'], 'value': p['value']}
for p in image['properties']]
self.assertEqual(expected, actual)
self.assertEqual('queued', image['status'])
self.assertNotEqual(image['created_at'], image['updated_at'])
def test_image_update_properties(self):
fixture = {'properties': {'ping': 'pong'}}
image = self.db_api.image_update(self.adm_context, UUID1, fixture)
expected = {'ping': 'pong', 'foo': 'bar', 'far': 'boo'}
actual = {p['name']: p['value'] for p in image['properties']}
self.assertEqual(expected, actual)
self.assertNotEqual(image['created_at'], image['updated_at'])
def test_image_update_purge_properties(self):
fixture = {'properties': {'ping': 'pong'}}
image = self.db_api.image_update(self.adm_context, UUID1,
fixture, purge_props=True)
properties = {p['name']: p for p in image['properties']}
# New properties are set
self.assertIn('ping', properties)
self.assertEqual('pong', properties['ping']['value'])
self.assertFalse(properties['ping']['deleted'])
# Original properties still show up, but with deleted=True
# TODO(markwash): db api should not return deleted properties
self.assertIn('foo', properties)
self.assertEqual('bar', properties['foo']['value'])
self.assertTrue(properties['foo']['deleted'])
def test_image_update_bad_name(self):
fixture = {'name': u'A new name with forbidden symbol \U0001f62a'}
self.assertRaises(exception.Invalid, self.db_api.image_update,
self.adm_context, UUID1, fixture)
def test_image_update_bad_property(self):
# bad value
fixture = {'status': 'queued',
'properties': {'bad': u'Bad \U0001f62a'}}
self.assertRaises(exception.Invalid, self.db_api.image_update,
self.adm_context, UUID1, fixture)
# bad property names are also not allowed
fixture = {'status': 'queued', 'properties': {u'Bad \U0001f62a': 'ok'}}
self.assertRaises(exception.Invalid, self.db_api.image_update,
self.adm_context, UUID1, fixture)
def test_image_update_bad_location(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'},
{'url': u'Bad \U0001f60a', 'metadata': {},
'status': 'active'}]
fixture = {'status': 'queued', 'locations': location_data}
self.assertRaises(exception.Invalid, self.db_api.image_update,
self.adm_context, UUID1, fixture)
def test_update_locations_direct(self):
"""
For some reasons update_locations can be called directly
(not via image_update), so better check that everything is ok if passed
4 byte unicode characters
"""
# update locations correctly first to retrieve existing location id
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'}]
fixture = {'locations': location_data}
image = self.db_api.image_update(self.adm_context, UUID1, fixture)
self.assertEqual(1, len(image['locations']))
self.assertIn('id', image['locations'][0])
loc_id = image['locations'][0].pop('id')
bad_location = {'url': u'Bad \U0001f60a', 'metadata': {},
'status': 'active', 'id': loc_id}
self.assertRaises(exception.Invalid,
self.db_api.image_location_update,
self.adm_context, UUID1, bad_location)
def test_image_property_delete(self):
fixture = {'name': 'ping', 'value': 'pong', 'image_id': UUID1}
prop = self.db_api.image_property_create(self.context, fixture)
prop = self.db_api.image_property_delete(self.context,
prop['name'], UUID1)
self.assertIsNotNone(prop['deleted_at'])
self.assertTrue(prop['deleted'])
def test_image_get(self):
image = self.db_api.image_get(self.context, UUID1)
self.assertEqual(self.fixtures[0]['id'], image['id'])
def test_image_get_disallow_deleted(self):
self.db_api.image_destroy(self.adm_context, UUID1)
self.assertRaises(exception.NotFound, self.db_api.image_get,
self.context, UUID1)
def test_image_get_allow_deleted(self):
self.db_api.image_destroy(self.adm_context, UUID1)
image = self.db_api.image_get(self.adm_context, UUID1)
self.assertEqual(self.fixtures[0]['id'], image['id'])
self.assertTrue(image['deleted'])
def test_image_get_force_allow_deleted(self):
self.db_api.image_destroy(self.adm_context, UUID1)
image = self.db_api.image_get(self.context, UUID1,
force_show_deleted=True)
self.assertEqual(self.fixtures[0]['id'], image['id'])
def test_image_get_not_owned(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
ctxt2 = context.RequestContext(is_admin=False, tenant=TENANT2,
auth_token='user:%s:user' % TENANT2)
image = self.db_api.image_create(
ctxt1, {'status': 'queued', 'owner': TENANT1})
self.assertRaises(exception.Forbidden,
self.db_api.image_get, ctxt2, image['id'])
def test_image_get_not_found(self):
UUID = str(uuid.uuid4())
self.assertRaises(exception.NotFound,
self.db_api.image_get, self.context, UUID)
def test_image_get_all(self):
images = self.db_api.image_get_all(self.context)
self.assertEqual(3, len(images))
def test_image_get_all_with_filter(self):
images = self.db_api.image_get_all(self.context,
filters={
'id': self.fixtures[0]['id'],
})
self.assertEqual(1, len(images))
self.assertEqual(self.fixtures[0]['id'], images[0]['id'])
def test_image_get_all_with_filter_user_defined_property(self):
images = self.db_api.image_get_all(self.context,
filters={'foo': 'bar'})
self.assertEqual(1, len(images))
self.assertEqual(self.fixtures[0]['id'], images[0]['id'])
def test_image_get_all_with_filter_nonexistent_userdef_property(self):
images = self.db_api.image_get_all(self.context,
filters={'faz': 'boo'})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_userdef_prop_nonexistent_value(self):
images = self.db_api.image_get_all(self.context,
filters={'foo': 'baz'})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_multiple_user_defined_properties(self):
images = self.db_api.image_get_all(self.context,
filters={'foo': 'bar',
'far': 'boo'})
self.assertEqual(1, len(images))
self.assertEqual(images[0]['id'], self.fixtures[0]['id'])
def test_image_get_all_with_filter_nonexistent_user_defined_property(self):
images = self.db_api.image_get_all(self.context,
filters={'foo': 'bar',
'faz': 'boo'})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_user_deleted_property(self):
fixture = {'name': 'poo', 'value': 'bear', 'image_id': UUID1}
prop = self.db_api.image_property_create(self.context,
fixture)
images = self.db_api.image_get_all(self.context,
filters={
'properties': {'poo': 'bear'},
})
self.assertEqual(1, len(images))
self.db_api.image_property_delete(self.context,
prop['name'], images[0]['id'])
images = self.db_api.image_get_all(self.context,
filters={
'properties': {'poo': 'bear'},
})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_undefined_property(self):
images = self.db_api.image_get_all(self.context,
filters={'poo': 'bear'})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_protected(self):
images = self.db_api.image_get_all(self.context,
filters={'protected':
True})
self.assertEqual(1, len(images))
images = self.db_api.image_get_all(self.context,
filters={'protected':
False})
self.assertEqual(2, len(images))
def test_image_get_all_with_filter_comparative_created_at(self):
anchor = timeutils.isotime(self.fixtures[0]['created_at'])
time_expr = 'lt:' + anchor
images = self.db_api.image_get_all(self.context,
filters={'created_at': time_expr})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_comparative_updated_at(self):
anchor = timeutils.isotime(self.fixtures[0]['updated_at'])
time_expr = 'lt:' + anchor
images = self.db_api.image_get_all(self.context,
filters={'updated_at': time_expr})
self.assertEqual(0, len(images))
def test_filter_image_by_invalid_operator(self):
self.assertRaises(exception.InvalidFilterOperatorValue,
self.db_api.image_get_all,
self.context, filters={'status': 'lala:active'})
def test_image_get_all_with_filter_in_status(self):
images = self.db_api.image_get_all(self.context,
filters={'status': 'in:active'})
self.assertEqual(3, len(images))
def test_image_get_all_with_filter_in_name(self):
data = 'in:%s' % self.fixtures[0]['name']
images = self.db_api.image_get_all(self.context,
filters={'name': data})
self.assertEqual(3, len(images))
def test_image_get_all_with_filter_in_container_format(self):
images = self.db_api.image_get_all(self.context,
filters={'container_format':
'in:ami,bare,ovf'})
self.assertEqual(3, len(images))
def test_image_get_all_with_filter_in_disk_format(self):
images = self.db_api.image_get_all(self.context,
filters={'disk_format':
'in:vhd'})
self.assertEqual(3, len(images))
def test_image_get_all_with_filter_in_id(self):
data = 'in:%s,%s' % (UUID1, UUID2)
images = self.db_api.image_get_all(self.context,
filters={'id': data})
self.assertEqual(2, len(images))
def test_image_get_all_with_quotes(self):
fixture = {'name': 'fake\\\"name'}
self.db_api.image_update(self.adm_context, UUID3, fixture)
fixture = {'name': 'fake,name'}
self.db_api.image_update(self.adm_context, UUID2, fixture)
fixture = {'name': 'fakename'}
self.db_api.image_update(self.adm_context, UUID1, fixture)
data = 'in:\"fake\\\"name\",fakename,\"fake,name\"'
images = self.db_api.image_get_all(self.context,
filters={'name': data})
self.assertEqual(3, len(images))
def test_image_get_all_with_invalid_quotes(self):
invalid_expr = ['in:\"name', 'in:\"name\"name', 'in:name\"dd\"',
'in:na\"me', 'in:\"name\"\"name\"']
for expr in invalid_expr:
self.assertRaises(exception.InvalidParameterValue,
self.db_api.image_get_all,
self.context, filters={'name': expr})
def test_image_get_all_size_min_max(self):
images = self.db_api.image_get_all(self.context,
filters={
'size_min': 10,
'size_max': 15,
})
self.assertEqual(1, len(images))
self.assertEqual(self.fixtures[0]['id'], images[0]['id'])
def test_image_get_all_size_min(self):
images = self.db_api.image_get_all(self.context,
filters={'size_min': 15})
self.assertEqual(2, len(images))
self.assertEqual(self.fixtures[2]['id'], images[0]['id'])
self.assertEqual(self.fixtures[1]['id'], images[1]['id'])
def test_image_get_all_size_range(self):
images = self.db_api.image_get_all(self.context,
filters={'size_max': 15,
'size_min': 20})
self.assertEqual(0, len(images))
def test_image_get_all_size_max(self):
images = self.db_api.image_get_all(self.context,
filters={'size_max': 15})
self.assertEqual(1, len(images))
self.assertEqual(self.fixtures[0]['id'], images[0]['id'])
def test_image_get_all_with_filter_min_range_bad_value(self):
self.assertRaises(exception.InvalidFilterRangeValue,
self.db_api.image_get_all,
self.context, filters={'size_min': 'blah'})
def test_image_get_all_with_filter_max_range_bad_value(self):
self.assertRaises(exception.InvalidFilterRangeValue,
self.db_api.image_get_all,
self.context, filters={'size_max': 'blah'})
def test_image_get_all_marker(self):
images = self.db_api.image_get_all(self.context, marker=UUID3)
self.assertEqual(2, len(images))
def test_image_get_all_marker_with_size(self):
# Use sort_key=size to test BigInteger
images = self.db_api.image_get_all(self.context, sort_key=['size'],
marker=UUID3)
self.assertEqual(2, len(images))
self.assertEqual(17, images[0]['size'])
self.assertEqual(13, images[1]['size'])
def test_image_get_all_marker_deleted(self):
"""Cannot specify a deleted image as a marker."""
self.db_api.image_destroy(self.adm_context, UUID1)
filters = {'deleted': False}
self.assertRaises(exception.NotFound, self.db_api.image_get_all,
self.context, marker=UUID1, filters=filters)
def test_image_get_all_marker_deleted_showing_deleted_as_admin(self):
"""Specify a deleted image as a marker if showing deleted images."""
self.db_api.image_destroy(self.adm_context, UUID3)
images = self.db_api.image_get_all(self.adm_context, marker=UUID3)
# NOTE(bcwaldon): an admin should see all images (deleted or not)
self.assertEqual(2, len(images))
def test_image_get_all_marker_deleted_showing_deleted(self):
"""Specify a deleted image as a marker if showing deleted images.
A non-admin user has to explicitly ask for deleted
images, and should only see deleted images in the results
"""
self.db_api.image_destroy(self.adm_context, UUID3)
self.db_api.image_destroy(self.adm_context, UUID1)
filters = {'deleted': True}
images = self.db_api.image_get_all(self.context, marker=UUID3,
filters=filters)
self.assertEqual(1, len(images))
def test_image_get_all_marker_null_name_desc(self):
"""Check an image with name null is handled
Check an image with name null is handled
marker is specified and order is descending
"""
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'name': None,
'owner': TENANT1})
images = self.db_api.image_get_all(ctxt1, marker=UUIDX,
sort_key=['name'],
sort_dir=['desc'])
image_ids = [image['id'] for image in images]
expected = []
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_marker_null_disk_format_desc(self):
"""Check an image with disk_format null is handled
Check an image with disk_format null is handled when
marker is specified and order is descending
"""
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'disk_format': None,
'owner': TENANT1})
images = self.db_api.image_get_all(ctxt1, marker=UUIDX,
sort_key=['disk_format'],
sort_dir=['desc'])
image_ids = [image['id'] for image in images]
expected = []
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_marker_null_container_format_desc(self):
"""Check an image with container_format null is handled
Check an image with container_format null is handled when
marker is specified and order is descending
"""
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'container_format': None,
'owner': TENANT1})
images = self.db_api.image_get_all(ctxt1, marker=UUIDX,
sort_key=['container_format'],
sort_dir=['desc'])
image_ids = [image['id'] for image in images]
expected = []
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_marker_null_name_asc(self):
"""Check an image with name null is handled
Check an image with name null is handled when
marker is specified and order is ascending
"""
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'name': None,
'owner': TENANT1})
images = self.db_api.image_get_all(ctxt1, marker=UUIDX,
sort_key=['name'],
sort_dir=['asc'])
image_ids = [image['id'] for image in images]
expected = [UUID3, UUID2, UUID1]
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_marker_null_disk_format_asc(self):
"""Check an image with disk_format null is handled
Check an image with disk_format null is handled when
marker is specified and order is ascending
"""
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'disk_format': None,
'owner': TENANT1})
images = self.db_api.image_get_all(ctxt1, marker=UUIDX,
sort_key=['disk_format'],
sort_dir=['asc'])
image_ids = [image['id'] for image in images]
expected = [UUID3, UUID2, UUID1]
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_marker_null_container_format_asc(self):
"""Check an image with container_format null is handled
Check an image with container_format null is handled when
marker is specified and order is ascending
"""
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'container_format': None,
'owner': TENANT1})
images = self.db_api.image_get_all(ctxt1, marker=UUIDX,
sort_key=['container_format'],
sort_dir=['asc'])
image_ids = [image['id'] for image in images]
expected = [UUID3, UUID2, UUID1]
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_limit(self):
images = self.db_api.image_get_all(self.context, limit=2)
self.assertEqual(2, len(images))
# A limit of None should not equate to zero
images = self.db_api.image_get_all(self.context, limit=None)
self.assertEqual(3, len(images))
# A limit of zero should actually mean zero
images = self.db_api.image_get_all(self.context, limit=0)
self.assertEqual(0, len(images))
def test_image_get_all_owned(self):
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False,
tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
image_meta_data = {'id': UUIDX, 'status': 'queued', 'owner': TENANT1}
self.db_api.image_create(ctxt1, image_meta_data)
TENANT2 = str(uuid.uuid4())
ctxt2 = context.RequestContext(is_admin=False,
tenant=TENANT2,
auth_token='user:%s:user' % TENANT2)
UUIDY = str(uuid.uuid4())
image_meta_data = {'id': UUIDY, 'status': 'queued', 'owner': TENANT2}
self.db_api.image_create(ctxt2, image_meta_data)
images = self.db_api.image_get_all(ctxt1)
image_ids = [image['id'] for image in images]
expected = [UUIDX, UUID3, UUID2, UUID1]
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_owned_checksum(self):
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False,
tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
UUIDX = str(uuid.uuid4())
CHECKSUM1 = '91264c3edf5972c9f1cb309543d38a5c'
image_meta_data = {
'id': UUIDX,
'status': 'queued',
'checksum': CHECKSUM1,
'owner': TENANT1
}
self.db_api.image_create(ctxt1, image_meta_data)
image_member_data = {
'image_id': UUIDX,
'member': TENANT1,
'can_share': False,
"status": "accepted",
}
self.db_api.image_member_create(ctxt1, image_member_data)
TENANT2 = str(uuid.uuid4())
ctxt2 = context.RequestContext(is_admin=False,
tenant=TENANT2,
auth_token='user:%s:user' % TENANT2)
UUIDY = str(uuid.uuid4())
CHECKSUM2 = '92264c3edf5972c9f1cb309543d38a5c'
image_meta_data = {
'id': UUIDY,
'status': 'queued',
'checksum': CHECKSUM2,
'owner': TENANT2
}
self.db_api.image_create(ctxt2, image_meta_data)
image_member_data = {
'image_id': UUIDY,
'member': TENANT2,
'can_share': False,
"status": "accepted",
}
self.db_api.image_member_create(ctxt2, image_member_data)
filters = {'visibility': 'shared', 'checksum': CHECKSUM2}
images = self.db_api.image_get_all(ctxt2, filters)
self.assertEqual(1, len(images))
self.assertEqual(UUIDY, images[0]['id'])
def test_image_get_all_with_filter_tags(self):
self.db_api.image_tag_create(self.context, UUID1, 'x86')
self.db_api.image_tag_create(self.context, UUID1, '64bit')
self.db_api.image_tag_create(self.context, UUID2, 'power')
self.db_api.image_tag_create(self.context, UUID2, '64bit')
images = self.db_api.image_get_all(self.context,
filters={'tags': ['64bit']})
self.assertEqual(2, len(images))
image_ids = [image['id'] for image in images]
expected = [UUID1, UUID2]
self.assertEqual(sorted(expected), sorted(image_ids))
def test_image_get_all_with_filter_multi_tags(self):
self.db_api.image_tag_create(self.context, UUID1, 'x86')
self.db_api.image_tag_create(self.context, UUID1, '64bit')
self.db_api.image_tag_create(self.context, UUID2, 'power')
self.db_api.image_tag_create(self.context, UUID2, '64bit')
images = self.db_api.image_get_all(self.context,
filters={'tags': ['64bit', 'power']
})
self.assertEqual(1, len(images))
self.assertEqual(UUID2, images[0]['id'])
def test_image_get_all_with_filter_tags_and_nonexistent(self):
self.db_api.image_tag_create(self.context, UUID1, 'x86')
images = self.db_api.image_get_all(self.context,
filters={'tags': ['x86', 'fake']
})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_deleted_tags(self):
tag = self.db_api.image_tag_create(self.context, UUID1, 'AIX')
images = self.db_api.image_get_all(self.context,
filters={
'tags': [tag],
})
self.assertEqual(1, len(images))
self.db_api.image_tag_delete(self.context, UUID1, tag)
images = self.db_api.image_get_all(self.context,
filters={
'tags': [tag],
})
self.assertEqual(0, len(images))
def test_image_get_all_with_filter_undefined_tags(self):
images = self.db_api.image_get_all(self.context,
filters={'tags': ['fake']})
self.assertEqual(0, len(images))
def test_image_paginate(self):
"""Paginate through a list of images using limit and marker"""
now = timeutils.utcnow()
extra_uuids = [(str(uuid.uuid4()),
now + datetime.timedelta(seconds=i * 5))
for i in range(2)]
extra_images = [build_image_fixture(id=_id,
created_at=_dt,
updated_at=_dt)
for _id, _dt in extra_uuids]
self.create_images(extra_images)
# Reverse uuids to match default sort of created_at
extra_uuids.reverse()
page = self.db_api.image_get_all(self.context, limit=2)
self.assertEqual([i[0] for i in extra_uuids], [i['id'] for i in page])
last = page[-1]['id']
page = self.db_api.image_get_all(self.context, limit=2, marker=last)
self.assertEqual([UUID3, UUID2], [i['id'] for i in page])
page = self.db_api.image_get_all(self.context, limit=2, marker=UUID2)
self.assertEqual([UUID1], [i['id'] for i in page])
def test_image_get_all_invalid_sort_key(self):
self.assertRaises(exception.InvalidSortKey, self.db_api.image_get_all,
self.context, sort_key=['blah'])
def test_image_get_all_limit_marker(self):
images = self.db_api.image_get_all(self.context, limit=2)
self.assertEqual(2, len(images))
def test_image_get_all_with_tag_returning(self):
expected_tags = {UUID1: ['foo'], UUID2: ['bar'], UUID3: ['baz']}
self.db_api.image_tag_create(self.context, UUID1,
expected_tags[UUID1][0])
self.db_api.image_tag_create(self.context, UUID2,
expected_tags[UUID2][0])
self.db_api.image_tag_create(self.context, UUID3,
expected_tags[UUID3][0])
images = self.db_api.image_get_all(self.context, return_tag=True)
self.assertEqual(3, len(images))
for image in images:
self.assertIn('tags', image)
self.assertEqual(expected_tags[image['id']], image['tags'])
self.db_api.image_tag_delete(self.context, UUID1,
expected_tags[UUID1][0])
expected_tags[UUID1] = []
images = self.db_api.image_get_all(self.context, return_tag=True)
self.assertEqual(3, len(images))
for image in images:
self.assertIn('tags', image)
self.assertEqual(expected_tags[image['id']], image['tags'])
def test_image_destroy(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'},
{'url': 'b', 'metadata': {},
'status': 'active'}]
fixture = {'status': 'queued', 'locations': location_data}
image = self.db_api.image_create(self.context, fixture)
IMG_ID = image['id']
fixture = {'name': 'ping', 'value': 'pong', 'image_id': IMG_ID}
prop = self.db_api.image_property_create(self.context, fixture)
TENANT2 = str(uuid.uuid4())
fixture = {'image_id': IMG_ID, 'member': TENANT2, 'can_share': False}
member = self.db_api.image_member_create(self.context, fixture)
self.db_api.image_tag_create(self.context, IMG_ID, 'snarf')
self.assertEqual(2, len(image['locations']))
self.assertIn('id', image['locations'][0])
self.assertIn('id', image['locations'][1])
image['locations'][0].pop('id')
image['locations'][1].pop('id')
self.assertEqual(location_data, image['locations'])
self.assertEqual(('ping', 'pong', IMG_ID, False),
(prop['name'], prop['value'],
prop['image_id'], prop['deleted']))
self.assertEqual((TENANT2, IMG_ID, False),
(member['member'], member['image_id'],
member['can_share']))
self.assertEqual(['snarf'],
self.db_api.image_tag_get_all(self.context, IMG_ID))
image = self.db_api.image_destroy(self.adm_context, IMG_ID)
self.assertTrue(image['deleted'])
self.assertTrue(image['deleted_at'])
self.assertRaises(exception.NotFound, self.db_api.image_get,
self.context, IMG_ID)
self.assertEqual([], image['locations'])
prop = image['properties'][0]
self.assertEqual(('ping', IMG_ID, True),
(prop['name'], prop['image_id'], prop['deleted']))
self.context.auth_token = 'user:%s:user' % TENANT2
members = self.db_api.image_member_find(self.context, IMG_ID)
self.assertEqual([], members)
tags = self.db_api.image_tag_get_all(self.context, IMG_ID)
self.assertEqual([], tags)
def test_image_destroy_with_delete_all(self):
"""Check the image child element's _image_delete_all methods.
checks if all the image_delete_all methods deletes only the child
elements of the image to be deleted.
"""
TENANT2 = str(uuid.uuid4())
location_data = [{'url': 'a', 'metadata': {'key': 'value'},
'status': 'active'},
{'url': 'b', 'metadata': {}, 'status': 'active'}]
def _create_image_with_child_entries():
fixture = {'status': 'queued', 'locations': location_data}
image_id = self.db_api.image_create(self.context, fixture)['id']
fixture = {'name': 'ping', 'value': 'pong', 'image_id': image_id}
self.db_api.image_property_create(self.context, fixture)
fixture = {'image_id': image_id, 'member': TENANT2,
'can_share': False}
self.db_api.image_member_create(self.context, fixture)
self.db_api.image_tag_create(self.context, image_id, 'snarf')
return image_id
ACTIVE_IMG_ID = _create_image_with_child_entries()
DEL_IMG_ID = _create_image_with_child_entries()
deleted_image = self.db_api.image_destroy(self.adm_context, DEL_IMG_ID)
self.assertTrue(deleted_image['deleted'])
self.assertTrue(deleted_image['deleted_at'])
self.assertRaises(exception.NotFound, self.db_api.image_get,
self.context, DEL_IMG_ID)
active_image = self.db_api.image_get(self.context, ACTIVE_IMG_ID)
self.assertFalse(active_image['deleted'])
self.assertFalse(active_image['deleted_at'])
self.assertEqual(2, len(active_image['locations']))
self.assertIn('id', active_image['locations'][0])
self.assertIn('id', active_image['locations'][1])
active_image['locations'][0].pop('id')
active_image['locations'][1].pop('id')
self.assertEqual(location_data, active_image['locations'])
self.assertEqual(1, len(active_image['properties']))
prop = active_image['properties'][0]
self.assertEqual(('ping', 'pong', ACTIVE_IMG_ID),
(prop['name'], prop['value'],
prop['image_id']))
self.assertEqual((False, None),
(prop['deleted'], prop['deleted_at']))
self.context.auth_token = 'user:%s:user' % TENANT2
members = self.db_api.image_member_find(self.context, ACTIVE_IMG_ID)
self.assertEqual(1, len(members))
member = members[0]
self.assertEqual((TENANT2, ACTIVE_IMG_ID, False),
(member['member'], member['image_id'],
member['can_share']))
tags = self.db_api.image_tag_get_all(self.context, ACTIVE_IMG_ID)
self.assertEqual(['snarf'], tags)
def test_image_get_multiple_members(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=False, user=TENANT2,
auth_token='user:%s:user' % TENANT2,
owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need a shared image and context.owner should not match image
# owner
self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
values = {'image_id': UUIDX, 'member': TENANT2, 'can_share': False}
self.db_api.image_member_create(ctxt1, values)
image = self.db_api.image_get(ctxt2, UUIDX)
self.assertEqual(UUIDX, image['id'])
# by default get_all displays only images with status 'accepted'
images = self.db_api.image_get_all(ctxt2)
self.assertEqual(3, len(images))
# filter by rejected
images = self.db_api.image_get_all(ctxt2, member_status='rejected')
self.assertEqual(3, len(images))
# filter by visibility
images = self.db_api.image_get_all(ctxt2,
filters={'visibility': 'shared'})
self.assertEqual(0, len(images))
# filter by visibility
images = self.db_api.image_get_all(ctxt2, member_status='pending',
filters={'visibility': 'shared'})
self.assertEqual(1, len(images))
# filter by visibility
images = self.db_api.image_get_all(ctxt2, member_status='all',
filters={'visibility': 'shared'})
self.assertEqual(1, len(images))
# filter by status pending
images = self.db_api.image_get_all(ctxt2, member_status='pending')
self.assertEqual(4, len(images))
# filter by status all
images = self.db_api.image_get_all(ctxt2, member_status='all')
self.assertEqual(4, len(images))
def test_is_image_visible(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=False, user=TENANT2,
auth_token='user:%s:user' % TENANT2,
owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need a shared image and context.owner should not match image
# owner
image = self.db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
values = {'image_id': UUIDX, 'member': TENANT2, 'can_share': False}
self.db_api.image_member_create(ctxt1, values)
result = self.db_api.is_image_visible(ctxt2, image)
self.assertTrue(result)
# image should not be visible for a deleted member
members = self.db_api.image_member_find(ctxt1, image_id=UUIDX)
self.db_api.image_member_delete(ctxt1, members[0]['id'])
result = self.db_api.is_image_visible(ctxt2, image)
self.assertFalse(result)
def test_is_community_image_visible(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
owners_ctxt = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user'
% TENANT1, owner_is_tenant=True)
viewing_ctxt = context.RequestContext(is_admin=False, user=TENANT2,
auth_token='user:%s:user'
% TENANT2, owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need a community image and context.owner should not match image
# owner
image = self.db_api.image_create(owners_ctxt,
{'id': UUIDX,
'status': 'queued',
'visibility': 'community',
'owner': TENANT1})
# image should be visible in every context
result = self.db_api.is_image_visible(owners_ctxt, image)
self.assertTrue(result)
result = self.db_api.is_image_visible(viewing_ctxt, image)
self.assertTrue(result)
def test_image_tag_create(self):
tag = self.db_api.image_tag_create(self.context, UUID1, 'snap')
self.assertEqual('snap', tag)
def test_image_tag_create_bad_value(self):
self.assertRaises(exception.Invalid,
self.db_api.image_tag_create, self.context,
UUID1, u'Bad \U0001f62a')
def test_image_tag_set_all(self):
tags = self.db_api.image_tag_get_all(self.context, UUID1)
self.assertEqual([], tags)
self.db_api.image_tag_set_all(self.context, UUID1, ['ping', 'pong'])
tags = self.db_api.image_tag_get_all(self.context, UUID1)
# NOTE(bcwaldon): tag ordering should match exactly what was provided
self.assertEqual(['ping', 'pong'], tags)
def test_image_tag_get_all(self):
self.db_api.image_tag_create(self.context, UUID1, 'snap')
self.db_api.image_tag_create(self.context, UUID1, 'snarf')
self.db_api.image_tag_create(self.context, UUID2, 'snarf')
# Check the tags for the first image
tags = self.db_api.image_tag_get_all(self.context, UUID1)
expected = ['snap', 'snarf']
self.assertEqual(expected, tags)
# Check the tags for the second image
tags = self.db_api.image_tag_get_all(self.context, UUID2)
expected = ['snarf']
self.assertEqual(expected, tags)
def test_image_tag_get_all_no_tags(self):
actual = self.db_api.image_tag_get_all(self.context, UUID1)
self.assertEqual([], actual)
def test_image_tag_get_all_non_existent_image(self):
bad_image_id = str(uuid.uuid4())
actual = self.db_api.image_tag_get_all(self.context, bad_image_id)
self.assertEqual([], actual)
def test_image_tag_delete(self):
self.db_api.image_tag_create(self.context, UUID1, 'snap')
self.db_api.image_tag_delete(self.context, UUID1, 'snap')
self.assertRaises(exception.NotFound, self.db_api.image_tag_delete,
self.context, UUID1, 'snap')
@mock.patch.object(timeutils, 'utcnow')
def test_image_member_create(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime.utcnow()
memberships = self.db_api.image_member_find(self.context)
self.assertEqual([], memberships)
TENANT1 = str(uuid.uuid4())
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT1
self.db_api.image_member_create(self.context,
{'member': TENANT1, 'image_id': UUID1})
memberships = self.db_api.image_member_find(self.context)
self.assertEqual(1, len(memberships))
actual = memberships[0]
self.assertIsNotNone(actual['created_at'])
self.assertIsNotNone(actual['updated_at'])
actual.pop('id')
actual.pop('created_at')
actual.pop('updated_at')
expected = {
'member': TENANT1,
'image_id': UUID1,
'can_share': False,
'status': 'pending',
'deleted': False,
}
self.assertEqual(expected, actual)
def test_image_member_update(self):
TENANT1 = str(uuid.uuid4())
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT1
member = self.db_api.image_member_create(self.context,
{'member': TENANT1,
'image_id': UUID1})
member_id = member.pop('id')
member.pop('created_at')
member.pop('updated_at')
expected = {'member': TENANT1,
'image_id': UUID1,
'status': 'pending',
'can_share': False,
'deleted': False}
self.assertEqual(expected, member)
member = self.db_api.image_member_update(self.context,
member_id,
{'can_share': True})
self.assertNotEqual(member['created_at'], member['updated_at'])
member.pop('id')
member.pop('created_at')
member.pop('updated_at')
expected = {'member': TENANT1,
'image_id': UUID1,
'status': 'pending',
'can_share': True,
'deleted': False}
self.assertEqual(expected, member)
members = self.db_api.image_member_find(self.context,
member=TENANT1,
image_id=UUID1)
member = members[0]
member.pop('id')
member.pop('created_at')
member.pop('updated_at')
self.assertEqual(expected, member)
def test_image_member_update_status(self):
TENANT1 = str(uuid.uuid4())
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT1
member = self.db_api.image_member_create(self.context,
{'member': TENANT1,
'image_id': UUID1})
member_id = member.pop('id')
member.pop('created_at')
member.pop('updated_at')
expected = {'member': TENANT1,
'image_id': UUID1,
'status': 'pending',
'can_share': False,
'deleted': False}
self.assertEqual(expected, member)
member = self.db_api.image_member_update(self.context,
member_id,
{'status': 'accepted'})
self.assertNotEqual(member['created_at'], member['updated_at'])
member.pop('id')
member.pop('created_at')
member.pop('updated_at')
expected = {'member': TENANT1,
'image_id': UUID1,
'status': 'accepted',
'can_share': False,
'deleted': False}
self.assertEqual(expected, member)
members = self.db_api.image_member_find(self.context,
member=TENANT1,
image_id=UUID1)
member = members[0]
member.pop('id')
member.pop('created_at')
member.pop('updated_at')
self.assertEqual(expected, member)
def test_image_member_find(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
fixtures = [
{'member': TENANT1, 'image_id': UUID1},
{'member': TENANT1, 'image_id': UUID2, 'status': 'rejected'},
{'member': TENANT2, 'image_id': UUID1, 'status': 'accepted'},
]
for f in fixtures:
self.db_api.image_member_create(self.context, copy.deepcopy(f))
def _simplify(output):
return
def _assertMemberListMatch(list1, list2):
_simple = lambda x: set([(o['member'], o['image_id']) for o in x])
self.assertEqual(_simple(list1), _simple(list2))
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT1
output = self.db_api.image_member_find(self.context, member=TENANT1)
_assertMemberListMatch([fixtures[0], fixtures[1]], output)
output = self.db_api.image_member_find(self.adm_context,
image_id=UUID1)
_assertMemberListMatch([fixtures[0], fixtures[2]], output)
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT2
output = self.db_api.image_member_find(self.context,
member=TENANT2,
image_id=UUID1)
_assertMemberListMatch([fixtures[2]], output)
output = self.db_api.image_member_find(self.context,
status='accepted')
_assertMemberListMatch([fixtures[2]], output)
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT1
output = self.db_api.image_member_find(self.context,
status='rejected')
_assertMemberListMatch([fixtures[1]], output)
output = self.db_api.image_member_find(self.context,
status='pending')
_assertMemberListMatch([fixtures[0]], output)
output = self.db_api.image_member_find(self.context,
status='pending',
image_id=UUID2)
_assertMemberListMatch([], output)
image_id = str(uuid.uuid4())
output = self.db_api.image_member_find(self.context,
member=TENANT2,
image_id=image_id)
_assertMemberListMatch([], output)
def test_image_member_count(self):
TENANT1 = str(uuid.uuid4())
self.db_api.image_member_create(self.context,
{'member': TENANT1,
'image_id': UUID1})
actual = self.db_api.image_member_count(self.context, UUID1)
self.assertEqual(1, actual)
def test_image_member_count_invalid_image_id(self):
TENANT1 = str(uuid.uuid4())
self.db_api.image_member_create(self.context,
{'member': TENANT1,
'image_id': UUID1})
self.assertRaises(exception.Invalid, self.db_api.image_member_count,
self.context, None)
def test_image_member_count_empty_image_id(self):
TENANT1 = str(uuid.uuid4())
self.db_api.image_member_create(self.context,
{'member': TENANT1,
'image_id': UUID1})
self.assertRaises(exception.Invalid, self.db_api.image_member_count,
self.context, "")
def test_image_member_delete(self):
TENANT1 = str(uuid.uuid4())
# NOTE(flaper87): Update auth token, otherwise
# non visible members won't be returned.
self.context.auth_token = 'user:%s:user' % TENANT1
fixture = {'member': TENANT1, 'image_id': UUID1, 'can_share': True}
member = self.db_api.image_member_create(self.context, fixture)
self.assertEqual(1, len(self.db_api.image_member_find(self.context)))
member = self.db_api.image_member_delete(self.context, member['id'])
self.assertEqual(0, len(self.db_api.image_member_find(self.context)))
class DriverQuotaTests(test_utils.BaseTestCase):
def setUp(self):
super(DriverQuotaTests, self).setUp()
self.owner_id1 = str(uuid.uuid4())
self.context1 = context.RequestContext(
is_admin=False, user=self.owner_id1, tenant=self.owner_id1,
auth_token='%s:%s:user' % (self.owner_id1, self.owner_id1))
self.db_api = db_tests.get_db(self.config)
db_tests.reset_db(self.db_api)
dt1 = timeutils.utcnow()
dt2 = dt1 + datetime.timedelta(microseconds=5)
fixtures = [
{
'id': UUID1,
'created_at': dt1,
'updated_at': dt1,
'size': 13,
'owner': self.owner_id1,
},
{
'id': UUID2,
'created_at': dt1,
'updated_at': dt2,
'size': 17,
'owner': self.owner_id1,
},
{
'id': UUID3,
'created_at': dt2,
'updated_at': dt2,
'size': 7,
'owner': self.owner_id1,
},
]
self.owner1_fixtures = [
build_image_fixture(**fixture) for fixture in fixtures]
for fixture in self.owner1_fixtures:
self.db_api.image_create(self.context1, fixture)
def test_storage_quota(self):
total = reduce(lambda x, y: x + y,
[f['size'] for f in self.owner1_fixtures])
x = self.db_api.user_get_storage_usage(self.context1, self.owner_id1)
self.assertEqual(total, x)
def test_storage_quota_without_image_id(self):
total = reduce(lambda x, y: x + y,
[f['size'] for f in self.owner1_fixtures])
total = total - self.owner1_fixtures[0]['size']
x = self.db_api.user_get_storage_usage(
self.context1, self.owner_id1,
image_id=self.owner1_fixtures[0]['id'])
self.assertEqual(total, x)
def test_storage_quota_multiple_locations(self):
dt1 = timeutils.utcnow()
sz = 53
new_fixture_dict = {'id': str(uuid.uuid4()), 'created_at': dt1,
'updated_at': dt1, 'size': sz,
'owner': self.owner_id1}
new_fixture = build_image_fixture(**new_fixture_dict)
new_fixture['locations'].append({'url': 'file:///some/path/file',
'metadata': {},
'status': 'active'})
self.db_api.image_create(self.context1, new_fixture)
total = reduce(lambda x, y: x + y,
[f['size'] for f in self.owner1_fixtures]) + (sz * 2)
x = self.db_api.user_get_storage_usage(self.context1, self.owner_id1)
self.assertEqual(total, x)
def test_storage_quota_deleted_image(self):
# NOTE(flaper87): This needs to be tested for
# soft deleted images as well. Currently there's no
# good way to delete locations.
dt1 = timeutils.utcnow()
sz = 53
image_id = str(uuid.uuid4())
new_fixture_dict = {'id': image_id, 'created_at': dt1,
'updated_at': dt1, 'size': sz,
'owner': self.owner_id1}
new_fixture = build_image_fixture(**new_fixture_dict)
new_fixture['locations'].append({'url': 'file:///some/path/file',
'metadata': {},
'status': 'active'})
self.db_api.image_create(self.context1, new_fixture)
total = reduce(lambda x, y: x + y,
[f['size'] for f in self.owner1_fixtures])
x = self.db_api.user_get_storage_usage(self.context1, self.owner_id1)
self.assertEqual(total + (sz * 2), x)
self.db_api.image_destroy(self.context1, image_id)
x = self.db_api.user_get_storage_usage(self.context1, self.owner_id1)
self.assertEqual(total, x)
class TaskTests(test_utils.BaseTestCase):
def setUp(self):
super(TaskTests, self).setUp()
self.admin_id = 'admin'
self.owner_id = 'user'
self.adm_context = context.RequestContext(
is_admin=True, auth_token='user:admin:admin', tenant=self.admin_id)
self.context = context.RequestContext(
is_admin=False, auth_token='user:user:user', user=self.owner_id)
self.db_api = db_tests.get_db(self.config)
self.fixtures = self.build_task_fixtures()
db_tests.reset_db(self.db_api)
def build_task_fixtures(self):
self.context.tenant = str(uuid.uuid4())
fixtures = [
{
'owner': self.context.owner,
'type': 'import',
'input': {'import_from': 'file:///a.img',
'import_from_format': 'qcow2',
'image_properties': {
"name": "GreatStack 1.22",
"tags": ["lamp", "custom"]
}},
},
{
'owner': self.context.owner,
'type': 'import',
'input': {'import_from': 'file:///b.img',
'import_from_format': 'qcow2',
'image_properties': {
"name": "GreatStack 1.23",
"tags": ["lamp", "good"]
}},
},
{
'owner': self.context.owner,
"type": "export",
"input": {
"export_uuid": "deadbeef-dead-dead-dead-beefbeefbeef",
"export_to":
"swift://cloud.foo/myaccount/mycontainer/path",
"export_format": "qcow2"
}
},
]
return [build_task_fixture(**fixture) for fixture in fixtures]
def test_task_get_all_with_filter(self):
for fixture in self.fixtures:
self.db_api.task_create(self.adm_context,
build_task_fixture(**fixture))
import_tasks = self.db_api.task_get_all(self.adm_context,
filters={'type': 'import'})
self.assertTrue(import_tasks)
self.assertEqual(2, len(import_tasks))
for task in import_tasks:
self.assertEqual('import', task['type'])
self.assertEqual(self.context.owner, task['owner'])
def test_task_get_all_as_admin(self):
tasks = []
for fixture in self.fixtures:
task = self.db_api.task_create(self.adm_context,
build_task_fixture(**fixture))
tasks.append(task)
import_tasks = self.db_api.task_get_all(self.adm_context)
self.assertTrue(import_tasks)
self.assertEqual(3, len(import_tasks))
def test_task_get_all_marker(self):
for fixture in self.fixtures:
self.db_api.task_create(self.adm_context,
build_task_fixture(**fixture))
tasks = self.db_api.task_get_all(self.adm_context, sort_key='id')
task_ids = [t['id'] for t in tasks]
tasks = self.db_api.task_get_all(self.adm_context, sort_key='id',
marker=task_ids[0])
self.assertEqual(2, len(tasks))
def test_task_get_all_limit(self):
for fixture in self.fixtures:
self.db_api.task_create(self.adm_context,
build_task_fixture(**fixture))
tasks = self.db_api.task_get_all(self.adm_context, limit=2)
self.assertEqual(2, len(tasks))
# A limit of None should not equate to zero
tasks = self.db_api.task_get_all(self.adm_context, limit=None)
self.assertEqual(3, len(tasks))
# A limit of zero should actually mean zero
tasks = self.db_api.task_get_all(self.adm_context, limit=0)
self.assertEqual(0, len(tasks))
def test_task_get_all_owned(self):
then = timeutils.utcnow() + datetime.timedelta(days=365)
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False,
tenant=TENANT1,
auth_token='user:%s:user' % TENANT1)
task_values = {'type': 'import', 'status': 'pending',
'input': '{"loc": "fake"}', 'owner': TENANT1,
'expires_at': then}
self.db_api.task_create(ctxt1, task_values)
TENANT2 = str(uuid.uuid4())
ctxt2 = context.RequestContext(is_admin=False,
tenant=TENANT2,
auth_token='user:%s:user' % TENANT2)
task_values = {'type': 'export', 'status': 'pending',
'input': '{"loc": "fake"}', 'owner': TENANT2,
'expires_at': then}
self.db_api.task_create(ctxt2, task_values)
tasks = self.db_api.task_get_all(ctxt1)
task_owners = set([task['owner'] for task in tasks])
expected = set([TENANT1])
self.assertEqual(sorted(expected), sorted(task_owners))
def test_task_get(self):
expires_at = timeutils.utcnow()
image_id = str(uuid.uuid4())
fixture = {
'owner': self.context.owner,
'type': 'import',
'status': 'pending',
'input': '{"loc": "fake"}',
'result': "{'image_id': %s}" % image_id,
'message': 'blah',
'expires_at': expires_at
}
task = self.db_api.task_create(self.adm_context, fixture)
self.assertIsNotNone(task)
self.assertIsNotNone(task['id'])
task_id = task['id']
task = self.db_api.task_get(self.adm_context, task_id)
self.assertIsNotNone(task)
self.assertEqual(task_id, task['id'])
self.assertEqual(self.context.owner, task['owner'])
self.assertEqual('import', task['type'])
self.assertEqual('pending', task['status'])
self.assertEqual(fixture['input'], task['input'])
self.assertEqual(fixture['result'], task['result'])
self.assertEqual(fixture['message'], task['message'])
self.assertEqual(expires_at, task['expires_at'])
def test_task_get_all(self):
now = timeutils.utcnow()
then = now + datetime.timedelta(days=365)
image_id = str(uuid.uuid4())
fixture1 = {
'owner': self.context.owner,
'type': 'import',
'status': 'pending',
'input': '{"loc": "fake_1"}',
'result': "{'image_id': %s}" % image_id,
'message': 'blah_1',
'expires_at': then,
'created_at': now,
'updated_at': now
}
fixture2 = {
'owner': self.context.owner,
'type': 'import',
'status': 'pending',
'input': '{"loc": "fake_2"}',
'result': "{'image_id': %s}" % image_id,
'message': 'blah_2',
'expires_at': then,
'created_at': now,
'updated_at': now
}
task1 = self.db_api.task_create(self.adm_context, fixture1)
task2 = self.db_api.task_create(self.adm_context, fixture2)
self.assertIsNotNone(task1)
self.assertIsNotNone(task2)
task1_id = task1['id']
task2_id = task2['id']
task_fixtures = {task1_id: fixture1, task2_id: fixture2}
tasks = self.db_api.task_get_all(self.adm_context)
self.assertEqual(2, len(tasks))
self.assertEqual(set((tasks[0]['id'], tasks[1]['id'])),
set((task1_id, task2_id)))
for task in tasks:
fixture = task_fixtures[task['id']]
self.assertEqual(self.context.owner, task['owner'])
self.assertEqual(fixture['type'], task['type'])
self.assertEqual(fixture['status'], task['status'])
self.assertEqual(fixture['expires_at'], task['expires_at'])
self.assertFalse(task['deleted'])
self.assertIsNone(task['deleted_at'])
self.assertEqual(fixture['created_at'], task['created_at'])
self.assertEqual(fixture['updated_at'], task['updated_at'])
task_details_keys = ['input', 'message', 'result']
for key in task_details_keys:
self.assertNotIn(key, task)
def test_task_soft_delete(self):
now = timeutils.utcnow()
then = now + datetime.timedelta(days=365)
fixture1 = build_task_fixture(id='1', expires_at=now,
owner=self.adm_context.owner)
fixture2 = build_task_fixture(id='2', expires_at=now,
owner=self.adm_context.owner)
fixture3 = build_task_fixture(id='3', expires_at=then,
owner=self.adm_context.owner)
fixture4 = build_task_fixture(id='4', expires_at=then,
owner=self.adm_context.owner)
task1 = self.db_api.task_create(self.adm_context, fixture1)
task2 = self.db_api.task_create(self.adm_context, fixture2)
task3 = self.db_api.task_create(self.adm_context, fixture3)
task4 = self.db_api.task_create(self.adm_context, fixture4)
self.assertIsNotNone(task1)
self.assertIsNotNone(task2)
self.assertIsNotNone(task3)
self.assertIsNotNone(task4)
tasks = self.db_api.task_get_all(
self.adm_context, sort_key='id', sort_dir='asc')
self.assertEqual(4, len(tasks))
self.assertTrue(tasks[0]['deleted'])
self.assertTrue(tasks[1]['deleted'])
self.assertFalse(tasks[2]['deleted'])
self.assertFalse(tasks[3]['deleted'])
def test_task_create(self):
task_id = str(uuid.uuid4())
self.context.tenant = self.context.owner
values = {
'id': task_id,
'owner': self.context.owner,
'type': 'export',
'status': 'pending',
}
task_values = build_task_fixture(**values)
task = self.db_api.task_create(self.adm_context, task_values)
self.assertIsNotNone(task)
self.assertEqual(task_id, task['id'])
self.assertEqual(self.context.owner, task['owner'])
self.assertEqual('export', task['type'])
self.assertEqual('pending', task['status'])
self.assertEqual({'ping': 'pong'}, task['input'])
def test_task_create_with_all_task_info_null(self):
task_id = str(uuid.uuid4())
self.context.tenant = str(uuid.uuid4())
values = {
'id': task_id,
'owner': self.context.owner,
'type': 'export',
'status': 'pending',
'input': None,
'result': None,
'message': None,
}
task_values = build_task_fixture(**values)
task = self.db_api.task_create(self.adm_context, task_values)
self.assertIsNotNone(task)
self.assertEqual(task_id, task['id'])
self.assertEqual(self.context.owner, task['owner'])
self.assertEqual('export', task['type'])
self.assertEqual('pending', task['status'])
self.assertIsNone(task['input'])
self.assertIsNone(task['result'])
self.assertIsNone(task['message'])
def test_task_update(self):
self.context.tenant = str(uuid.uuid4())
result = {'foo': 'bar'}
task_values = build_task_fixture(owner=self.context.owner,
result=result)
task = self.db_api.task_create(self.adm_context, task_values)
task_id = task['id']
fixture = {
'status': 'processing',
'message': 'This is a error string',
}
task = self.db_api.task_update(self.adm_context, task_id, fixture)
self.assertEqual(task_id, task['id'])
self.assertEqual(self.context.owner, task['owner'])
self.assertEqual('import', task['type'])
self.assertEqual('processing', task['status'])
self.assertEqual({'ping': 'pong'}, task['input'])
self.assertEqual(result, task['result'])
self.assertEqual('This is a error string', task['message'])
self.assertFalse(task['deleted'])
self.assertIsNone(task['deleted_at'])
self.assertIsNone(task['expires_at'])
self.assertEqual(task_values['created_at'], task['created_at'])
self.assertGreater(task['updated_at'], task['created_at'])
def test_task_update_with_all_task_info_null(self):
self.context.tenant = str(uuid.uuid4())
task_values = build_task_fixture(owner=self.context.owner,
input=None,
result=None,
message=None)
task = self.db_api.task_create(self.adm_context, task_values)
task_id = task['id']
fixture = {'status': 'processing'}
task = self.db_api.task_update(self.adm_context, task_id, fixture)
self.assertEqual(task_id, task['id'])
self.assertEqual(self.context.owner, task['owner'])
self.assertEqual('import', task['type'])
self.assertEqual('processing', task['status'])
self.assertIsNone(task['input'])
self.assertIsNone(task['result'])
self.assertIsNone(task['message'])
self.assertFalse(task['deleted'])
self.assertIsNone(task['deleted_at'])
self.assertIsNone(task['expires_at'])
self.assertEqual(task_values['created_at'], task['created_at'])
self.assertGreater(task['updated_at'], task['created_at'])
def test_task_delete(self):
task_values = build_task_fixture(owner=self.context.owner)
task = self.db_api.task_create(self.adm_context, task_values)
self.assertIsNotNone(task)
self.assertFalse(task['deleted'])
self.assertIsNone(task['deleted_at'])
task_id = task['id']
self.db_api.task_delete(self.adm_context, task_id)
self.assertRaises(exception.TaskNotFound, self.db_api.task_get,
self.context, task_id)
def test_task_delete_as_admin(self):
task_values = build_task_fixture(owner=self.context.owner)
task = self.db_api.task_create(self.adm_context, task_values)
self.assertIsNotNone(task)
self.assertFalse(task['deleted'])
self.assertIsNone(task['deleted_at'])
task_id = task['id']
self.db_api.task_delete(self.adm_context, task_id)
del_task = self.db_api.task_get(self.adm_context,
task_id,
force_show_deleted=True)
self.assertIsNotNone(del_task)
self.assertEqual(task_id, del_task['id'])
self.assertTrue(del_task['deleted'])
self.assertIsNotNone(del_task['deleted_at'])
class DBPurgeTests(test_utils.BaseTestCase):
def setUp(self):
super(DBPurgeTests, self).setUp()
self.adm_context = context.get_admin_context(show_deleted=True)
self.db_api = db_tests.get_db(self.config)
db_tests.reset_db(self.db_api)
self.image_fixtures, self.task_fixtures = self.build_fixtures()
self.create_tasks(self.task_fixtures)
self.create_images(self.image_fixtures)
def build_fixtures(self):
dt1 = timeutils.utcnow() - datetime.timedelta(days=5)
dt2 = dt1 + datetime.timedelta(days=1)
dt3 = dt2 + datetime.timedelta(days=1)
fixtures = [
{
'created_at': dt1,
'updated_at': dt1,
'deleted_at': dt3,
'deleted': True,
},
{
'created_at': dt1,
'updated_at': dt2,
'deleted_at': timeutils.utcnow(),
'deleted': True,
},
{
'created_at': dt2,
'updated_at': dt2,
'deleted_at': None,
'deleted': False,
},
]
return (
[build_image_fixture(**fixture) for fixture in fixtures],
[build_task_fixture(**fixture) for fixture in fixtures],
)
def create_images(self, images):
for fixture in images:
self.db_api.image_create(self.adm_context, fixture)
def create_tasks(self, tasks):
for fixture in tasks:
self.db_api.task_create(self.adm_context, fixture)
def test_db_purge(self):
self.db_api.purge_deleted_rows(self.adm_context, 1, 5)
images = self.db_api.image_get_all(self.adm_context)
self.assertEqual(len(images), 2)
tasks = self.db_api.task_get_all(self.adm_context)
self.assertEqual(len(tasks), 2)
def test_purge_fk_constraint_failure(self):
"""Test foreign key constraint failure
Test whether foreign key constraint failure during purge
operation is raising DBReferenceError or not.
"""
session = db_api.get_session()
engine = db_api.get_engine()
connection = engine.connect()
dialect = engine.url.get_dialect()
if dialect == sqlite.dialect:
# We're seeing issues with foreign key support in SQLite 3.6.20
# SQLAlchemy doesn't support it at all with SQLite < 3.6.19
# It works fine in SQLite 3.7.
# So return early to skip this test if running SQLite < 3.7
if test_utils.is_sqlite_version_prior_to(3, 7):
self.skipTest(
'sqlite version too old for reliable SQLA foreign_keys')
# This is required for enforcing Foreign Key Constraint
# in SQLite 3.x
connection.execute("PRAGMA foreign_keys = ON")
images = sqlalchemyutils.get_table(
engine, "images")
image_tags = sqlalchemyutils.get_table(
engine, "image_tags")
# Add a 4th row in images table and set it deleted 15 days ago
uuidstr = uuid.uuid4().hex
created_time = timeutils.utcnow() - datetime.timedelta(days=20)
deleted_time = created_time + datetime.timedelta(days=5)
images_row_fixture = {
'id': uuidstr,
'status': 'status',
'created_at': created_time,
'deleted_at': deleted_time,
'deleted': 1,
'visibility': 'public',
'min_disk': 1,
'min_ram': 1,
'protected': 0
}
ins_stmt = images.insert().values(**images_row_fixture)
connection.execute(ins_stmt)
# Add a record in image_tags referencing the above images record
# but do not set it as deleted
image_tags_row_fixture = {
'image_id': uuidstr,
'value': 'tag_value',
'created_at': created_time,
'deleted': 0
}
ins_stmt = image_tags.insert().values(**image_tags_row_fixture)
connection.execute(ins_stmt)
# Purge all records deleted at least 10 days ago
self.assertRaises(db_exception.DBReferenceError,
db_api.purge_deleted_rows,
self.adm_context,
age_in_days=10,
max_rows=50)
# Verify that no records from images have been deleted
# due to DBReferenceError being raised
images_rows = session.query(images).count()
self.assertEqual(4, images_rows)
class TestVisibility(test_utils.BaseTestCase):
def setUp(self):
super(TestVisibility, self).setUp()
self.db_api = db_tests.get_db(self.config)
db_tests.reset_db(self.db_api)
self.setup_tenants()
self.setup_contexts()
self.fixtures = self.build_image_fixtures()
self.create_images(self.fixtures)
def setup_tenants(self):
self.admin_tenant = str(uuid.uuid4())
self.tenant1 = str(uuid.uuid4())
self.tenant2 = str(uuid.uuid4())
def setup_contexts(self):
self.admin_context = context.RequestContext(
is_admin=True, tenant=self.admin_tenant)
self.admin_none_context = context.RequestContext(
is_admin=True, tenant=None)
self.tenant1_context = context.RequestContext(tenant=self.tenant1)
self.tenant2_context = context.RequestContext(tenant=self.tenant2)
self.none_context = context.RequestContext(tenant=None)
def build_image_fixtures(self):
fixtures = []
owners = {
'Unowned': None,
'Admin Tenant': self.admin_tenant,
'Tenant 1': self.tenant1,
'Tenant 2': self.tenant2,
}
visibilities = ['community', 'private', 'public', 'shared']
for owner_label, owner in owners.items():
for visibility in visibilities:
fixture = {
'name': '%s, %s' % (owner_label, visibility),
'owner': owner,
'visibility': visibility,
}
fixtures.append(fixture)
return [build_image_fixture(**f) for f in fixtures]
def create_images(self, images):
for fixture in images:
self.db_api.image_create(self.admin_context, fixture)
class VisibilityTests(object):
def test_unknown_admin_sees_all_but_community(self):
images = self.db_api.image_get_all(self.admin_none_context)
self.assertEqual(12, len(images))
def test_unknown_admin_is_public_true(self):
images = self.db_api.image_get_all(self.admin_none_context,
is_public=True)
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_unknown_admin_is_public_false(self):
images = self.db_api.image_get_all(self.admin_none_context,
is_public=False)
self.assertEqual(8, len(images))
for i in images:
self.assertTrue(i['visibility'] in ['shared', 'private'])
def test_unknown_admin_is_public_none(self):
images = self.db_api.image_get_all(self.admin_none_context)
self.assertEqual(12, len(images))
def test_unknown_admin_visibility_public(self):
images = self.db_api.image_get_all(self.admin_none_context,
filters={'visibility': 'public'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_unknown_admin_visibility_shared(self):
images = self.db_api.image_get_all(self.admin_none_context,
filters={'visibility': 'shared'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('shared', i['visibility'])
def test_unknown_admin_visibility_private(self):
images = self.db_api.image_get_all(self.admin_none_context,
filters={'visibility': 'private'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('private', i['visibility'])
def test_unknown_admin_visibility_community(self):
images = self.db_api.image_get_all(self.admin_none_context,
filters={'visibility': 'community'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('community', i['visibility'])
def test_known_admin_sees_all_but_others_community_images(self):
images = self.db_api.image_get_all(self.admin_context)
self.assertEqual(13, len(images))
def test_known_admin_is_public_true(self):
images = self.db_api.image_get_all(self.admin_context, is_public=True)
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_known_admin_is_public_false(self):
images = self.db_api.image_get_all(self.admin_context,
is_public=False)
self.assertEqual(9, len(images))
for i in images:
self.assertTrue(i['visibility']
in ['shared', 'private', 'community'])
def test_known_admin_is_public_none(self):
images = self.db_api.image_get_all(self.admin_context)
self.assertEqual(13, len(images))
def test_admin_as_user_true(self):
images = self.db_api.image_get_all(self.admin_context,
admin_as_user=True)
self.assertEqual(7, len(images))
for i in images:
self.assertTrue(('public' == i['visibility'])
or i['owner'] == self.admin_tenant)
def test_known_admin_visibility_public(self):
images = self.db_api.image_get_all(self.admin_context,
filters={'visibility': 'public'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_known_admin_visibility_shared(self):
images = self.db_api.image_get_all(self.admin_context,
filters={'visibility': 'shared'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('shared', i['visibility'])
def test_known_admin_visibility_private(self):
images = self.db_api.image_get_all(self.admin_context,
filters={'visibility': 'private'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('private', i['visibility'])
def test_known_admin_visibility_community(self):
images = self.db_api.image_get_all(self.admin_context,
filters={'visibility': 'community'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('community', i['visibility'])
def test_what_unknown_user_sees(self):
images = self.db_api.image_get_all(self.none_context)
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_unknown_user_is_public_true(self):
images = self.db_api.image_get_all(self.none_context, is_public=True)
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_unknown_user_is_public_false(self):
images = self.db_api.image_get_all(self.none_context, is_public=False)
self.assertEqual(0, len(images))
def test_unknown_user_is_public_none(self):
images = self.db_api.image_get_all(self.none_context)
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_unknown_user_visibility_public(self):
images = self.db_api.image_get_all(self.none_context,
filters={'visibility': 'public'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_unknown_user_visibility_shared(self):
images = self.db_api.image_get_all(self.none_context,
filters={'visibility': 'shared'})
self.assertEqual(0, len(images))
def test_unknown_user_visibility_private(self):
images = self.db_api.image_get_all(self.none_context,
filters={'visibility': 'private'})
self.assertEqual(0, len(images))
def test_unknown_user_visibility_community(self):
images = self.db_api.image_get_all(self.none_context,
filters={'visibility': 'community'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('community', i['visibility'])
def test_what_tenant1_sees(self):
images = self.db_api.image_get_all(self.tenant1_context)
self.assertEqual(7, len(images))
for i in images:
if not ('public' == i['visibility']):
self.assertEqual(i['owner'], self.tenant1)
def test_tenant1_is_public_true(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=True)
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_tenant1_is_public_false(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=False)
self.assertEqual(3, len(images))
for i in images:
self.assertEqual(i['owner'], self.tenant1)
self.assertTrue(i['visibility']
in ['private', 'shared', 'community'])
def test_tenant1_is_public_none(self):
images = self.db_api.image_get_all(self.tenant1_context)
self.assertEqual(7, len(images))
for i in images:
if not ('public' == i['visibility']):
self.assertEqual(self.tenant1, i['owner'])
def test_tenant1_visibility_public(self):
images = self.db_api.image_get_all(self.tenant1_context,
filters={'visibility': 'public'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('public', i['visibility'])
def test_tenant1_visibility_shared(self):
images = self.db_api.image_get_all(self.tenant1_context,
filters={'visibility': 'shared'})
self.assertEqual(1, len(images))
self.assertEqual('shared', images[0]['visibility'])
self.assertEqual(self.tenant1, images[0]['owner'])
def test_tenant1_visibility_private(self):
images = self.db_api.image_get_all(self.tenant1_context,
filters={'visibility': 'private'})
self.assertEqual(1, len(images))
self.assertEqual('private', images[0]['visibility'])
self.assertEqual(self.tenant1, images[0]['owner'])
def test_tenant1_visibility_community(self):
images = self.db_api.image_get_all(self.tenant1_context,
filters={'visibility': 'community'})
self.assertEqual(4, len(images))
for i in images:
self.assertEqual('community', i['visibility'])
def _setup_is_public_red_herring(self):
values = {
'name': 'Red Herring',
'owner': self.tenant1,
'visibility': 'shared',
'properties': {'is_public': 'silly'}
}
fixture = build_image_fixture(**values)
self.db_api.image_create(self.admin_context, fixture)
def test_is_public_is_a_normal_filter_for_admin(self):
self._setup_is_public_red_herring()
images = self.db_api.image_get_all(self.admin_context,
filters={'is_public': 'silly'})
self.assertEqual(1, len(images))
self.assertEqual('Red Herring', images[0]['name'])
def test_is_public_is_a_normal_filter_for_user(self):
self._setup_is_public_red_herring()
images = self.db_api.image_get_all(self.tenant1_context,
filters={'is_public': 'silly'})
self.assertEqual(1, len(images))
self.assertEqual('Red Herring', images[0]['name'])
# NOTE(markwash): the following tests are sanity checks to make sure
# visibility filtering and is_public=(True|False) do not interact in
# unexpected ways. However, using both of the filtering techniques
# simultaneously is not an anticipated use case.
def test_admin_is_public_true_and_visibility_public(self):
images = self.db_api.image_get_all(self.admin_context, is_public=True,
filters={'visibility': 'public'})
self.assertEqual(4, len(images))
def test_admin_is_public_false_and_visibility_public(self):
images = self.db_api.image_get_all(self.admin_context, is_public=False,
filters={'visibility': 'public'})
self.assertEqual(0, len(images))
def test_admin_is_public_true_and_visibility_shared(self):
images = self.db_api.image_get_all(self.admin_context, is_public=True,
filters={'visibility': 'shared'})
self.assertEqual(0, len(images))
def test_admin_is_public_false_and_visibility_shared(self):
images = self.db_api.image_get_all(self.admin_context, is_public=False,
filters={'visibility': 'shared'})
self.assertEqual(4, len(images))
def test_admin_is_public_true_and_visibility_private(self):
images = self.db_api.image_get_all(self.admin_context, is_public=True,
filters={'visibility': 'private'})
self.assertEqual(0, len(images))
def test_admin_is_public_false_and_visibility_private(self):
images = self.db_api.image_get_all(self.admin_context, is_public=False,
filters={'visibility': 'private'})
self.assertEqual(4, len(images))
def test_admin_is_public_true_and_visibility_community(self):
images = self.db_api.image_get_all(self.admin_context, is_public=True,
filters={'visibility': 'community'})
self.assertEqual(0, len(images))
def test_admin_is_public_false_and_visibility_community(self):
images = self.db_api.image_get_all(self.admin_context, is_public=False,
filters={'visibility': 'community'})
self.assertEqual(4, len(images))
def test_tenant1_is_public_true_and_visibility_public(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=True,
filters={'visibility': 'public'})
self.assertEqual(4, len(images))
def test_tenant1_is_public_false_and_visibility_public(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=False,
filters={'visibility': 'public'})
self.assertEqual(0, len(images))
def test_tenant1_is_public_true_and_visibility_shared(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=True,
filters={'visibility': 'shared'})
self.assertEqual(0, len(images))
def test_tenant1_is_public_false_and_visibility_shared(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=False,
filters={'visibility': 'shared'})
self.assertEqual(1, len(images))
def test_tenant1_is_public_true_and_visibility_private(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=True,
filters={'visibility': 'private'})
self.assertEqual(0, len(images))
def test_tenant1_is_public_false_and_visibility_private(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=False,
filters={'visibility': 'private'})
self.assertEqual(1, len(images))
def test_tenant1_is_public_true_and_visibility_community(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=True,
filters={'visibility': 'community'})
self.assertEqual(0, len(images))
def test_tenant1_is_public_false_and_visibility_community(self):
images = self.db_api.image_get_all(self.tenant1_context,
is_public=False,
filters={'visibility': 'community'})
self.assertEqual(4, len(images))
class TestMembershipVisibility(test_utils.BaseTestCase):
def setUp(self):
super(TestMembershipVisibility, self).setUp()
self.db_api = db_tests.get_db(self.config)
db_tests.reset_db(self.db_api)
self._create_contexts()
self._create_images()
def _create_contexts(self):
self.owner1, self.owner1_ctx = self._user_fixture()
self.owner2, self.owner2_ctx = self._user_fixture()
self.tenant1, self.user1_ctx = self._user_fixture()
self.tenant2, self.user2_ctx = self._user_fixture()
self.tenant3, self.user3_ctx = self._user_fixture()
self.admin_tenant, self.admin_ctx = self._user_fixture(admin=True)
def _user_fixture(self, admin=False):
tenant_id = str(uuid.uuid4())
ctx = context.RequestContext(tenant=tenant_id, is_admin=admin)
return tenant_id, ctx
def _create_images(self):
self.image_ids = {}
for owner in [self.owner1, self.owner2]:
self._create_image('not_shared', owner)
self._create_image('shared-with-1', owner, members=[self.tenant1])
self._create_image('shared-with-2', owner, members=[self.tenant2])
self._create_image('shared-with-both', owner,
members=[self.tenant1, self.tenant2])
def _create_image(self, name, owner, members=None):
image = build_image_fixture(name=name, owner=owner,
visibility='shared')
self.image_ids[(owner, name)] = image['id']
self.db_api.image_create(self.admin_ctx, image)
for member in members or []:
member = {'image_id': image['id'], 'member': member}
self.db_api.image_member_create(self.admin_ctx, member)
class MembershipVisibilityTests(object):
def _check_by_member(self, ctx, member_id, expected):
members = self.db_api.image_member_find(ctx, member=member_id)
images = [self.db_api.image_get(self.admin_ctx, member['image_id'])
for member in members]
facets = [(image['owner'], image['name']) for image in images]
self.assertEqual(set(expected), set(facets))
def test_owner1_finding_user1_memberships(self):
"""Owner1 should see images it owns that are shared with User1."""
expected = [
(self.owner1, 'shared-with-1'),
(self.owner1, 'shared-with-both'),
]
self._check_by_member(self.owner1_ctx, self.tenant1, expected)
def test_user1_finding_user1_memberships(self):
"""User1 should see all images shared with User1 """
expected = [
(self.owner1, 'shared-with-1'),
(self.owner1, 'shared-with-both'),
(self.owner2, 'shared-with-1'),
(self.owner2, 'shared-with-both'),
]
self._check_by_member(self.user1_ctx, self.tenant1, expected)
def test_user2_finding_user1_memberships(self):
"""User2 should see no images shared with User1 """
expected = []
self._check_by_member(self.user2_ctx, self.tenant1, expected)
def test_admin_finding_user1_memberships(self):
"""Admin should see all images shared with User1 """
expected = [
(self.owner1, 'shared-with-1'),
(self.owner1, 'shared-with-both'),
(self.owner2, 'shared-with-1'),
(self.owner2, 'shared-with-both'),
]
self._check_by_member(self.admin_ctx, self.tenant1, expected)
def _check_by_image(self, context, image_id, expected):
members = self.db_api.image_member_find(context, image_id=image_id)
member_ids = [member['member'] for member in members]
self.assertEqual(set(expected), set(member_ids))
def test_owner1_finding_owner1s_image_members(self):
"""Owner1 should see all memberships of its image """
expected = [self.tenant1, self.tenant2]
image_id = self.image_ids[(self.owner1, 'shared-with-both')]
self._check_by_image(self.owner1_ctx, image_id, expected)
def test_admin_finding_owner1s_image_members(self):
"""Admin should see all memberships of owner1's image """
expected = [self.tenant1, self.tenant2]
image_id = self.image_ids[(self.owner1, 'shared-with-both')]
self._check_by_image(self.admin_ctx, image_id, expected)
def test_user1_finding_owner1s_image_members(self):
"""User1 should see its own membership of owner1's image """
expected = [self.tenant1]
image_id = self.image_ids[(self.owner1, 'shared-with-both')]
self._check_by_image(self.user1_ctx, image_id, expected)
def test_user2_finding_owner1s_image_members(self):
"""User2 should see its own membership of owner1's image """
expected = [self.tenant2]
image_id = self.image_ids[(self.owner1, 'shared-with-both')]
self._check_by_image(self.user2_ctx, image_id, expected)
def test_user3_finding_owner1s_image_members(self):
"""User3 should see no memberships of owner1's image """
expected = []
image_id = self.image_ids[(self.owner1, 'shared-with-both')]
self._check_by_image(self.user3_ctx, image_id, expected)
glance-16.0.1/glance/tests/functional/db/test_sqlalchemy.py 0000666 0001750 0001750 00000014232 13267672245 023771 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_db import options
from glance.common import exception
import glance.db.sqlalchemy.api
from glance.db.sqlalchemy import models as db_models
from glance.db.sqlalchemy import models_metadef as metadef_models
import glance.tests.functional.db as db_tests
from glance.tests.functional.db import base
from glance.tests.functional.db import base_metadef
CONF = cfg.CONF
def get_db(config):
options.set_defaults(CONF, connection='sqlite://')
config(debug=False)
db_api = glance.db.sqlalchemy.api
return db_api
def reset_db(db_api):
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def reset_db_metadef(db_api):
metadef_models.unregister_models(db_api.get_engine())
metadef_models.register_models(db_api.get_engine())
class TestSqlAlchemyDriver(base.TestDriver,
base.DriverTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSqlAlchemyDriver, self).setUp()
self.addCleanup(db_tests.reset)
def test_get_image_with_invalid_long_image_id(self):
image_id = '343f9ba5-0197-41be-9543-16bbb32e12aa-xxxxxx'
self.assertRaises(exception.NotFound, self.db_api._image_get,
self.context, image_id)
def test_image_tag_delete_with_invalid_long_image_id(self):
image_id = '343f9ba5-0197-41be-9543-16bbb32e12aa-xxxxxx'
self.assertRaises(exception.NotFound, self.db_api.image_tag_delete,
self.context, image_id, 'fake')
def test_image_tag_get_all_with_invalid_long_image_id(self):
image_id = '343f9ba5-0197-41be-9543-16bbb32e12aa-xxxxxx'
self.assertRaises(exception.NotFound, self.db_api.image_tag_get_all,
self.context, image_id)
def test_user_get_storage_usage_with_invalid_long_image_id(self):
image_id = '343f9ba5-0197-41be-9543-16bbb32e12aa-xxxxxx'
self.assertRaises(exception.NotFound,
self.db_api.user_get_storage_usage,
self.context, 'fake_owner_id', image_id)
class TestSqlAlchemyVisibility(base.TestVisibility,
base.VisibilityTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSqlAlchemyVisibility, self).setUp()
self.addCleanup(db_tests.reset)
class TestSqlAlchemyMembershipVisibility(base.TestMembershipVisibility,
base.MembershipVisibilityTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSqlAlchemyMembershipVisibility, self).setUp()
self.addCleanup(db_tests.reset)
class TestSqlAlchemyDBDataIntegrity(base.TestDriver,
base.FunctionalInitWrapper):
"""Test class for checking the data integrity in the database.
Helpful in testing scenarios specific to the sqlalchemy api.
"""
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSqlAlchemyDBDataIntegrity, self).setUp()
self.addCleanup(db_tests.reset)
def test_paginate_redundant_sort_keys(self):
original_method = self.db_api._paginate_query
def fake_paginate_query(query, model, limit,
sort_keys, marker, sort_dir, sort_dirs):
self.assertEqual(['created_at', 'id'], sort_keys)
return original_method(query, model, limit,
sort_keys, marker, sort_dir, sort_dirs)
self.stubs.Set(self.db_api, '_paginate_query',
fake_paginate_query)
self.db_api.image_get_all(self.context, sort_key=['created_at'])
def test_paginate_non_redundant_sort_keys(self):
original_method = self.db_api._paginate_query
def fake_paginate_query(query, model, limit,
sort_keys, marker, sort_dir, sort_dirs):
self.assertEqual(['name', 'created_at', 'id'], sort_keys)
return original_method(query, model, limit,
sort_keys, marker, sort_dir, sort_dirs)
self.stubs.Set(self.db_api, '_paginate_query',
fake_paginate_query)
self.db_api.image_get_all(self.context, sort_key=['name'])
class TestSqlAlchemyTask(base.TaskTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSqlAlchemyTask, self).setUp()
self.addCleanup(db_tests.reset)
class TestSqlAlchemyQuota(base.DriverQuotaTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSqlAlchemyQuota, self).setUp()
self.addCleanup(db_tests.reset)
class TestDBPurge(base.DBPurgeTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestDBPurge, self).setUp()
self.addCleanup(db_tests.reset)
class TestMetadefSqlAlchemyDriver(base_metadef.TestMetadefDriver,
base_metadef.MetadefDriverTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db_metadef)
super(TestMetadefSqlAlchemyDriver, self).setUp()
self.addCleanup(db_tests.reset)
glance-16.0.1/glance/tests/functional/db/test_rpc_endpoint.py 0000666 0001750 0001750 00000004051 13267672245 024311 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
class TestRegistryURLVisibility(functional.FunctionalTest):
def setUp(self):
super(TestRegistryURLVisibility, self).setUp()
self.cleanup()
self.registry_server.deployment_flavor = ''
self.req_body = jsonutils.dumps([{"command": "image_get_all"}])
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.registry_port, path)
def _headers(self, custom_headers=None):
base_headers = {
}
base_headers.update(custom_headers or {})
return base_headers
def test_v2_not_enabled(self):
self.registry_server.enable_v2_registry = False
self.start_servers(**self.__dict__.copy())
path = self._url('/rpc')
response = requests.post(path, headers=self._headers(),
data=self.req_body)
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_v2_enabled(self):
self.registry_server.enable_v2_registry = True
self.start_servers(**self.__dict__.copy())
path = self._url('/rpc')
response = requests.post(path, headers=self._headers(),
data=self.req_body)
self.assertEqual(http.OK, response.status_code)
self.stop_servers()
glance-16.0.1/glance/tests/functional/db/migrations/ 0000775 0001750 0001750 00000000000 13267672475 022373 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/db/migrations/test_pike_expand01.py 0000666 0001750 0001750 00000003240 13267672245 026430 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_db.sqlalchemy import test_base
from oslo_db.sqlalchemy import utils as db_utils
from glance.tests.functional.db import test_migrations
class TestPikeExpand01Mixin(test_migrations.AlembicMigrationsMixin):
artifacts_table_names = [
'artifact_blob_locations',
'artifact_properties',
'artifact_blobs',
'artifact_dependencies',
'artifact_tags',
'artifacts'
]
def _get_revisions(self, config):
return test_migrations.AlembicMigrationsMixin._get_revisions(
self, config, head='pike_expand01')
def _pre_upgrade_pike_expand01(self, engine):
# verify presence of the artifacts tables
for table_name in self.artifacts_table_names:
table = db_utils.get_table(engine, table_name)
self.assertIsNotNone(table)
def _check_pike_expand01(self, engine, data):
# should be no changes, so re-run pre-upgrade check
self._pre_upgrade_pike_expand01(engine)
class TestPikeExpand01MySQL(TestPikeExpand01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
glance-16.0.1/glance/tests/functional/db/migrations/__init__.py 0000666 0001750 0001750 00000000000 13267672245 024467 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/db/migrations/test_ocata_contract01.py 0000666 0001750 0001750 00000005146 13267672245 027134 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_db.sqlalchemy import test_base
from oslo_db.sqlalchemy import utils as db_utils
from glance.db.sqlalchemy.alembic_migrations import data_migrations
from glance.tests.functional.db import test_migrations
class TestOcataContract01Mixin(test_migrations.AlembicMigrationsMixin):
def _get_revisions(self, config):
return test_migrations.AlembicMigrationsMixin._get_revisions(
self, config, head='ocata_contract01')
def _pre_upgrade_ocata_contract01(self, engine):
images = db_utils.get_table(engine, 'images')
now = datetime.datetime.now()
self.assertIn('is_public', images.c)
self.assertIn('visibility', images.c)
self.assertTrue(images.c.is_public.nullable)
self.assertTrue(images.c.visibility.nullable)
# inserting a public image record
public_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=True,
min_disk=0,
min_ram=0,
id='public_id_before_expand')
images.insert().values(public_temp).execute()
# inserting a private image record
shared_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=False,
min_disk=0,
min_ram=0,
id='private_id_before_expand')
images.insert().values(shared_temp).execute()
data_migrations.migrate(engine=engine, release='ocata')
def _check_ocata_contract01(self, engine, data):
# check that after contract 'is_public' column is dropped
images = db_utils.get_table(engine, 'images')
self.assertNotIn('is_public', images.c)
self.assertIn('visibility', images.c)
class TestOcataContract01MySQL(TestOcataContract01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
glance-16.0.1/glance/tests/functional/db/migrations/test_pike_contract01.py 0000666 0001750 0001750 00000003500 13267672245 026765 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_db.sqlalchemy import test_base
from oslo_db.sqlalchemy import utils as db_utils
import sqlalchemy
from glance.tests.functional.db import test_migrations
class TestPikeContract01Mixin(test_migrations.AlembicMigrationsMixin):
artifacts_table_names = [
'artifact_blob_locations',
'artifact_properties',
'artifact_blobs',
'artifact_dependencies',
'artifact_tags',
'artifacts'
]
def _get_revisions(self, config):
return test_migrations.AlembicMigrationsMixin._get_revisions(
self, config, head='pike_contract01')
def _pre_upgrade_pike_contract01(self, engine):
# verify presence of the artifacts tables
for table_name in self.artifacts_table_names:
table = db_utils.get_table(engine, table_name)
self.assertIsNotNone(table)
def _check_pike_contract01(self, engine, data):
# verify absence of the artifacts tables
for table_name in self.artifacts_table_names:
self.assertRaises(sqlalchemy.exc.NoSuchTableError,
db_utils.get_table, engine, table_name)
class TestPikeContract01MySQL(TestPikeContract01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
glance-16.0.1/glance/tests/functional/db/migrations/test_pike_migrate01.py 0000666 0001750 0001750 00000001613 13267672245 026603 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_db.sqlalchemy import test_base
import glance.tests.functional.db.migrations.test_pike_expand01 as tpe01
# no TestPikeMigrate01Mixin class needed, can use TestPikeExpand01Mixin instead
class TestPikeMigrate01MySQL(tpe01.TestPikeExpand01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
glance-16.0.1/glance/tests/functional/db/migrations/test_ocata_migrate01.py 0000666 0001750 0001750 00000015555 13267672245 026754 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_db.sqlalchemy import test_base
from oslo_db.sqlalchemy import utils as db_utils
from glance.db.sqlalchemy.alembic_migrations import data_migrations
from glance.tests.functional.db import test_migrations
class TestOcataMigrate01Mixin(test_migrations.AlembicMigrationsMixin):
def _get_revisions(self, config):
return test_migrations.AlembicMigrationsMixin._get_revisions(
self, config, head='ocata_expand01')
def _pre_upgrade_ocata_expand01(self, engine):
images = db_utils.get_table(engine, 'images')
image_members = db_utils.get_table(engine, 'image_members')
now = datetime.datetime.now()
# inserting a public image record
public_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=True,
min_disk=0,
min_ram=0,
id='public_id')
images.insert().values(public_temp).execute()
# inserting a non-public image record for 'shared' visibility test
shared_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=False,
min_disk=0,
min_ram=0,
id='shared_id')
images.insert().values(shared_temp).execute()
# inserting a non-public image records for 'private' visibility test
private_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=False,
min_disk=0,
min_ram=0,
id='private_id_1')
images.insert().values(private_temp).execute()
private_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=False,
min_disk=0,
min_ram=0,
id='private_id_2')
images.insert().values(private_temp).execute()
# adding an active as well as a deleted image member for checking
# 'shared' visibility
temp = dict(deleted=False,
created_at=now,
image_id='shared_id',
member='fake_member_452',
can_share=True,
id=45)
image_members.insert().values(temp).execute()
temp = dict(deleted=True,
created_at=now,
image_id='shared_id',
member='fake_member_453',
can_share=True,
id=453)
image_members.insert().values(temp).execute()
# adding an image member, but marking it deleted,
# for testing 'private' visibility
temp = dict(deleted=True,
created_at=now,
image_id='private_id_2',
member='fake_member_451',
can_share=True,
id=451)
image_members.insert().values(temp).execute()
# adding an active image member for the 'public' image,
# to test it remains public regardless.
temp = dict(deleted=False,
created_at=now,
image_id='public_id',
member='fake_member_450',
can_share=True,
id=450)
image_members.insert().values(temp).execute()
def _check_ocata_expand01(self, engine, data):
images = db_utils.get_table(engine, 'images')
# check that visibility is null for existing images
rows = (images.select()
.order_by(images.c.id)
.execute()
.fetchall())
self.assertEqual(4, len(rows))
for row in rows:
self.assertIsNone(row['visibility'])
# run data migrations
data_migrations.migrate(engine)
# check that visibility is set appropriately for all images
rows = (images.select()
.order_by(images.c.id)
.execute()
.fetchall())
self.assertEqual(4, len(rows))
# private_id_1 has private visibility
self.assertEqual('private_id_1', rows[0]['id'])
# TODO(rosmaita): bug #1745003
# self.assertEqual('private', rows[0]['visibility'])
# private_id_2 has private visibility
self.assertEqual('private_id_2', rows[1]['id'])
# TODO(rosmaita): bug #1745003
# self.assertEqual('private', rows[1]['visibility'])
# public_id has public visibility
self.assertEqual('public_id', rows[2]['id'])
# TODO(rosmaita): bug #1745003
# self.assertEqual('public', rows[2]['visibility'])
# shared_id has shared visibility
self.assertEqual('shared_id', rows[3]['id'])
# TODO(rosmaita): bug #1745003
# self.assertEqual('shared', rows[3]['visibility'])
class TestOcataMigrate01MySQL(TestOcataMigrate01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
class TestOcataMigrate01_EmptyDBMixin(test_migrations.AlembicMigrationsMixin):
"""This mixin is used to create an initial glance database and upgrade it
up to the ocata_expand01 revision.
"""
def _get_revisions(self, config):
return test_migrations.AlembicMigrationsMixin._get_revisions(
self, config, head='ocata_expand01')
def _pre_upgrade_ocata_expand01(self, engine):
# New/empty database
pass
def _check_ocata_expand01(self, engine, data):
images = db_utils.get_table(engine, 'images')
# check that there are no rows in the images table
rows = (images.select()
.order_by(images.c.id)
.execute()
.fetchall())
self.assertEqual(0, len(rows))
# run data migrations
data_migrations.migrate(engine)
class TestOcataMigrate01_EmptyDBMySQL(TestOcataMigrate01_EmptyDBMixin,
test_base.MySQLOpportunisticTestCase):
"""This test runs the Ocata data migrations on an empty databse."""
pass
glance-16.0.1/glance/tests/functional/db/migrations/test_mitaka02.py 0000666 0001750 0001750 00000004646 13267672245 025423 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_db.sqlalchemy import test_base
from oslo_db.sqlalchemy import utils as db_utils
from glance.tests.functional.db import test_migrations
class TestMitaka02Mixin(test_migrations.AlembicMigrationsMixin):
def _pre_upgrade_mitaka02(self, engine):
metadef_resource_types = db_utils.get_table(engine,
'metadef_resource_types')
now = datetime.datetime.now()
db_rec1 = dict(id='9580',
name='OS::Nova::Instance',
protected=False,
created_at=now,
updated_at=now,)
db_rec2 = dict(id='9581',
name='OS::Nova::Blah',
protected=False,
created_at=now,
updated_at=now,)
db_values = (db_rec1, db_rec2)
metadef_resource_types.insert().values(db_values).execute()
def _check_mitaka02(self, engine, data):
metadef_resource_types = db_utils.get_table(engine,
'metadef_resource_types')
result = (metadef_resource_types.select()
.where(metadef_resource_types.c.name == 'OS::Nova::Instance')
.execute().fetchall())
self.assertEqual(0, len(result))
result = (metadef_resource_types.select()
.where(metadef_resource_types.c.name == 'OS::Nova::Server')
.execute().fetchall())
self.assertEqual(1, len(result))
class TestMitaka02MySQL(TestMitaka02Mixin,
test_base.MySQLOpportunisticTestCase):
pass
class TestMitaka02PostgresSQL(TestMitaka02Mixin,
test_base.PostgreSQLOpportunisticTestCase):
pass
class TestMitaka02Sqlite(TestMitaka02Mixin, test_base.DbTestCase):
pass
glance-16.0.1/glance/tests/functional/db/migrations/test_ocata_expand01.py 0000666 0001750 0001750 00000016055 13267672245 026577 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_db.sqlalchemy import test_base
from oslo_db.sqlalchemy import utils as db_utils
from glance.tests.functional.db import test_migrations
class TestOcataExpand01Mixin(test_migrations.AlembicMigrationsMixin):
def _get_revisions(self, config):
return test_migrations.AlembicMigrationsMixin._get_revisions(
self, config, head='ocata_expand01')
def _pre_upgrade_ocata_expand01(self, engine):
images = db_utils.get_table(engine, 'images')
now = datetime.datetime.now()
self.assertIn('is_public', images.c)
self.assertNotIn('visibility', images.c)
self.assertFalse(images.c.is_public.nullable)
# inserting a public image record
public_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=True,
min_disk=0,
min_ram=0,
id='public_id_before_expand')
images.insert().values(public_temp).execute()
# inserting a private image record
shared_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=False,
min_disk=0,
min_ram=0,
id='private_id_before_expand')
images.insert().values(shared_temp).execute()
def _check_ocata_expand01(self, engine, data):
# check that after migration, 'visibility' column is introduced
images = db_utils.get_table(engine, 'images')
self.assertIn('visibility', images.c)
self.assertIn('is_public', images.c)
self.assertTrue(images.c.is_public.nullable)
self.assertTrue(images.c.visibility.nullable)
# tests visibility set to None for existing images
rows = (images.select()
.where(images.c.id.like('%_before_expand'))
.order_by(images.c.id)
.execute()
.fetchall())
self.assertEqual(2, len(rows))
# private image first
self.assertEqual(0, rows[0]['is_public'])
self.assertEqual('private_id_before_expand', rows[0]['id'])
self.assertIsNone(rows[0]['visibility'])
# then public image
self.assertEqual(1, rows[1]['is_public'])
self.assertEqual('public_id_before_expand', rows[1]['id'])
self.assertIsNone(rows[1]['visibility'])
self._test_trigger_old_to_new(images)
self._test_trigger_new_to_old(images)
def _test_trigger_new_to_old(self, images):
now = datetime.datetime.now()
# inserting a public image record after expand
public_temp = dict(deleted=False,
created_at=now,
status='active',
visibility='public',
min_disk=0,
min_ram=0,
id='public_id_new_to_old')
images.insert().values(public_temp).execute()
# inserting a private image record after expand
shared_temp = dict(deleted=False,
created_at=now,
status='active',
visibility='private',
min_disk=0,
min_ram=0,
id='private_id_new_to_old')
images.insert().values(shared_temp).execute()
# inserting a shared image record after expand
shared_temp = dict(deleted=False,
created_at=now,
status='active',
visibility='shared',
min_disk=0,
min_ram=0,
id='shared_id_new_to_old')
images.insert().values(shared_temp).execute()
# test visibility is set appropriately by the trigger for new images
rows = (images.select()
.where(images.c.id.like('%_new_to_old'))
.order_by(images.c.id)
.execute()
.fetchall())
self.assertEqual(3, len(rows))
# private image first
self.assertEqual(0, rows[0]['is_public'])
self.assertEqual('private_id_new_to_old', rows[0]['id'])
self.assertEqual('private', rows[0]['visibility'])
# then public image
self.assertEqual(1, rows[1]['is_public'])
self.assertEqual('public_id_new_to_old', rows[1]['id'])
self.assertEqual('public', rows[1]['visibility'])
# then shared image
self.assertEqual(0, rows[2]['is_public'])
self.assertEqual('shared_id_new_to_old', rows[2]['id'])
self.assertEqual('shared', rows[2]['visibility'])
def _test_trigger_old_to_new(self, images):
now = datetime.datetime.now()
# inserting a public image record after expand
public_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=True,
min_disk=0,
min_ram=0,
id='public_id_old_to_new')
images.insert().values(public_temp).execute()
# inserting a private image record after expand
shared_temp = dict(deleted=False,
created_at=now,
status='active',
is_public=False,
min_disk=0,
min_ram=0,
id='private_id_old_to_new')
images.insert().values(shared_temp).execute()
# tests visibility is set appropriately by the trigger for new images
rows = (images.select()
.where(images.c.id.like('%_old_to_new'))
.order_by(images.c.id)
.execute()
.fetchall())
self.assertEqual(2, len(rows))
# private image first
self.assertEqual(0, rows[0]['is_public'])
self.assertEqual('private_id_old_to_new', rows[0]['id'])
self.assertEqual('shared', rows[0]['visibility'])
# then public image
self.assertEqual(1, rows[1]['is_public'])
self.assertEqual('public_id_old_to_new', rows[1]['id'])
self.assertEqual('public', rows[1]['visibility'])
class TestOcataExpand01MySQL(TestOcataExpand01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
glance-16.0.1/glance/tests/functional/db/migrations/test_mitaka01.py 0000666 0001750 0001750 00000003150 13267672245 025407 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_db.sqlalchemy import test_base
import sqlalchemy
from glance.tests.functional.db import test_migrations
def get_indexes(table, engine):
inspector = sqlalchemy.inspect(engine)
return [idx['name'] for idx in inspector.get_indexes(table)]
class TestMitaka01Mixin(test_migrations.AlembicMigrationsMixin):
def _pre_upgrade_mitaka01(self, engine):
indexes = get_indexes('images', engine)
self.assertNotIn('created_at_image_idx', indexes)
self.assertNotIn('updated_at_image_idx', indexes)
def _check_mitaka01(self, engine, data):
indexes = get_indexes('images', engine)
self.assertIn('created_at_image_idx', indexes)
self.assertIn('updated_at_image_idx', indexes)
class TestMitaka01MySQL(TestMitaka01Mixin,
test_base.MySQLOpportunisticTestCase):
pass
class TestMitaka01PostgresSQL(TestMitaka01Mixin,
test_base.PostgreSQLOpportunisticTestCase):
pass
class TestMitaka01Sqlite(TestMitaka01Mixin, test_base.DbTestCase):
pass
glance-16.0.1/glance/tests/functional/db/test_simple.py 0000666 0001750 0001750 00000005411 13267672245 023117 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.api import CONF
import glance.db.simple.api
import glance.tests.functional.db as db_tests
from glance.tests.functional.db import base
def get_db(config, workers=1):
CONF.set_override('data_api', 'glance.db.simple.api')
CONF.set_override('workers', workers)
db_api = glance.db.get_api()
return db_api
def reset_db(db_api):
db_api.reset()
class TestSimpleDriver(base.TestDriver,
base.DriverTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSimpleDriver, self).setUp()
self.addCleanup(db_tests.reset)
class TestSimpleQuota(base.DriverQuotaTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSimpleQuota, self).setUp()
self.addCleanup(db_tests.reset)
class TestSimpleVisibility(base.TestVisibility,
base.VisibilityTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSimpleVisibility, self).setUp()
self.addCleanup(db_tests.reset)
class TestSimpleMembershipVisibility(base.TestMembershipVisibility,
base.MembershipVisibilityTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSimpleMembershipVisibility, self).setUp()
self.addCleanup(db_tests.reset)
class TestSimpleTask(base.TaskTests,
base.FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestSimpleTask, self).setUp()
self.addCleanup(db_tests.reset)
class TestTooManyWorkers(base.TaskTests):
def setUp(self):
def get_db_too_many_workers(config):
self.assertRaises(SystemExit, get_db, config, 2)
return get_db(config)
db_tests.load(get_db_too_many_workers, reset_db)
super(TestTooManyWorkers, self).setUp()
self.addCleanup(db_tests.reset)
glance-16.0.1/glance/tests/functional/db/test_registry.py 0000666 0001750 0001750 00000007216 13267672245 023503 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_db import options
import glance.db
# NOTE(smcginnis) Need to make sure registry opts are registered
from glance import registry # noqa
from glance.registry import client # noqa
import glance.tests.functional.db as db_tests
from glance.tests.functional.db import base
from glance.tests.functional.db import base_metadef
CONF = cfg.CONF
def get_db(config):
options.set_defaults(CONF, connection='sqlite://')
config(data_api='glance.db.registry.api')
return glance.db.get_api()
def reset_db(db_api):
pass
class FunctionalInitWrapper(base.FunctionalInitWrapper):
def setUp(self):
# NOTE(flaper87): We need to start the
# registry service *before* TestDriver's
# setup goes on, since it'll create some
# images that will be later used in tests.
#
# Python's request is way too magical and
# it will make the TestDriver's super call
# FunctionalTest's without letting us start
# the server.
#
# This setUp will be called by TestDriver
# and will be used to call FunctionalTest
# setUp method *and* start the registry
# service right after it.
super(FunctionalInitWrapper, self).setUp()
self.registry_server.deployment_flavor = 'fakeauth'
self.start_with_retry(self.registry_server,
'registry_port', 3,
api_version=2)
self.config(registry_port=self.registry_server.bind_port,
use_user_token=True)
class TestRegistryDriver(base.TestDriver,
base.DriverTests,
FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestRegistryDriver, self).setUp()
self.addCleanup(db_tests.reset)
def tearDown(self):
self.registry_server.stop()
super(TestRegistryDriver, self).tearDown()
class TestRegistryQuota(base.DriverQuotaTests, FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestRegistryQuota, self).setUp()
self.addCleanup(db_tests.reset)
def tearDown(self):
self.registry_server.stop()
super(TestRegistryQuota, self).tearDown()
class TestRegistryMetadefDriver(base_metadef.TestMetadefDriver,
base_metadef.MetadefDriverTests,
FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestRegistryMetadefDriver, self).setUp()
self.addCleanup(db_tests.reset)
def tearDown(self):
self.registry_server.stop()
super(TestRegistryMetadefDriver, self).tearDown()
class TestTasksDriver(base.TaskTests, FunctionalInitWrapper):
def setUp(self):
db_tests.load(get_db, reset_db)
super(TestTasksDriver, self).setUp()
self.addCleanup(db_tests.reset)
def tearDown(self):
self.registry_server.stop()
super(TestTasksDriver, self).tearDown()
glance-16.0.1/glance/tests/functional/test_reload.py 0000666 0001750 0001750 00000022645 13267672245 022517 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import re
import time
import unittest
import psutil
import requests
import six
from six.moves import http_client as http
from glance.tests import functional
from glance.tests.utils import execute
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../', 'var'))
def set_config_value(filepath, key, value):
"""Set 'key = value' in config file"""
replacement_line = '%s = %s\n' % (key, value)
match = re.compile('^%s\s+=' % key).match
with open(filepath, 'r+') as f:
lines = f.readlines()
f.seek(0, 0)
f.truncate()
for line in lines:
f.write(line if not match(line) else replacement_line)
class TestReload(functional.FunctionalTest):
"""Test configuration reload"""
def setUp(self):
self.workers = 1
super(TestReload, self).setUp()
def tearDown(self):
self.stop_servers()
super(TestReload, self).tearDown()
def ticker(self, message, seconds=60, tick=0.01):
"""
Allows repeatedly testing for an expected result
for a finite amount of time.
:param message: Message to display on timeout
:param seconds: Time in seconds after which we timeout
:param tick: Time to sleep before rechecking for expected result
:returns: 'True' or fails the test with 'message' on timeout
"""
# We default to allowing 60 seconds timeout but
# typically only a few hundredths of a second
# are needed.
num_ticks = seconds * (1.0 / tick)
count = 0
while count < num_ticks:
count += 1
time.sleep(tick)
yield
self.fail(message)
def _get_children(self, server):
pid = None
pid = self._get_parent(server)
process = psutil.Process(pid)
try:
# psutils version >= 2
children = process.children()
except AttributeError:
# psutils version < 2
children = process.get_children()
pids = set()
for child in children:
pids.add(child.pid)
return pids
def _get_parent(self, server):
if server == 'api':
return self.api_server.process_pid
elif server == 'registry':
return self.registry_server.process_pid
def _conffile(self, service):
conf_dir = os.path.join(self.test_dir, 'etc')
conf_filepath = os.path.join(conf_dir, '%s.conf' % service)
return conf_filepath
def _url(self, protocol, path):
return '%s://127.0.0.1:%d%s' % (protocol, self.api_port, path)
@unittest.skipIf(six.PY3, 'SSL handshakes are broken in PY3')
def test_reload(self):
"""Test SIGHUP picks up new config values"""
def check_pids(pre, post=None, workers=2):
if post is None:
if len(pre) == workers:
return True
else:
return False
if len(post) == workers:
# Check new children have different pids
if post.intersection(pre) == set():
return True
return False
self.api_server.fork_socket = False
self.registry_server.fork_socket = False
self.start_servers(fork_socket=False, **vars(self))
pre_pids = {}
post_pids = {}
# Test changing the workers value creates all new children
# This recycles the existing socket
msg = 'Start timeout'
for _ in self.ticker(msg):
for server in ('api', 'registry'):
pre_pids[server] = self._get_children(server)
if check_pids(pre_pids['api'], workers=1):
if check_pids(pre_pids['registry'], workers=1):
break
for server in ('api', 'registry'):
# Labour costs have fallen
set_config_value(self._conffile(server), 'workers', '2')
cmd = "kill -HUP %s" % self._get_parent(server)
execute(cmd, raise_error=True)
msg = 'Worker change timeout'
for _ in self.ticker(msg):
for server in ('api', 'registry'):
post_pids[server] = self._get_children(server)
if check_pids(pre_pids['registry'], post_pids['registry']):
if check_pids(pre_pids['api'], post_pids['api']):
break
# Test changing from http to https
# This recycles the existing socket
path = self._url('http', '/')
response = requests.get(path)
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
del response # close socket so that process audit is reliable
pre_pids['api'] = self._get_children('api')
key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key')
set_config_value(self._conffile('api'), 'key_file', key_file)
cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
set_config_value(self._conffile('api'), 'cert_file', cert_file)
cmd = "kill -HUP %s" % self._get_parent('api')
execute(cmd, raise_error=True)
msg = 'http to https timeout'
for _ in self.ticker(msg):
post_pids['api'] = self._get_children('api')
if check_pids(pre_pids['api'], post_pids['api']):
break
ca_file = os.path.join(TEST_VAR_DIR, 'ca.crt')
path = self._url('https', '/')
response = requests.get(path, verify=ca_file)
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
del response
# Test https restart
# This recycles the existing socket
pre_pids['api'] = self._get_children('api')
cmd = "kill -HUP %s" % self._get_parent('api')
execute(cmd, raise_error=True)
msg = 'https restart timeout'
for _ in self.ticker(msg):
post_pids['api'] = self._get_children('api')
if check_pids(pre_pids['api'], post_pids['api']):
break
ca_file = os.path.join(TEST_VAR_DIR, 'ca.crt')
path = self._url('https', '/')
response = requests.get(path, verify=ca_file)
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
del response
# Test changing the https bind_host
# This requires a new socket
pre_pids['api'] = self._get_children('api')
set_config_value(self._conffile('api'), 'bind_host', '127.0.0.1')
cmd = "kill -HUP %s" % self._get_parent('api')
execute(cmd, raise_error=True)
msg = 'https bind_host timeout'
for _ in self.ticker(msg):
post_pids['api'] = self._get_children('api')
if check_pids(pre_pids['api'], post_pids['api']):
break
path = self._url('https', '/')
response = requests.get(path, verify=ca_file)
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
del response
# Test https -> http
# This recycles the existing socket
pre_pids['api'] = self._get_children('api')
set_config_value(self._conffile('api'), 'key_file', '')
set_config_value(self._conffile('api'), 'cert_file', '')
cmd = "kill -HUP %s" % self._get_parent('api')
execute(cmd, raise_error=True)
msg = 'https to http timeout'
for _ in self.ticker(msg):
post_pids['api'] = self._get_children('api')
if check_pids(pre_pids['api'], post_pids['api']):
break
path = self._url('http', '/')
response = requests.get(path)
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
del response
# Test changing the http bind_host
# This requires a new socket
pre_pids['api'] = self._get_children('api')
set_config_value(self._conffile('api'), 'bind_host', '127.0.0.1')
cmd = "kill -HUP %s" % self._get_parent('api')
execute(cmd, raise_error=True)
msg = 'http bind_host timeout'
for _ in self.ticker(msg):
post_pids['api'] = self._get_children('api')
if check_pids(pre_pids['api'], post_pids['api']):
break
path = self._url('http', '/')
response = requests.get(path)
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
del response
# Test logging configuration change
# This recycles the existing socket
conf_dir = os.path.join(self.test_dir, 'etc')
log_file = conf_dir + 'new.log'
self.assertFalse(os.path.exists(log_file))
set_config_value(self._conffile('api'), 'log_file', log_file)
cmd = "kill -HUP %s" % self._get_parent('api')
execute(cmd, raise_error=True)
msg = 'No new log file created'
for _ in self.ticker(msg):
if os.path.exists(log_file):
break
glance-16.0.1/glance/tests/functional/test_healthcheck_middleware.py 0000666 0001750 0001750 00000003425 13267672245 025704 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 Hewlett Packard
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests healthcheck middleware."""
import tempfile
import httplib2
from six.moves import http_client
from glance.tests import functional
from glance.tests import utils
class HealthcheckMiddlewareTest(functional.FunctionalTest):
def request(self):
url = 'http://127.0.0.1:%s/healthcheck' % self.api_port
http = httplib2.Http()
return http.request(url, 'GET')
@utils.skip_if_disabled
def test_healthcheck_enabled(self):
self.cleanup()
self.start_servers(**self.__dict__.copy())
response, content = self.request()
self.assertEqual(b'OK', content)
self.assertEqual(http_client.OK, response.status)
self.stop_servers()
def test_healthcheck_disabled(self):
with tempfile.NamedTemporaryFile() as test_disable_file:
self.cleanup()
self.api_server.disable_path = test_disable_file.name
self.start_servers(**self.__dict__.copy())
response, content = self.request()
self.assertEqual(b'DISABLED BY FILE', content)
self.assertEqual(http_client.SERVICE_UNAVAILABLE, response.status)
self.stop_servers()
glance-16.0.1/glance/tests/functional/v2/ 0000775 0001750 0001750 00000000000 13267672475 020161 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/v2/test_schemas.py 0000666 0001750 0001750 00000004445 13267672245 023221 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
class TestSchemas(functional.FunctionalTest):
def setUp(self):
super(TestSchemas, self).setUp()
self.cleanup()
self.start_servers(**self.__dict__.copy())
def test_resource(self):
# Ensure the image link works and custom properties are loaded
path = 'http://%s:%d/v2/schemas/image' % ('127.0.0.1', self.api_port)
response = requests.get(path)
self.assertEqual(http.OK, response.status_code)
image_schema = jsonutils.loads(response.text)
expected = set([
'id',
'name',
'visibility',
'checksum',
'created_at',
'updated_at',
'tags',
'size',
'virtual_size',
'owner',
'container_format',
'disk_format',
'self',
'file',
'status',
'schema',
'direct_url',
'locations',
'min_ram',
'min_disk',
'protected',
])
self.assertEqual(expected, set(image_schema['properties'].keys()))
# Ensure the images link works and agrees with the image schema
path = 'http://%s:%d/v2/schemas/images' % ('127.0.0.1', self.api_port)
response = requests.get(path)
self.assertEqual(http.OK, response.status_code)
images_schema = jsonutils.loads(response.text)
item_schema = images_schema['properties']['images']['items']
self.assertEqual(item_schema, image_schema)
self.stop_servers()
glance-16.0.1/glance/tests/functional/v2/__init__.py 0000666 0001750 0001750 00000000000 13267672245 022255 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/v2/test_tasks.py 0000666 0001750 0001750 00000012151 13267672245 022714 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 IBM Corp.
# All Rights Reserved.
#
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
TENANT3 = str(uuid.uuid4())
TENANT4 = str(uuid.uuid4())
class TestTasks(functional.FunctionalTest):
def setUp(self):
super(TestTasks, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'admin',
}
base_headers.update(custom_headers or {})
return base_headers
def test_task_not_allowed_non_admin(self):
self.start_servers(**self.__dict__.copy())
roles = {'X-Roles': 'member'}
# Task list should be empty
path = self._url('/v2/tasks')
response = requests.get(path, headers=self._headers(roles))
self.assertEqual(http.FORBIDDEN, response.status_code)
def test_task_lifecycle(self):
self.start_servers(**self.__dict__.copy())
# Task list should be empty
path = self._url('/v2/tasks')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tasks = jsonutils.loads(response.text)['tasks']
self.assertEqual(0, len(tasks))
# Create a task
path = self._url('/v2/tasks')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({
"type": "import",
"input": {
"import_from": "http://example.com",
"import_from_format": "qcow2",
"image_properties": {
'disk_format': 'vhd',
'container_format': 'ovf'
}
}
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned task entity should have a generated id and status
task = jsonutils.loads(response.text)
task_id = task['id']
self.assertIn('Location', response.headers)
self.assertEqual(path + '/' + task_id, response.headers['Location'])
checked_keys = set([u'created_at',
u'id',
u'input',
u'message',
u'owner',
u'schema',
u'self',
u'status',
u'type',
u'result',
u'updated_at'])
self.assertEqual(checked_keys, set(task.keys()))
expected_task = {
'status': 'pending',
'type': 'import',
'input': {
"import_from": "http://example.com",
"import_from_format": "qcow2",
"image_properties": {
'disk_format': 'vhd',
'container_format': 'ovf'
}},
'schema': '/v2/schemas/task',
}
for key, value in expected_task.items():
self.assertEqual(value, task[key], key)
# Tasks list should now have one entry
path = self._url('/v2/tasks')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tasks = jsonutils.loads(response.text)['tasks']
self.assertEqual(1, len(tasks))
self.assertEqual(task_id, tasks[0]['id'])
# Attempt to delete a task
path = self._url('/v2/tasks/%s' % tasks[0]['id'])
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.METHOD_NOT_ALLOWED, response.status_code)
self.assertIsNotNone(response.headers.get('Allow'))
self.assertEqual('GET', response.headers.get('Allow'))
self.stop_servers()
class TestTasksWithRegistry(TestTasks):
def setUp(self):
super(TestTasksWithRegistry, self).setUp()
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
self.include_scrubber = False
glance-16.0.1/glance/tests/functional/v2/test_metadef_tags.py 0000666 0001750 0001750 00000015671 13267672245 024224 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import uuid
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
class TestMetadefTags(functional.FunctionalTest):
def setUp(self):
super(TestMetadefTags, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'admin',
}
base_headers.update(custom_headers or {})
return base_headers
def test_metadata_tags_lifecycle(self):
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a namespace
path = self._url('/v2/metadefs/namespaces')
headers = self._headers({'content-type': 'application/json'})
namespace_name = 'MyNamespace'
data = jsonutils.dumps({
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": False,
"owner": "The Test Owner"}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Metadata tag should not exist
metadata_tag_name = "tag1"
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create the metadata tag
headers = self._headers({'content-type': 'application/json'})
response = requests.post(path, headers=headers)
self.assertEqual(http.CREATED, response.status_code)
# Get the metadata tag created above
response = requests.get(path,
headers=self._headers())
self.assertEqual(http.OK, response.status_code)
metadata_tag = jsonutils.loads(response.text)
self.assertEqual(metadata_tag_name, metadata_tag['name'])
# Returned tag should match the created tag
metadata_tag = jsonutils.loads(response.text)
checked_keys = set([
u'name',
u'created_at',
u'updated_at'
])
self.assertEqual(checked_keys, set(metadata_tag.keys()))
expected_metadata_tag = {
"name": metadata_tag_name
}
# Simple key values
checked_values = set([
u'name'
])
for key, value in expected_metadata_tag.items():
if(key in checked_values):
self.assertEqual(metadata_tag[key], value, key)
# Try to create a duplicate metadata tag
headers = self._headers({'content-type': 'application/json'})
response = requests.post(path, headers=headers)
self.assertEqual(http.CONFLICT, response.status_code)
# The metadata_tag should be mutable
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
metadata_tag_name = "tag1-UPDATED"
data = jsonutils.dumps(
{
"name": metadata_tag_name
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned metadata_tag should reflect the changes
metadata_tag = jsonutils.loads(response.text)
self.assertEqual('tag1-UPDATED', metadata_tag['name'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
self.assertEqual('tag1-UPDATED', metadata_tag['name'])
# Deletion of metadata_tag_name
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# metadata_tag_name should not exist
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create multiple tags.
path = self._url('/v2/metadefs/namespaces/%s/tags' %
(namespace_name))
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps(
{"tags": [{"name": "tag1"}, {"name": "tag2"}, {"name": "tag3"}]}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# List out the three new tags.
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(3, len(tags))
# Attempt to create bogus duplicate tag4
data = jsonutils.dumps(
{"tags": [{"name": "tag4"}, {"name": "tag5"}, {"name": "tag4"}]}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code)
# Verify the previous 3 still exist
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(3, len(tags))
glance-16.0.1/glance/tests/functional/v2/registry_data_api.py 0000666 0001750 0001750 00000004054 13267672245 024225 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.db.registry.api import * # noqa
from glance.common.rpc import RPCClient
from glance.registry.client.v2 import api
from glance.registry.client.v2 import client
def patched_bulk_request(self, commands):
# We add some auth headers which are typically
# added by keystone. This is required when testing
# without keystone, otherwise the tests fail.
# We use the 'trusted-auth' deployment flavour
# for testing so that these headers are interpreted
# as expected (ie the same way as if keystone was
# present)
body = self._serializer.to_json(commands)
headers = {"X-Identity-Status": "Confirmed", 'X-Roles': 'member'}
if self.context.user is not None:
headers['X-User-Id'] = self.context.user
if self.context.tenant is not None:
headers['X-Tenant-Id'] = self.context.tenant
response = super(RPCClient, self).do_request('POST',
self.base_path,
body,
headers=headers)
return self._deserializer.from_json(response.read())
def client_wrapper(func):
def call(context):
reg_client = func(context)
reg_client.context = context
return reg_client
return call
client.RegistryClient.bulk_request = patched_bulk_request
api.get_registry_client = client_wrapper(api.get_registry_client)
glance-16.0.1/glance/tests/functional/v2/test_metadef_namespaces.py 0000666 0001750 0001750 00000023652 13267672245 025403 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import uuid
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
class TestNamespaces(functional.FunctionalTest):
def setUp(self):
super(TestNamespaces, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'admin',
}
base_headers.update(custom_headers or {})
return base_headers
def test_namespace_lifecycle(self):
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a namespace
path = self._url('/v2/metadefs/namespaces')
headers = self._headers({'content-type': 'application/json'})
namespace_name = 'MyNamespace'
data = jsonutils.dumps({
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description"
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
namespace_loc_header = response.headers['Location']
# Returned namespace should match the created namespace with default
# values of visibility=private, protected=False and owner=Context
# Tenant
namespace = jsonutils.loads(response.text)
checked_keys = set([
u'namespace',
u'display_name',
u'description',
u'visibility',
u'self',
u'schema',
u'protected',
u'owner',
u'created_at',
u'updated_at'
])
self.assertEqual(set(namespace.keys()), checked_keys)
expected_namespace = {
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "private",
"protected": False,
"owner": TENANT1,
"self": "/v2/metadefs/namespaces/%s" % namespace_name,
"schema": "/v2/schemas/metadefs/namespace"
}
for key, value in expected_namespace.items():
self.assertEqual(namespace[key], value, key)
# Attempt to insert a duplicate
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code)
# Get the namespace using the returned Location header
response = requests.get(namespace_loc_header, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
namespace = jsonutils.loads(response.text)
self.assertEqual(namespace_name, namespace['namespace'])
self.assertNotIn('object', namespace)
self.assertEqual(TENANT1, namespace['owner'])
self.assertEqual('private', namespace['visibility'])
self.assertFalse(namespace['protected'])
# The namespace should be mutable
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
namespace_name = "MyNamespace-UPDATED"
data = jsonutils.dumps(
{
"namespace": namespace_name,
"display_name": "display_name-UPDATED",
"description": "description-UPDATED",
"visibility": "private", # Not changed
"protected": True,
"owner": TENANT2
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned namespace should reflect the changes
namespace = jsonutils.loads(response.text)
self.assertEqual('MyNamespace-UPDATED', namespace_name)
self.assertEqual('display_name-UPDATED', namespace['display_name'])
self.assertEqual('description-UPDATED', namespace['description'])
self.assertEqual('private', namespace['visibility'])
self.assertTrue(namespace['protected'])
self.assertEqual(TENANT2, namespace['owner'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
namespace = jsonutils.loads(response.text)
self.assertEqual('MyNamespace-UPDATED', namespace['namespace'])
self.assertEqual('display_name-UPDATED', namespace['display_name'])
self.assertEqual('description-UPDATED', namespace['description'])
self.assertEqual('private', namespace['visibility'])
self.assertTrue(namespace['protected'])
self.assertEqual(TENANT2, namespace['owner'])
# Deletion should not work on protected namespaces
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.FORBIDDEN, response.status_code)
# Unprotect namespace for deletion
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
doc = {
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": False,
"owner": TENANT2
}
data = jsonutils.dumps(doc)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Deletion should work. Deleting namespace MyNamespace
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
def test_metadef_dont_accept_illegal_bodies(self):
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/bodytest')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a namespace
path = self._url('/v2/metadefs/namespaces')
headers = self._headers({'content-type': 'application/json'})
namespace_name = 'bodytest'
data = jsonutils.dumps({
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description"
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Test all the urls that supply data
data_urls = [
'/v2/schemas/metadefs/namespace',
'/v2/schemas/metadefs/namespaces',
'/v2/schemas/metadefs/resource_type',
'/v2/schemas/metadefs/resource_types',
'/v2/schemas/metadefs/property',
'/v2/schemas/metadefs/properties',
'/v2/schemas/metadefs/object',
'/v2/schemas/metadefs/objects',
'/v2/schemas/metadefs/tag',
'/v2/schemas/metadefs/tags',
'/v2/metadefs/resource_types',
]
for value in data_urls:
path = self._url(value)
data = jsonutils.dumps(["body"])
response = requests.get(path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Put the namespace into the url
test_urls = [
('/v2/metadefs/namespaces/%s/resource_types', 'get'),
('/v2/metadefs/namespaces/%s/resource_types/type', 'delete'),
('/v2/metadefs/namespaces/%s', 'get'),
('/v2/metadefs/namespaces/%s', 'delete'),
('/v2/metadefs/namespaces/%s/objects/name', 'get'),
('/v2/metadefs/namespaces/%s/objects/name', 'delete'),
('/v2/metadefs/namespaces/%s/properties', 'get'),
('/v2/metadefs/namespaces/%s/tags/test', 'get'),
('/v2/metadefs/namespaces/%s/tags/test', 'post'),
('/v2/metadefs/namespaces/%s/tags/test', 'delete'),
]
for link, method in test_urls:
path = self._url(link % namespace_name)
data = jsonutils.dumps(["body"])
response = getattr(requests, method)(
path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
glance-16.0.1/glance/tests/functional/v2/test_images.py 0000666 0001750 0001750 00000551572 13267672245 023053 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import signal
import time
import uuid
from oslo_serialization import jsonutils
import requests
import six
from six.moves import http_client as http
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from six.moves import urllib
from glance.tests import functional
from glance.tests import utils as test_utils
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
TENANT3 = str(uuid.uuid4())
TENANT4 = str(uuid.uuid4())
class TestImages(functional.FunctionalTest):
def setUp(self):
super(TestImages, self).setUp()
self.cleanup()
self.include_scrubber = False
self.api_server.deployment_flavor = 'noauth'
self.api_server.data_api = 'glance.db.sqlalchemy.api'
for i in range(3):
ret = test_utils.start_http_server("foo_image_id%d" % i,
"foo_image%d" % i)
setattr(self, 'http_server%d_pid' % i, ret[0])
setattr(self, 'http_port%d' % i, ret[1])
def tearDown(self):
for i in range(3):
pid = getattr(self, 'http_server%d_pid' % i, None)
if pid:
os.kill(pid, signal.SIGKILL)
super(TestImages, self).tearDown()
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_v1_none_properties_v2(self):
self.api_server.deployment_flavor = 'noauth'
self.api_server.use_user_token = True
self.api_server.send_identity_credentials = True
self.registry_server.deployment_flavor = ''
# Image list should be empty
self.start_servers(**self.__dict__.copy())
# Create an image (with two deployer-defined properties)
path = self._url('/v1/images')
headers = self._headers({'content-type': 'application/octet-stream'})
headers.update(test_utils.minimal_headers('image-1'))
# NOTE(flaper87): Sending empty string, the server will use None
headers['x-image-meta-property-my_empty_prop'] = ''
response = requests.post(path, headers=headers)
self.assertEqual(http.CREATED, response.status_code)
data = jsonutils.loads(response.text)
image_id = data['image']['id']
# NOTE(flaper87): Get the image using V2 and verify
# the returned value for `my_empty_prop` is an empty
# string.
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual('', image['my_empty_prop'])
self.stop_servers()
def test_not_authenticated_in_registry_on_ops(self):
# https://bugs.launchpad.net/glance/+bug/1451850
# this configuration guarantees that authentication succeeds in
# glance-api and fails in glance-registry if no token is passed
self.api_server.deployment_flavor = ''
# make sure that request will reach registry
self.api_server.data_api = 'glance.db.registry.api'
self.registry_server.deployment_flavor = 'fakeauth'
self.start_servers(**self.__dict__.copy())
headers = {'content-type': 'application/json'}
image = {'name': 'image', 'type': 'kernel', 'disk_format': 'qcow2',
'container_format': 'bare'}
# image create should return 401
response = requests.post(self._url('/v2/images'), headers=headers,
data=jsonutils.dumps(image))
self.assertEqual(http.UNAUTHORIZED, response.status_code)
# image list should return 401
response = requests.get(self._url('/v2/images'))
self.assertEqual(http.UNAUTHORIZED, response.status_code)
# image show should return 401
response = requests.get(self._url('/v2/images/someimageid'))
self.assertEqual(http.UNAUTHORIZED, response.status_code)
# image update should return 401
ops = [{'op': 'replace', 'path': '/protected', 'value': False}]
media_type = 'application/openstack-images-v2.1-json-patch'
response = requests.patch(self._url('/v2/images/someimageid'),
headers={'content-type': media_type},
data=jsonutils.dumps(ops))
self.assertEqual(http.UNAUTHORIZED, response.status_code)
# image delete should return 401
response = requests.delete(self._url('/v2/images/someimageid'))
self.assertEqual(http.UNAUTHORIZED, response.status_code)
self.stop_servers()
def test_image_import_using_glance_direct(self):
self.api_server.enable_image_import = True
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# glance-direct should be available in discovery response
path = self._url('/v2/info/import')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
discovery_calls = jsonutils.loads(
response.text)['import-methods']['value']
self.assertIn("glance-direct", discovery_calls)
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity should have a generated id and status
image = jsonutils.loads(response.text)
image_id = image['id']
checked_keys = set([
u'status',
u'name',
u'tags',
u'created_at',
u'updated_at',
u'visibility',
u'self',
u'protected',
u'id',
u'file',
u'min_disk',
u'type',
u'min_ram',
u'schema',
u'disk_format',
u'container_format',
u'owner',
u'checksum',
u'size',
u'virtual_size',
])
self.assertEqual(checked_keys, set(image.keys()))
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'type': 'kernel',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Image list should now have one entry
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
def _verify_image_checksum_and_status(checksum=None, status=None):
# Checksum should be populated and status should be active
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual(checksum, image['checksum'])
self.assertEqual(status, image['status'])
# Upload some image data to staging area
path = self._url('/v2/images/%s/stage' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Verify image is in uploading state and checksum is None
_verify_image_checksum_and_status(status='uploading')
# Import image to store
path = self._url('/v2/images/%s/import' % image_id)
headers = self._headers({
'content-type': 'application/json',
'X-Roles': 'admin',
})
data = jsonutils.dumps({'method': {
'name': 'glance-direct'
}})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.ACCEPTED, response.status_code)
# Verify image is in active state and checksum is set
# NOTE(abhishekk): As import is a async call we need to provide
# some timelap to complete the call.
time.sleep(0.5)
_verify_image_checksum_and_status(
checksum='8f113e38d28a79a5a451b16048cc2b72',
status='active')
# Ensure the size is updated to reflect the data uploaded
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
self.assertEqual(5, jsonutils.loads(response.text)['size'])
# Deleting image should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Image list should now be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
self.stop_servers()
def test_image_import_using_web_download(self):
self.api_server.enable_image_import = True
self.config(node_staging_uri="file:///tmp/staging/")
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# web-download should be available in discovery response
path = self._url('/v2/info/import')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
discovery_calls = jsonutils.loads(
response.text)['import-methods']['value']
self.assertIn("web-download", discovery_calls)
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity should have a generated id and status
image = jsonutils.loads(response.text)
image_id = image['id']
checked_keys = set([
u'status',
u'name',
u'tags',
u'created_at',
u'updated_at',
u'visibility',
u'self',
u'protected',
u'id',
u'file',
u'min_disk',
u'type',
u'min_ram',
u'schema',
u'disk_format',
u'container_format',
u'owner',
u'checksum',
u'size',
u'virtual_size',
])
self.assertEqual(checked_keys, set(image.keys()))
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'type': 'kernel',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Image list should now have one entry
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
def _verify_image_checksum_and_status(checksum=None, status=None):
# Checksum should be populated and status should be active
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual(checksum, image['checksum'])
self.assertEqual(status, image['status'])
# Verify image is in queued state and checksum is None
_verify_image_checksum_and_status(status='queued')
# Import image to store
path = self._url('/v2/images/%s/import' % image_id)
headers = self._headers({
'content-type': 'application/json',
'X-Roles': 'admin',
})
data = jsonutils.dumps({'method': {
'name': 'web-download',
'uri': 'https://www.openstack.org/assets/openstack-logo/'
'2016R/OpenStack-Logo-Horizontal.eps.zip'
}})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.ACCEPTED, response.status_code)
# Verify image is in active state and checksum is set
# NOTE(abhishekk): As import is a async call we need to provide
# some timelap to complete the call.
time.sleep(5)
_verify_image_checksum_and_status(
checksum='bcd65f8922f61a9e6a20572ad7aa2bdd',
status='active')
# Deleting image should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Image list should now be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
self.stop_servers()
def test_image_lifecycle(self):
# Image list should be empty
self.api_server.show_multiple_locations = True
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image (with two deployer-defined properties)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki', 'abc': 'xyz',
'protected': True})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image_location_header = response.headers['Location']
# Returned image entity should have a generated id and status
image = jsonutils.loads(response.text)
image_id = image['id']
checked_keys = set([
u'status',
u'name',
u'tags',
u'created_at',
u'updated_at',
u'visibility',
u'self',
u'protected',
u'id',
u'file',
u'min_disk',
u'foo',
u'abc',
u'type',
u'min_ram',
u'schema',
u'disk_format',
u'container_format',
u'owner',
u'checksum',
u'size',
u'virtual_size',
u'locations',
])
self.assertEqual(checked_keys, set(image.keys()))
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': True,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'foo': 'bar',
'abc': 'xyz',
'type': 'kernel',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Image list should now have one entry
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
# Create another image (with two deployer-defined properties)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-2', 'type': 'kernel',
'bar': 'foo', 'disk_format': 'aki',
'container_format': 'aki', 'xyz': 'abc'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity should have a generated id and status
image = jsonutils.loads(response.text)
image2_id = image['id']
checked_keys = set([
u'status',
u'name',
u'tags',
u'created_at',
u'updated_at',
u'visibility',
u'self',
u'protected',
u'id',
u'file',
u'min_disk',
u'bar',
u'xyz',
u'type',
u'min_ram',
u'schema',
u'disk_format',
u'container_format',
u'owner',
u'checksum',
u'size',
u'virtual_size',
u'locations',
])
self.assertEqual(checked_keys, set(image.keys()))
expected_image = {
'status': 'queued',
'name': 'image-2',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image2_id,
'protected': False,
'file': '/v2/images/%s/file' % image2_id,
'min_disk': 0,
'bar': 'foo',
'xyz': 'abc',
'type': 'kernel',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Image list should now have two entries
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(2, len(images))
self.assertEqual(image2_id, images[0]['id'])
self.assertEqual(image_id, images[1]['id'])
# Image list should list only image-2 as image-1 doesn't contain the
# property 'bar'
path = self._url('/v2/images?bar=foo')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Image list should list only image-1 as image-2 doesn't contain the
# property 'foo'
path = self._url('/v2/images?foo=bar')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
# The "changes-since" filter shouldn't work on glance v2
path = self._url('/v2/images?changes-since=20001007T10:10:10')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
path = self._url('/v2/images?changes-since=aaa')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Image list should list only image-1 based on the filter
# 'protected=true'
path = self._url('/v2/images?protected=true')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
# Image list should list only image-2 based on the filter
# 'protected=false'
path = self._url('/v2/images?protected=false')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Image list should return 400 based on the filter
# 'protected=False'
path = self._url('/v2/images?protected=False')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Image list should list only image-1 based on the filter
# 'foo=bar&abc=xyz'
path = self._url('/v2/images?foo=bar&abc=xyz')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
# Image list should list only image-2 based on the filter
# 'bar=foo&xyz=abc'
path = self._url('/v2/images?bar=foo&xyz=abc')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Image list should not list anything as the filter 'foo=baz&abc=xyz'
# is not satisfied by either images
path = self._url('/v2/images?foo=baz&abc=xyz')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Get the image using the returned Location header
response = requests.get(image_location_header, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual(image_id, image['id'])
self.assertIsNone(image['checksum'])
self.assertIsNone(image['size'])
self.assertIsNone(image['virtual_size'])
self.assertEqual('bar', image['foo'])
self.assertTrue(image['protected'])
self.assertEqual('kernel', image['type'])
self.assertTrue(image['created_at'])
self.assertTrue(image['updated_at'])
self.assertEqual(image['updated_at'], image['created_at'])
# The URI file:// should return a 400 rather than a 500
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
url = ('file://')
changes = [{
'op': 'add',
'path': '/locations/-',
'value': {
'url': url,
'metadata': {}
}
}]
data = jsonutils.dumps(changes)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code, response.text)
# The image should be mutable, including adding and removing properties
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
data = jsonutils.dumps([
{'op': 'replace', 'path': '/name', 'value': 'image-2'},
{'op': 'replace', 'path': '/disk_format', 'value': 'vhd'},
{'op': 'replace', 'path': '/container_format', 'value': 'ami'},
{'op': 'replace', 'path': '/foo', 'value': 'baz'},
{'op': 'add', 'path': '/ping', 'value': 'pong'},
{'op': 'replace', 'path': '/protected', 'value': True},
{'op': 'remove', 'path': '/type'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
self.assertEqual('image-2', image['name'])
self.assertEqual('vhd', image['disk_format'])
self.assertEqual('baz', image['foo'])
self.assertEqual('pong', image['ping'])
self.assertTrue(image['protected'])
self.assertNotIn('type', image, response.text)
# Adding 11 image properties should fail since configured limit is 10
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
changes = []
for i in range(11):
changes.append({'op': 'add',
'path': '/ping%i' % i,
'value': 'pong'})
data = jsonutils.dumps(changes)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code,
response.text)
# Adding 3 image locations should fail since configured limit is 2
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
changes = []
for i in range(3):
url = ('http://127.0.0.1:%s/foo_image' %
getattr(self, 'http_port%d' % i))
changes.append({'op': 'add', 'path': '/locations/-',
'value': {'url': url, 'metadata': {}},
})
data = jsonutils.dumps(changes)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code,
response.text)
# Ensure the v2.0 json-patch content type is accepted
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.0-json-patch'
headers = self._headers({'content-type': media_type})
data = jsonutils.dumps([{'add': '/ding', 'value': 'dong'}])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
self.assertEqual('dong', image['ding'])
# Updates should persist across requests
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual(image_id, image['id'])
self.assertEqual('image-2', image['name'])
self.assertEqual('baz', image['foo'])
self.assertEqual('pong', image['ping'])
self.assertTrue(image['protected'])
self.assertNotIn('type', image, response.text)
# Try to download data before its uploaded
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers()
response = requests.get(path, headers=headers)
self.assertEqual(http.NO_CONTENT, response.status_code)
def _verify_image_checksum_and_status(checksum, status):
# Checksum should be populated and status should be active
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual(checksum, image['checksum'])
self.assertEqual(status, image['status'])
# Upload some image data
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
expected_checksum = '8f113e38d28a79a5a451b16048cc2b72'
_verify_image_checksum_and_status(expected_checksum, 'active')
# `disk_format` and `container_format` cannot
# be replaced when the image is active.
immutable_paths = ['/disk_format', '/container_format']
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
path = self._url('/v2/images/%s' % image_id)
for immutable_path in immutable_paths:
data = jsonutils.dumps([
{'op': 'replace', 'path': immutable_path, 'value': 'ari'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Try to download the data that was just uploaded
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
self.assertEqual(expected_checksum, response.headers['Content-MD5'])
self.assertEqual('ZZZZZ', response.text)
# Uploading duplicate data should be rejected with a 409. The
# original data should remain untouched.
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='XXX')
self.assertEqual(http.CONFLICT, response.status_code)
_verify_image_checksum_and_status(expected_checksum, 'active')
# Ensure the size is updated to reflect the data uploaded
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
self.assertEqual(5, jsonutils.loads(response.text)['size'])
# Should be able to deactivate image
path = self._url('/v2/images/%s/actions/deactivate' % image_id)
response = requests.post(path, data={}, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Change the image to public so TENANT2 can see it
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.0-json-patch'
headers = self._headers({'content-type': media_type})
data = jsonutils.dumps([{"replace": "/visibility", "value": "public"}])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Tenant2 should get Forbidden when deactivating the public image
path = self._url('/v2/images/%s/actions/deactivate' % image_id)
response = requests.post(path, data={}, headers=self._headers(
{'X-Tenant-Id': TENANT2}))
self.assertEqual(http.FORBIDDEN, response.status_code)
# Tenant2 should get Forbidden when reactivating the public image
path = self._url('/v2/images/%s/actions/reactivate' % image_id)
response = requests.post(path, data={}, headers=self._headers(
{'X-Tenant-Id': TENANT2}))
self.assertEqual(http.FORBIDDEN, response.status_code)
# Deactivating a deactivated image succeeds (no-op)
path = self._url('/v2/images/%s/actions/deactivate' % image_id)
response = requests.post(path, data={}, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Can't download a deactivated image
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.FORBIDDEN, response.status_code)
# Deactivated image should still be in a listing
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(2, len(images))
self.assertEqual(image2_id, images[0]['id'])
self.assertEqual(image_id, images[1]['id'])
# Should be able to reactivate a deactivated image
path = self._url('/v2/images/%s/actions/reactivate' % image_id)
response = requests.post(path, data={}, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Reactivating an active image succeeds (no-op)
path = self._url('/v2/images/%s/actions/reactivate' % image_id)
response = requests.post(path, data={}, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Deletion should not work on protected images
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.FORBIDDEN, response.status_code)
# Unprotect image for deletion
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
doc = [{'op': 'replace', 'path': '/protected', 'value': False}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Deletion should work. Deleting image-1
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# And neither should its data
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers()
response = requests.get(path, headers=headers)
self.assertEqual(http.NOT_FOUND, response.status_code)
# Image list should now contain just image-2
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Deleting image-2 should work
path = self._url('/v2/images/%s' % image2_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Image list should now be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create image that tries to send True should return 400
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = 'true'
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Create image that tries to send a string should return 400
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = '"hello"'
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Create image that tries to send 123 should return 400
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = '123'
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
self.stop_servers()
def test_update_readonly_prop(self):
self.start_servers(**self.__dict__.copy())
# Create an image (with two deployer-defined properties)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1'})
response = requests.post(path, headers=headers, data=data)
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
props = ['/id', '/file', '/location', '/schema', '/self']
for prop in props:
doc = [{'op': 'replace',
'path': prop,
'value': 'value1'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
for prop in props:
doc = [{'op': 'remove',
'path': prop,
'value': 'value1'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
for prop in props:
doc = [{'op': 'add',
'path': prop,
'value': 'value1'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
self.stop_servers()
def test_methods_that_dont_accept_illegal_bodies(self):
# Check images can be reached
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
# Test all the schemas
schema_urls = [
'/v2/schemas/images',
'/v2/schemas/image',
'/v2/schemas/members',
'/v2/schemas/member',
]
for value in schema_urls:
path = self._url(value)
data = jsonutils.dumps(["body"])
response = requests.get(path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Create image for use with tests
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
test_urls = [
('/v2/images/%s', 'get'),
('/v2/images/%s/actions/deactivate', 'post'),
('/v2/images/%s/actions/reactivate', 'post'),
('/v2/images/%s/tags/mytag', 'put'),
('/v2/images/%s/tags/mytag', 'delete'),
('/v2/images/%s/members', 'get'),
('/v2/images/%s/file', 'get'),
('/v2/images/%s', 'delete'),
]
for link, method in test_urls:
path = self._url(link % image_id)
data = jsonutils.dumps(["body"])
response = getattr(requests, method)(
path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# DELETE /images/imgid without legal json
path = self._url('/v2/images/%s' % image_id)
data = '{"hello"]'
response = requests.delete(path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# POST /images/imgid/members
path = self._url('/v2/images/%s/members' % image_id)
data = jsonutils.dumps({'member': TENANT3})
response = requests.post(path, headers=self._headers(), data=data)
self.assertEqual(http.OK, response.status_code)
# GET /images/imgid/members/memid
path = self._url('/v2/images/%s/members/%s' % (image_id, TENANT3))
data = jsonutils.dumps(["body"])
response = requests.get(path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# DELETE /images/imgid/members/memid
path = self._url('/v2/images/%s/members/%s' % (image_id, TENANT3))
data = jsonutils.dumps(["body"])
response = requests.delete(path, headers=self._headers(), data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code)
self.stop_servers()
def test_download_random_access_w_range_request(self):
"""
Test partial download 'Range' requests for images (random image access)
"""
self.start_servers(**self.__dict__.copy())
# Create an image (with two deployer-defined properties)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-2', 'type': 'kernel',
'bar': 'foo', 'disk_format': 'aki',
'container_format': 'aki', 'xyz': 'abc'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
# Upload data to image
image_data = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data=image_data)
self.assertEqual(http.NO_CONTENT, response.status_code)
# test for success on satisfiable Range request.
range_ = 'bytes=3-10'
headers = self._headers({'Range': range_})
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.PARTIAL_CONTENT, response.status_code)
self.assertEqual('DEFGHIJK', response.text)
# test for failure on unsatisfiable Range request.
range_ = 'bytes=10-5'
headers = self._headers({'Range': range_})
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.REQUESTED_RANGE_NOT_SATISFIABLE,
response.status_code)
self.stop_servers()
def test_download_random_access_w_content_range(self):
"""
Even though Content-Range is incorrect on requests, we support it
for backward compatibility with clients written for pre-Pike Glance.
The following test is for 'Content-Range' requests, which we have
to ensure that we prevent regression.
"""
self.start_servers(**self.__dict__.copy())
# Create another image (with two deployer-defined properties)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-2', 'type': 'kernel',
'bar': 'foo', 'disk_format': 'aki',
'container_format': 'aki', 'xyz': 'abc'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
# Upload data to image
image_data = 'Z' * 15
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data=image_data)
self.assertEqual(http.NO_CONTENT, response.status_code)
result_body = ''
for x in range(15):
# NOTE(flaper87): Read just 1 byte. Content-Range is
# 0-indexed and it specifies the first byte to read
# and the last byte to read.
content_range = 'bytes %s-%s/15' % (x, x)
headers = self._headers({'Content-Range': content_range})
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.PARTIAL_CONTENT, response.status_code)
result_body += response.text
self.assertEqual(result_body, image_data)
# test for failure on unsatisfiable request for ContentRange.
content_range = 'bytes 3-16/15'
headers = self._headers({'Content-Range': content_range})
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.REQUESTED_RANGE_NOT_SATISFIABLE,
response.status_code)
self.stop_servers()
def test_download_policy_when_cache_is_not_enabled(self):
rules = {'context_is_admin': 'role:admin',
'default': '',
'add_image': '',
'get_image': '',
'modify_image': '',
'upload_image': '',
'delete_image': '',
'download_image': '!'}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in six.iteritems(expected_image):
self.assertEqual(value, image[key], key)
# Upload data to image
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Get an image should fail
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.get(path, headers=headers)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_download_image_not_allowed_using_restricted_policy(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "",
"modify_image": "",
"upload_image": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in six.iteritems(expected_image):
self.assertEqual(value, image[key], key)
# Upload data to image
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Get an image should fail
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream',
'X-Roles': '_member_'})
response = requests.get(path, headers=headers)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_download_image_allowed_using_restricted_policy(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in six.iteritems(expected_image):
self.assertEqual(value, image[key], key)
# Upload data to image
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Get an image should be allowed
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream',
'X-Roles': 'member'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
# Image Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_download_image_raises_service_unavailable(self):
"""Test image download returns HTTPServiceUnavailable."""
self.api_server.show_multiple_locations = True
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Update image locations via PATCH
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
http_server_pid, http_port = test_utils.start_http_server(image_id,
"image-1")
values = [{'url': 'http://127.0.0.1:%s/image-1' % http_port,
'metadata': {'idx': '0'}}]
doc = [{'op': 'replace',
'path': '/locations',
'value': values}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
# Download an image should work
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
# Stop http server used to update image location
os.kill(http_server_pid, signal.SIGKILL)
# Download an image should raise HTTPServiceUnavailable
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.SERVICE_UNAVAILABLE, response.status_code)
# Image Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_image_modification_works_for_owning_tenant_id(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "",
"modify_image": "tenant:%(owner)s",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers['content-type'] = media_type
del headers['X-Roles']
data = jsonutils.dumps([
{'op': 'replace', 'path': '/name', 'value': 'new-name'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
self.stop_servers()
def test_image_modification_fails_on_mismatched_tenant_ids(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "",
"modify_image": "'A-Fake-Tenant-Id':%(owner)s",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers['content-type'] = media_type
del headers['X-Roles']
data = jsonutils.dumps([
{'op': 'replace', 'path': '/name', 'value': 'new-name'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
self.stop_servers()
def test_member_additions_works_for_owning_tenant_id(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted",
"add_member": "tenant:%(owner)s",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
# Get the image's members resource
path = self._url('/v2/images/%s/members' % image_id)
body = jsonutils.dumps({'member': TENANT3})
del headers['X-Roles']
response = requests.post(path, headers=headers, data=body)
self.assertEqual(http.OK, response.status_code)
self.stop_servers()
def test_image_additions_works_only_for_specific_tenant_id(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "'{0}':%(owner)s".format(TENANT1),
"get_image": "",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted",
"add_member": "",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin', 'X-Tenant-Id': TENANT1})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
headers['X-Tenant-Id'] = TENANT2
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
self.stop_servers()
def test_owning_tenant_id_can_retrieve_image_information(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "tenant:%(owner)s",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted",
"add_member": "",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin', 'X-Tenant-Id': TENANT1})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Remove the admin role
del headers['X-Roles']
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
# Can retrieve the image as TENANT1
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
# Can retrieve the image's members as TENANT1
path = self._url('/v2/images/%s/members' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
headers['X-Tenant-Id'] = TENANT2
response = requests.get(path, headers=headers)
self.assertEqual(http.FORBIDDEN, response.status_code)
self.stop_servers()
def test_owning_tenant_can_publicize_image(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"publicize_image": "tenant:%(owner)s",
"get_image": "tenant:%(owner)s",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted",
"add_member": "",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin', 'X-Tenant-Id': TENANT1})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'X-Tenant-Id': TENANT1,
})
doc = [{'op': 'replace', 'path': '/visibility', 'value': 'public'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
def test_owning_tenant_can_communitize_image(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"communitize_image": "tenant:%(owner)s",
"get_image": "tenant:%(owner)s",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted",
"add_member": "",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin', 'X-Tenant-Id': TENANT1})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'X-Tenant-Id': TENANT1,
})
doc = [{'op': 'replace', 'path': '/visibility', 'value': 'community'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(200, response.status_code)
def test_owning_tenant_can_delete_image(self):
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"publicize_image": "tenant:%(owner)s",
"get_image": "tenant:%(owner)s",
"modify_image": "",
"upload_image": "",
"get_image_location": "",
"delete_image": "",
"restricted":
"not ('aki':%(container_format)s and role:_member_)",
"download_image": "role:admin or rule:restricted",
"add_member": "",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin', 'X-Tenant-Id': TENANT1})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=headers)
self.assertEqual(http.NO_CONTENT, response.status_code)
def test_list_show_ok_when_get_location_allowed_for_admins(self):
self.api_server.show_image_direct_url = True
self.api_server.show_multiple_locations = True
# setup context to allow a list locations by admin only
rules = {
"context_is_admin": "role:admin",
"default": "",
"add_image": "",
"get_image": "",
"modify_image": "",
"upload_image": "",
"get_image_location": "role:admin",
"delete_image": "",
"restricted": "",
"download_image": "",
"add_member": "",
}
self.set_policy_rules(rules)
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Tenant-Id': TENANT1})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image's ID
image = jsonutils.loads(response.text)
image_id = image['id']
# Can retrieve the image as TENANT1
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
# Can list images as TENANT1
path = self._url('/v2/images')
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
self.stop_servers()
def test_image_size_cap(self):
self.api_server.image_size_cap = 128
self.start_servers(**self.__dict__.copy())
# create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-size-cap-test-image',
'type': 'kernel', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
# try to populate it with oversized data
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
class StreamSim(object):
# Using a one-shot iterator to force chunked transfer in the PUT
# request
def __init__(self, size):
self.size = size
def __iter__(self):
yield b'Z' * self.size
response = requests.put(path, headers=headers, data=StreamSim(
self.api_server.image_size_cap + 1))
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code)
# hashlib.md5('Z'*129).hexdigest()
# == '76522d28cb4418f12704dfa7acd6e7ee'
# If the image has this checksum, it means that the whole stream was
# accepted and written to the store, which should not be the case.
path = self._url('/v2/images/{0}'.format(image_id))
headers = self._headers({'content-type': 'application/json'})
response = requests.get(path, headers=headers)
image_checksum = jsonutils.loads(response.text).get('checksum')
self.assertNotEqual(image_checksum, '76522d28cb4418f12704dfa7acd6e7ee')
def test_permissions(self):
self.start_servers(**self.__dict__.copy())
# Create an image that belongs to TENANT1
path = self._url('/v2/images')
headers = self._headers({'Content-Type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'raw',
'container_format': 'bare'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image_id = jsonutils.loads(response.text)['id']
# Upload some image data
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# TENANT1 should see the image in their list
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(image_id, images[0]['id'])
# TENANT1 should be able to access the image directly
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
# TENANT2 should not see the image in their list
path = self._url('/v2/images')
headers = self._headers({'X-Tenant-Id': TENANT2})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# TENANT2 should not be able to access the image directly
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'X-Tenant-Id': TENANT2})
response = requests.get(path, headers=headers)
self.assertEqual(http.NOT_FOUND, response.status_code)
# TENANT2 should not be able to modify the image, either
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'X-Tenant-Id': TENANT2,
})
doc = [{'op': 'replace', 'path': '/name', 'value': 'image-2'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.NOT_FOUND, response.status_code)
# TENANT2 should not be able to delete the image, either
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'X-Tenant-Id': TENANT2})
response = requests.delete(path, headers=headers)
self.assertEqual(http.NOT_FOUND, response.status_code)
# Publicize the image as an admin of TENANT1
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'X-Roles': 'admin',
})
doc = [{'op': 'replace', 'path': '/visibility', 'value': 'public'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
# TENANT3 should now see the image in their list
path = self._url('/v2/images')
headers = self._headers({'X-Tenant-Id': TENANT3})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(image_id, images[0]['id'])
# TENANT3 should also be able to access the image directly
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'X-Tenant-Id': TENANT3})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
# TENANT3 still should not be able to modify the image
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'X-Tenant-Id': TENANT3,
})
doc = [{'op': 'replace', 'path': '/name', 'value': 'image-2'}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
# TENANT3 should not be able to delete the image, either
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'X-Tenant-Id': TENANT3})
response = requests.delete(path, headers=headers)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image data should still be present after the failed delete
path = self._url('/v2/images/%s/file' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
self.assertEqual(response.text, 'ZZZZZ')
self.stop_servers()
def test_property_protections_with_roles(self):
# Enable property protection
self.api_server.property_protection_file = self.property_file_roles
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image for role member with extra props
# Raises 403 since user is not allowed to set 'foo'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'foo': 'bar',
'disk_format': 'aki',
'container_format': 'aki',
'x_owner_foo': 'o_s_bar'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Create an image for role member without 'foo'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki',
'x_owner_foo': 'o_s_bar'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity should have 'x_owner_foo'
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'x_owner_foo': 'o_s_bar',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Create an image for role spl_role with extra props
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'spl_role'})
data = jsonutils.dumps({'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'spl_create_prop': 'create_bar',
'spl_create_prop_policy': 'create_policy_bar',
'spl_read_prop': 'read_bar',
'spl_update_prop': 'update_bar',
'spl_delete_prop': 'delete_bar',
'spl_delete_empty_prop': ''})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
# Attempt to replace, add and remove properties which are forbidden
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'spl_role'})
data = jsonutils.dumps([
{'op': 'replace', 'path': '/spl_read_prop', 'value': 'r'},
{'op': 'replace', 'path': '/spl_update_prop', 'value': 'u'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
# Attempt to replace, add and remove properties which are forbidden
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'spl_role'})
data = jsonutils.dumps([
{'op': 'add', 'path': '/spl_new_prop', 'value': 'new'},
{'op': 'remove', 'path': '/spl_create_prop'},
{'op': 'remove', 'path': '/spl_delete_prop'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
# Attempt to replace properties
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'spl_role'})
data = jsonutils.dumps([
# Updating an empty property to verify bug #1332103.
{'op': 'replace', 'path': '/spl_update_prop', 'value': ''},
{'op': 'replace', 'path': '/spl_update_prop', 'value': 'u'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
# 'spl_update_prop' has update permission for spl_role
# hence the value has changed
self.assertEqual('u', image['spl_update_prop'])
# Attempt to remove properties
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'spl_role'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/spl_delete_prop'},
# Deleting an empty property to verify bug #1332103.
{'op': 'remove', 'path': '/spl_delete_empty_prop'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
# 'spl_delete_prop' and 'spl_delete_empty_prop' have delete
# permission for spl_role hence the property has been deleted
self.assertNotIn('spl_delete_prop', image.keys())
self.assertNotIn('spl_delete_empty_prop', image.keys())
# Image Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_property_protections_with_policies(self):
# Enable property protection
self.api_server.property_protection_file = self.property_file_policies
self.api_server.property_protection_rule_format = 'policies'
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image for role member with extra props
# Raises 403 since user is not allowed to set 'foo'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'foo': 'bar',
'disk_format': 'aki',
'container_format': 'aki',
'x_owner_foo': 'o_s_bar'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Create an image for role member without 'foo'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'member'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Create an image for role spl_role with extra props
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'spl_role, admin'})
data = jsonutils.dumps({'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'spl_creator_policy': 'creator_bar',
'spl_default_policy': 'default_bar'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertEqual('creator_bar', image['spl_creator_policy'])
self.assertEqual('default_bar', image['spl_default_policy'])
# Attempt to replace a property which is permitted
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
# Updating an empty property to verify bug #1332103.
{'op': 'replace', 'path': '/spl_creator_policy', 'value': ''},
{'op': 'replace', 'path': '/spl_creator_policy', 'value': 'r'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
# 'spl_creator_policy' has update permission for admin
# hence the value has changed
self.assertEqual('r', image['spl_creator_policy'])
# Attempt to replace a property which is forbidden
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'spl_role'})
data = jsonutils.dumps([
{'op': 'replace', 'path': '/spl_creator_policy', 'value': 'z'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
# Attempt to read properties
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'content-type': media_type,
'X-Roles': 'random_role'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
# 'random_role' is allowed read 'spl_default_policy'.
self.assertEqual(image['spl_default_policy'], 'default_bar')
# 'random_role' is forbidden to read 'spl_creator_policy'.
self.assertNotIn('spl_creator_policy', image)
# Attempt to replace and remove properties which are permitted
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
# Deleting an empty property to verify bug #1332103.
{'op': 'replace', 'path': '/spl_creator_policy', 'value': ''},
{'op': 'remove', 'path': '/spl_creator_policy'},
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
# 'spl_creator_policy' has delete permission for admin
# hence the value has been deleted
self.assertNotIn('spl_creator_policy', image)
# Attempt to read a property that is permitted
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'content-type': media_type,
'X-Roles': 'random_role'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
# Returned image entity should reflect the changes
image = jsonutils.loads(response.text)
self.assertEqual(image['spl_default_policy'], 'default_bar')
# Image Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# This image should be no longer be directly accessible
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
self.stop_servers()
def test_property_protections_special_chars_roles(self):
# Enable property protection
self.api_server.property_protection_file = self.property_file_roles
self.start_servers(**self.__dict__.copy())
# Verify both admin and unknown role can create properties marked with
# '@'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_all_permitted_admin': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'x_all_permitted_admin': '1',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_all_permitted_joe_soap': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'x_all_permitted_joe_soap': '1',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Verify both admin and unknown role can read properties marked with
# '@'
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual('1', image['x_all_permitted_joe_soap'])
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual('1', image['x_all_permitted_joe_soap'])
# Verify both admin and unknown role can update properties marked with
# '@'
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_all_permitted_joe_soap', 'value': '2'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertEqual('2', image['x_all_permitted_joe_soap'])
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_all_permitted_joe_soap', 'value': '3'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertEqual('3', image['x_all_permitted_joe_soap'])
# Verify both admin and unknown role can delete properties marked with
# '@'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_all_permitted_a': '1',
'x_all_permitted_b': '2'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_all_permitted_a'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertNotIn('x_all_permitted_a', image.keys())
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_all_permitted_b'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertNotIn('x_all_permitted_b', image.keys())
# Verify neither admin nor unknown role can create a property protected
# with '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_permitted_admin': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_permitted_joe_soap': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Verify neither admin nor unknown role can read properties marked with
# '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_read': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertNotIn('x_none_read', image.keys())
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertNotIn('x_none_read', image.keys())
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertNotIn('x_none_read', image.keys())
# Verify neither admin nor unknown role can update properties marked
# with '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_update': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertEqual('1', image['x_none_update'])
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_none_update', 'value': '2'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_none_update', 'value': '3'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code, response.text)
# Verify neither admin nor unknown role can delete properties marked
# with '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_delete': '1',
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_none_delete'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_none_delete'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code, response.text)
self.stop_servers()
def test_property_protections_special_chars_policies(self):
# Enable property protection
self.api_server.property_protection_file = self.property_file_policies
self.api_server.property_protection_rule_format = 'policies'
self.start_servers(**self.__dict__.copy())
# Verify both admin and unknown role can create properties marked with
# '@'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_all_permitted_admin': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'x_all_permitted_admin': '1',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_all_permitted_joe_soap': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
expected_image = {
'status': 'queued',
'name': 'image-1',
'tags': [],
'visibility': 'shared',
'self': '/v2/images/%s' % image_id,
'protected': False,
'file': '/v2/images/%s/file' % image_id,
'min_disk': 0,
'x_all_permitted_joe_soap': '1',
'min_ram': 0,
'schema': '/v2/schemas/image',
}
for key, value in expected_image.items():
self.assertEqual(value, image[key], key)
# Verify both admin and unknown role can read properties marked with
# '@'
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual('1', image['x_all_permitted_joe_soap'])
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual('1', image['x_all_permitted_joe_soap'])
# Verify both admin and unknown role can update properties marked with
# '@'
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_all_permitted_joe_soap', 'value': '2'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertEqual('2', image['x_all_permitted_joe_soap'])
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_all_permitted_joe_soap', 'value': '3'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertEqual('3', image['x_all_permitted_joe_soap'])
# Verify both admin and unknown role can delete properties marked with
# '@'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_all_permitted_a': '1',
'x_all_permitted_b': '2'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_all_permitted_a'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertNotIn('x_all_permitted_a', image.keys())
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_all_permitted_b'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
image = jsonutils.loads(response.text)
self.assertNotIn('x_all_permitted_b', image.keys())
# Verify neither admin nor unknown role can create a property protected
# with '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_permitted_admin': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_permitted_joe_soap': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Verify neither admin nor unknown role can read properties marked with
# '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_read': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertNotIn('x_none_read', image.keys())
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertNotIn('x_none_read', image.keys())
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'joe_soap'})
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertNotIn('x_none_read', image.keys())
# Verify neither admin nor unknown role can update properties marked
# with '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_update': '1'
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertEqual('1', image['x_none_update'])
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_none_update', 'value': '2'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'replace',
'path': '/x_none_update', 'value': '3'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code, response.text)
# Verify neither admin nor unknown role can delete properties marked
# with '!'
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json',
'X-Roles': 'admin'})
data = jsonutils.dumps({
'name': 'image-1',
'disk_format': 'aki',
'container_format': 'aki',
'x_none_delete': '1',
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'admin'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_none_delete'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.FORBIDDEN, response.status_code, response.text)
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type,
'X-Roles': 'joe_soap'})
data = jsonutils.dumps([
{'op': 'remove', 'path': '/x_none_delete'}
])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code, response.text)
self.stop_servers()
def test_tag_lifecycle(self):
self.start_servers(**self.__dict__.copy())
# Create an image with a tag - duplicate should be ignored
path = self._url('/v2/images')
headers = self._headers({'Content-Type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'tags': ['sniff', 'sniff']})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image_id = jsonutils.loads(response.text)['id']
# Image should show a list with a single tag
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['sniff'], tags)
# Delete all tags
for tag in tags:
path = self._url('/v2/images/%s/tags/%s' % (image_id, tag))
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Update image with too many tags via PUT
# Configured limit is 10 tags
for i in range(10):
path = self._url('/v2/images/%s/tags/foo%i' % (image_id, i))
response = requests.put(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# 11th tag should fail
path = self._url('/v2/images/%s/tags/fail_me' % image_id)
response = requests.put(path, headers=self._headers())
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code)
# Make sure the 11th tag was not added
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(10, len(tags))
# Update image tags via PATCH
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
doc = [
{
'op': 'replace',
'path': '/tags',
'value': ['foo'],
},
]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
# Update image with too many tags via PATCH
# Configured limit is 10 tags
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
tags = ['foo%d' % i for i in range(11)]
doc = [
{
'op': 'replace',
'path': '/tags',
'value': tags,
},
]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code)
# Tags should not have changed since request was over limit
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['foo'], tags)
# Update image with duplicate tag - it should be ignored
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
doc = [
{
'op': 'replace',
'path': '/tags',
'value': ['sniff', 'snozz', 'snozz'],
},
]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['sniff', 'snozz'], sorted(tags))
# Image should show the appropriate tags
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['sniff', 'snozz'], sorted(tags))
# Attempt to tag the image with a duplicate should be ignored
path = self._url('/v2/images/%s/tags/snozz' % image_id)
response = requests.put(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Create another more complex tag
path = self._url('/v2/images/%s/tags/gabe%%40example.com' % image_id)
response = requests.put(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Double-check that the tags container on the image is populated
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['gabe@example.com', 'sniff', 'snozz'],
sorted(tags))
# Query images by single tag
path = self._url('/v2/images?tag=sniff')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual('image-1', images[0]['name'])
# Query images by multiple tags
path = self._url('/v2/images?tag=sniff&tag=snozz')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual('image-1', images[0]['name'])
# Query images by tag and other attributes
path = self._url('/v2/images?tag=sniff&status=queued')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual('image-1', images[0]['name'])
# Query images by tag and a nonexistent tag
path = self._url('/v2/images?tag=sniff&tag=fake')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# The tag should be deletable
path = self._url('/v2/images/%s/tags/gabe%%40example.com' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# List of tags should reflect the deletion
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['sniff', 'snozz'], sorted(tags))
# Deleting the same tag should return a 404
path = self._url('/v2/images/%s/tags/gabe%%40example.com' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# The tags won't be able to query the images after deleting
path = self._url('/v2/images?tag=gabe%%40example.com')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Try to add a tag that is too long
big_tag = 'a' * 300
path = self._url('/v2/images/%s/tags/%s' % (image_id, big_tag))
response = requests.put(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Tags should not have changed since request was over limit
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(['sniff', 'snozz'], sorted(tags))
self.stop_servers()
def test_images_container(self):
# Image list should be empty and no next link should be present
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
first = jsonutils.loads(response.text)['first']
self.assertEqual(0, len(images))
self.assertNotIn('next', jsonutils.loads(response.text))
self.assertEqual('/v2/images', first)
# Create 7 images
images = []
fixtures = [
{'name': 'image-3', 'type': 'kernel', 'ping': 'pong',
'container_format': 'ami', 'disk_format': 'ami'},
{'name': 'image-4', 'type': 'kernel', 'ping': 'pong',
'container_format': 'bare', 'disk_format': 'ami'},
{'name': 'image-1', 'type': 'kernel', 'ping': 'pong'},
{'name': 'image-3', 'type': 'ramdisk', 'ping': 'pong'},
{'name': 'image-2', 'type': 'kernel', 'ping': 'ding'},
{'name': 'image-3', 'type': 'kernel', 'ping': 'pong'},
{'name': 'image-2,image-5', 'type': 'kernel', 'ping': 'pong'},
]
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
for fixture in fixtures:
data = jsonutils.dumps(fixture)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
images.append(jsonutils.loads(response.text))
# Image list should contain 7 images
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(7, len(body['images']))
self.assertEqual('/v2/images', body['first'])
self.assertNotIn('next', jsonutils.loads(response.text))
# Image list filters by created_at time
url_template = '/v2/images?created_at=lt:%s'
path = self._url(url_template % images[0]['created_at'])
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(0, len(body['images']))
self.assertEqual(url_template % images[0]['created_at'],
urllib.parse.unquote(body['first']))
# Image list filters by updated_at time
url_template = '/v2/images?updated_at=lt:%s'
path = self._url(url_template % images[2]['updated_at'])
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertGreaterEqual(3, len(body['images']))
self.assertEqual(url_template % images[2]['updated_at'],
urllib.parse.unquote(body['first']))
# Image list filters by updated_at and created time with invalid value
url_template = '/v2/images?%s=lt:invalid_value'
for filter in ['updated_at', 'created_at']:
path = self._url(url_template % filter)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Image list filters by updated_at and created_at with invalid operator
url_template = '/v2/images?%s=invalid_operator:2015-11-19T12:24:02Z'
for filter in ['updated_at', 'created_at']:
path = self._url(url_template % filter)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Image list filters by non-'URL encoding' value
path = self._url('/v2/images?name=%FF')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Image list filters by name with in operator
url_template = '/v2/images?name=in:%s'
filter_value = 'image-1,image-2'
path = self._url(url_template % filter_value)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertGreaterEqual(3, len(body['images']))
# Image list filters by container_format with in operator
url_template = '/v2/images?container_format=in:%s'
filter_value = 'bare,ami'
path = self._url(url_template % filter_value)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertGreaterEqual(2, len(body['images']))
# Image list filters by disk_format with in operator
url_template = '/v2/images?disk_format=in:%s'
filter_value = 'bare,ami,iso'
path = self._url(url_template % filter_value)
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertGreaterEqual(2, len(body['images']))
# Begin pagination after the first image
template_url = ('/v2/images?limit=2&sort_dir=asc&sort_key=name'
'&marker=%s&type=kernel&ping=pong')
path = self._url(template_url % images[2]['id'])
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(2, len(body['images']))
response_ids = [image['id'] for image in body['images']]
self.assertEqual([images[6]['id'], images[0]['id']], response_ids)
# Continue pagination using next link from previous request
path = self._url(body['next'])
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(2, len(body['images']))
response_ids = [image['id'] for image in body['images']]
self.assertEqual([images[5]['id'], images[1]['id']], response_ids)
# Continue pagination - expect no results
path = self._url(body['next'])
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(0, len(body['images']))
# Delete first image
path = self._url('/v2/images/%s' % images[0]['id'])
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# Ensure bad request for using a deleted image as marker
path = self._url('/v2/images?marker=%s' % images[0]['id'])
response = requests.get(path, headers=self._headers())
self.assertEqual(http.BAD_REQUEST, response.status_code)
self.stop_servers()
def test_image_visibility_to_different_users(self):
self.cleanup()
self.api_server.deployment_flavor = 'fakeauth'
self.registry_server.deployment_flavor = 'fakeauth'
kwargs = self.__dict__.copy()
kwargs['use_user_token'] = True
self.start_servers(**kwargs)
owners = ['admin', 'tenant1', 'tenant2', 'none']
visibilities = ['public', 'private', 'shared', 'community']
for owner in owners:
for visibility in visibilities:
path = self._url('/v2/images')
headers = self._headers({
'content-type': 'application/json',
'X-Auth-Token': 'createuser:%s:admin' % owner,
})
data = jsonutils.dumps({
'name': '%s-%s' % (owner, visibility),
'visibility': visibility,
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
def list_images(tenant, role='', visibility=None):
auth_token = 'user:%s:%s' % (tenant, role)
headers = {'X-Auth-Token': auth_token}
path = self._url('/v2/images')
if visibility is not None:
path += '?visibility=%s' % visibility
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
return jsonutils.loads(response.text)['images']
# 1. Known user sees public and their own images
images = list_images('tenant1')
self.assertEqual(7, len(images))
for image in images:
self.assertTrue(image['visibility'] == 'public'
or 'tenant1' in image['name'])
# 2. Known user, visibility=public, sees all public images
images = list_images('tenant1', visibility='public')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('public', image['visibility'])
# 3. Known user, visibility=private, sees only their private image
images = list_images('tenant1', visibility='private')
self.assertEqual(1, len(images))
image = images[0]
self.assertEqual('private', image['visibility'])
self.assertIn('tenant1', image['name'])
# 4. Known user, visibility=shared, sees only their shared image
images = list_images('tenant1', visibility='shared')
self.assertEqual(1, len(images))
image = images[0]
self.assertEqual('shared', image['visibility'])
self.assertIn('tenant1', image['name'])
# 5. Known user, visibility=community, sees all community images
images = list_images('tenant1', visibility='community')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('community', image['visibility'])
# 6. Unknown user sees only public images
images = list_images('none')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('public', image['visibility'])
# 7. Unknown user, visibility=public, sees only public images
images = list_images('none', visibility='public')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('public', image['visibility'])
# 8. Unknown user, visibility=private, sees no images
images = list_images('none', visibility='private')
self.assertEqual(0, len(images))
# 9. Unknown user, visibility=shared, sees no images
images = list_images('none', visibility='shared')
self.assertEqual(0, len(images))
# 10. Unknown user, visibility=community, sees only community images
images = list_images('none', visibility='community')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('community', image['visibility'])
# 11. Unknown admin sees all images except for community images
images = list_images('none', role='admin')
self.assertEqual(12, len(images))
# 12. Unknown admin, visibility=public, shows only public images
images = list_images('none', role='admin', visibility='public')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('public', image['visibility'])
# 13. Unknown admin, visibility=private, sees only private images
images = list_images('none', role='admin', visibility='private')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('private', image['visibility'])
# 14. Unknown admin, visibility=shared, sees only shared images
images = list_images('none', role='admin', visibility='shared')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('shared', image['visibility'])
# 15. Unknown admin, visibility=community, sees only community images
images = list_images('none', role='admin', visibility='community')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('community', image['visibility'])
# 16. Known admin sees all images, except community images owned by
# others
images = list_images('admin', role='admin')
self.assertEqual(13, len(images))
# 17. Known admin, visibility=public, sees all public images
images = list_images('admin', role='admin', visibility='public')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('public', image['visibility'])
# 18. Known admin, visibility=private, sees all private images
images = list_images('admin', role='admin', visibility='private')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('private', image['visibility'])
# 19. Known admin, visibility=shared, sees all shared images
images = list_images('admin', role='admin', visibility='shared')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('shared', image['visibility'])
# 20. Known admin, visibility=community, sees all community images
images = list_images('admin', role='admin', visibility='community')
self.assertEqual(4, len(images))
for image in images:
self.assertEqual('community', image['visibility'])
self.stop_servers()
def test_update_locations(self):
self.api_server.show_multiple_locations = True
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity should have a generated id and status
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertEqual('queued', image['status'])
self.assertIsNone(image['size'])
self.assertIsNone(image['virtual_size'])
# Update locations for the queued image
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
'value': [{'url': url, 'metadata': {}}]
}])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# The image size should be updated
path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertEqual(10, image['size'])
def test_update_locations_with_restricted_sources(self):
self.api_server.show_multiple_locations = True
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Returned image entity should have a generated id and status
image = jsonutils.loads(response.text)
image_id = image['id']
self.assertEqual('queued', image['status'])
self.assertIsNone(image['size'])
self.assertIsNone(image['virtual_size'])
# Update locations for the queued image
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
'value': [{'url': 'file:///foo_image',
'metadata': {}}]
}])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code, response.text)
data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
'value': [{'url': 'swift+config:///foo_image',
'metadata': {}}]
}])
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.BAD_REQUEST, response.status_code, response.text)
class TestImagesWithRegistry(TestImages):
def setUp(self):
super(TestImagesWithRegistry, self).setUp()
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
class TestImagesIPv6(functional.FunctionalTest):
"""Verify that API and REG servers running IPv6 can communicate"""
def setUp(self):
"""
First applying monkey patches of functions and methods which have
IPv4 hardcoded.
"""
# Setting up initial monkey patch (1)
test_utils.get_unused_port_ipv4 = test_utils.get_unused_port
test_utils.get_unused_port_and_socket_ipv4 = (
test_utils.get_unused_port_and_socket)
test_utils.get_unused_port = test_utils.get_unused_port_ipv6
test_utils.get_unused_port_and_socket = (
test_utils.get_unused_port_and_socket_ipv6)
super(TestImagesIPv6, self).setUp()
self.cleanup()
# Setting up monkey patch (2), after object is ready...
self.ping_server_ipv4 = self.ping_server
self.ping_server = self.ping_server_ipv6
self.include_scrubber = False
def tearDown(self):
# Cleaning up monkey patch (2).
self.ping_server = self.ping_server_ipv4
super(TestImagesIPv6, self).tearDown()
# Cleaning up monkey patch (1).
test_utils.get_unused_port = test_utils.get_unused_port_ipv4
test_utils.get_unused_port_and_socket = (
test_utils.get_unused_port_and_socket_ipv4)
def _url(self, path):
return "http://[::1]:%d%s" % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_image_list_ipv6(self):
# Image list should be empty
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
# Setting up configuration parameters properly
# (bind_host is not needed since it is replaced by monkey patches,
# but it would be reflected in the configuration file, which is
# at least improving consistency)
self.registry_server.bind_host = "::1"
self.api_server.bind_host = "::1"
self.api_server.registry_host = "::1"
self.scrubber_daemon.registry_host = "::1"
self.start_servers(**self.__dict__.copy())
requests.get(self._url('/'), headers=self._headers())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
class TestImageDirectURLVisibility(functional.FunctionalTest):
def setUp(self):
super(TestImageDirectURLVisibility, self).setUp()
self.cleanup()
self.include_scrubber = False
self.api_server.deployment_flavor = 'noauth'
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_v2_not_enabled(self):
self.api_server.enable_v2_api = False
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
self.stop_servers()
def test_v2_enabled(self):
self.api_server.enable_v2_api = True
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
self.stop_servers()
def test_image_direct_url_visible(self):
self.api_server.show_image_direct_url = True
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki',
'visibility': 'public'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Image direct_url should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertNotIn('direct_url', image)
# Upload some image data, setting the image location
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Image direct_url should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertIn('direct_url', image)
# Image direct_url should be visible to non-owner, non-admin user
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json',
'X-Tenant-Id': TENANT2})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertIn('direct_url', image)
# Image direct_url should be visible in a list
path = self._url('/v2/images')
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)['images'][0]
self.assertIn('direct_url', image)
self.stop_servers()
def test_image_multiple_location_url_visible(self):
self.api_server.show_multiple_locations = True
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Image locations should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertIn('locations', image)
self.assertEqual([], image["locations"])
# Upload some image data, setting the image location
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Image locations should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertIn('locations', image)
loc = image['locations']
self.assertGreater(len(loc), 0)
loc = loc[0]
self.assertIn('url', loc)
self.assertIn('metadata', loc)
self.stop_servers()
def test_image_direct_url_not_visible(self):
self.api_server.show_image_direct_url = False
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Upload some image data, setting the image location
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(http.NO_CONTENT, response.status_code)
# Image direct_url should not be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertNotIn('direct_url', image)
# Image direct_url should not be visible in a list
path = self._url('/v2/images')
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)['images'][0]
self.assertNotIn('direct_url', image)
self.stop_servers()
class TestImageDirectURLVisibilityWithRegistry(TestImageDirectURLVisibility):
def setUp(self):
super(TestImageDirectURLVisibilityWithRegistry, self).setUp()
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
class TestImageLocationSelectionStrategy(functional.FunctionalTest):
def setUp(self):
super(TestImageLocationSelectionStrategy, self).setUp()
self.cleanup()
self.include_scrubber = False
self.api_server.deployment_flavor = 'noauth'
for i in range(3):
ret = test_utils.start_http_server("foo_image_id%d" % i,
"foo_image%d" % i)
setattr(self, 'http_server%d_pid' % i, ret[0])
setattr(self, 'http_port%d' % i, ret[1])
def tearDown(self):
for i in range(3):
pid = getattr(self, 'http_server%d_pid' % i, None)
if pid:
os.kill(pid, signal.SIGKILL)
super(TestImageLocationSelectionStrategy, self).tearDown()
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_image_locations_with_order_strategy(self):
self.api_server.show_image_direct_url = True
self.api_server.show_multiple_locations = True
self.image_location_quota = 10
self.api_server.location_strategy = 'location_order'
preference = "http, swift, filesystem"
self.api_server.store_type_location_strategy_preference = preference
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Get the image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Image locations should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertIn('locations', image)
self.assertEqual([], image["locations"])
# Update image locations via PATCH
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
'metadata': {}},
{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
'metadata': {}}]
doc = [{'op': 'replace',
'path': '/locations',
'value': values}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code)
# Image locations should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(http.OK, response.status_code)
image = jsonutils.loads(response.text)
self.assertIn('locations', image)
self.assertEqual(values, image['locations'])
self.assertIn('direct_url', image)
self.assertEqual(values[0]['url'], image['direct_url'])
self.stop_servers()
class TestImageLocationSelectionStrategyWithRegistry(
TestImageLocationSelectionStrategy):
def setUp(self):
super(TestImageLocationSelectionStrategyWithRegistry, self).setUp()
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
class TestImageMembers(functional.FunctionalTest):
def setUp(self):
super(TestImageMembers, self).setUp()
self.cleanup()
self.include_scrubber = False
self.api_server.deployment_flavor = 'fakeauth'
self.registry_server.deployment_flavor = 'fakeauth'
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_image_member_lifecycle(self):
def get_header(tenant, role=''):
auth_token = 'user:%s:%s' % (tenant, role)
headers = {'X-Auth-Token': auth_token}
return headers
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
owners = ['tenant1', 'tenant2', 'admin']
visibilities = ['community', 'private', 'public', 'shared']
image_fixture = []
for owner in owners:
for visibility in visibilities:
path = self._url('/v2/images')
headers = self._headers({
'content-type': 'application/json',
'X-Auth-Token': 'createuser:%s:admin' % owner,
})
data = jsonutils.dumps({
'name': '%s-%s' % (owner, visibility),
'visibility': visibility,
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image_fixture.append(jsonutils.loads(response.text))
# Image list should contain 6 images for tenant1
path = self._url('/v2/images')
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(6, len(images))
# Image list should contain 3 images for TENANT3
path = self._url('/v2/images')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(3, len(images))
# Add Image member for tenant1-shared image
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
body = jsonutils.dumps({'member': TENANT3})
response = requests.post(path, headers=get_header('tenant1'),
data=body)
self.assertEqual(http.OK, response.status_code)
image_member = jsonutils.loads(response.text)
self.assertEqual(image_fixture[3]['id'], image_member['image_id'])
self.assertEqual(TENANT3, image_member['member_id'])
self.assertIn('created_at', image_member)
self.assertIn('updated_at', image_member)
self.assertEqual('pending', image_member['status'])
# Image list should contain 3 images for TENANT3
path = self._url('/v2/images')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(3, len(images))
# Image list should contain 0 shared images for TENANT3
# because default is accepted
path = self._url('/v2/images?visibility=shared')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Image list should contain 4 images for TENANT3 with status pending
path = self._url('/v2/images?member_status=pending')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(4, len(images))
# Image list should contain 4 images for TENANT3 with status all
path = self._url('/v2/images?member_status=all')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(4, len(images))
# Image list should contain 1 image for TENANT3 with status pending
# and visibility shared
path = self._url('/v2/images?member_status=pending&visibility=shared')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(1, len(images))
self.assertEqual(images[0]['name'], 'tenant1-shared')
# Image list should contain 0 image for TENANT3 with status rejected
# and visibility shared
path = self._url('/v2/images?member_status=rejected&visibility=shared')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Image list should contain 0 image for TENANT3 with status accepted
# and visibility shared
path = self._url('/v2/images?member_status=accepted&visibility=shared')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Image list should contain 0 image for TENANT3 with status accepted
# and visibility private
path = self._url('/v2/images?visibility=private')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Image tenant2-shared's image members list should contain no members
path = self._url('/v2/images/%s/members' % image_fixture[7]['id'])
response = requests.get(path, headers=get_header('tenant2'))
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(0, len(body['members']))
# Tenant 1, who is the owner cannot change status of image member
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
body = jsonutils.dumps({'status': 'accepted'})
response = requests.put(path, headers=get_header('tenant1'), data=body)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Tenant 1, who is the owner can get status of its own image member
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual('pending', body['status'])
self.assertEqual(image_fixture[3]['id'], body['image_id'])
self.assertEqual(TENANT3, body['member_id'])
# Tenant 3, who is the member can get status of its own status
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual('pending', body['status'])
self.assertEqual(image_fixture[3]['id'], body['image_id'])
self.assertEqual(TENANT3, body['member_id'])
# Tenant 2, who not the owner cannot get status of image member
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
response = requests.get(path, headers=get_header('tenant2'))
self.assertEqual(http.NOT_FOUND, response.status_code)
# Tenant 3 can change status of image member
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
body = jsonutils.dumps({'status': 'accepted'})
response = requests.put(path, headers=get_header(TENANT3), data=body)
self.assertEqual(http.OK, response.status_code)
image_member = jsonutils.loads(response.text)
self.assertEqual(image_fixture[3]['id'], image_member['image_id'])
self.assertEqual(TENANT3, image_member['member_id'])
self.assertEqual('accepted', image_member['status'])
# Image list should contain 4 images for TENANT3 because status is
# accepted
path = self._url('/v2/images')
response = requests.get(path, headers=get_header(TENANT3))
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(4, len(images))
# Tenant 3 invalid status change
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
body = jsonutils.dumps({'status': 'invalid-status'})
response = requests.put(path, headers=get_header(TENANT3), data=body)
self.assertEqual(http.BAD_REQUEST, response.status_code)
# Owner cannot change status of image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
body = jsonutils.dumps({'status': 'accepted'})
response = requests.put(path, headers=get_header('tenant1'), data=body)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Add Image member for tenant2-shared image
path = self._url('/v2/images/%s/members' % image_fixture[7]['id'])
body = jsonutils.dumps({'member': TENANT4})
response = requests.post(path, headers=get_header('tenant2'),
data=body)
self.assertEqual(http.OK, response.status_code)
image_member = jsonutils.loads(response.text)
self.assertEqual(image_fixture[7]['id'], image_member['image_id'])
self.assertEqual(TENANT4, image_member['member_id'])
self.assertIn('created_at', image_member)
self.assertIn('updated_at', image_member)
# Add Image member to public image
path = self._url('/v2/images/%s/members' % image_fixture[2]['id'])
body = jsonutils.dumps({'member': TENANT2})
response = requests.post(path, headers=get_header('tenant1'),
data=body)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Add Image member to private image
path = self._url('/v2/images/%s/members' % image_fixture[1]['id'])
body = jsonutils.dumps({'member': TENANT2})
response = requests.post(path, headers=get_header('tenant1'),
data=body)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Add Image member to community image
path = self._url('/v2/images/%s/members' % image_fixture[0]['id'])
body = jsonutils.dumps({'member': TENANT2})
response = requests.post(path, headers=get_header('tenant1'),
data=body)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image tenant1-shared's members list should contain 1 member
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(1, len(body['members']))
# Admin can see any members
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
response = requests.get(path, headers=get_header('tenant1', 'admin'))
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(1, len(body['members']))
# Image members not found for private image not owned by TENANT 1
path = self._url('/v2/images/%s/members' % image_fixture[7]['id'])
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.NOT_FOUND, response.status_code)
# Image members forbidden for public image
path = self._url('/v2/images/%s/members' % image_fixture[2]['id'])
response = requests.get(path, headers=get_header('tenant1'))
self.assertIn("Only shared images have members", response.text)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image members forbidden for community image
path = self._url('/v2/images/%s/members' % image_fixture[0]['id'])
response = requests.get(path, headers=get_header('tenant1'))
self.assertIn("Only shared images have members", response.text)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image members forbidden for private image
path = self._url('/v2/images/%s/members' % image_fixture[1]['id'])
response = requests.get(path, headers=get_header('tenant1'))
self.assertIn("Only shared images have members", response.text)
self.assertEqual(http.FORBIDDEN, response.status_code)
# Image Member Cannot delete Image membership
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
response = requests.delete(path, headers=get_header(TENANT3))
self.assertEqual(http.FORBIDDEN, response.status_code)
# Delete Image member
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
TENANT3))
response = requests.delete(path, headers=get_header('tenant1'))
self.assertEqual(http.NO_CONTENT, response.status_code)
# Now the image has no members
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.OK, response.status_code)
body = jsonutils.loads(response.text)
self.assertEqual(0, len(body['members']))
# Adding 11 image members should fail since configured limit is 10
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
for i in range(10):
body = jsonutils.dumps({'member': str(uuid.uuid4())})
response = requests.post(path, headers=get_header('tenant1'),
data=body)
self.assertEqual(http.OK, response.status_code)
body = jsonutils.dumps({'member': str(uuid.uuid4())})
response = requests.post(path, headers=get_header('tenant1'),
data=body)
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code)
# Get Image member should return not found for public image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[2]['id'],
TENANT3))
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.NOT_FOUND, response.status_code)
# Get Image member should return not found for community image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[0]['id'],
TENANT3))
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.NOT_FOUND, response.status_code)
# Get Image member should return not found for private image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[1]['id'],
TENANT3))
response = requests.get(path, headers=get_header('tenant1'))
self.assertEqual(http.NOT_FOUND, response.status_code)
# Delete Image member should return forbidden for public image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[2]['id'],
TENANT3))
response = requests.delete(path, headers=get_header('tenant1'))
self.assertEqual(http.FORBIDDEN, response.status_code)
# Delete Image member should return forbidden for community image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[0]['id'],
TENANT3))
response = requests.delete(path, headers=get_header('tenant1'))
self.assertEqual(http.FORBIDDEN, response.status_code)
# Delete Image member should return forbidden for private image
path = self._url('/v2/images/%s/members/%s' % (image_fixture[1]['id'],
TENANT3))
response = requests.delete(path, headers=get_header('tenant1'))
self.assertEqual(http.FORBIDDEN, response.status_code)
self.stop_servers()
class TestImageMembersWithRegistry(TestImageMembers):
def setUp(self):
super(TestImageMembersWithRegistry, self).setUp()
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
class TestQuotas(functional.FunctionalTest):
def setUp(self):
super(TestQuotas, self).setUp()
self.cleanup()
self.include_scrubber = False
self.api_server.deployment_flavor = 'noauth'
self.registry_server.deployment_flavor = 'trusted-auth'
self.user_storage_quota = 100
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def _upload_image_test(self, data_src, expected_status):
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image (with a deployer-defined property)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'testimg',
'type': 'kernel',
'foo': 'bar',
'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
# upload data
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data=data_src)
self.assertEqual(expected_status, response.status_code)
# Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
def test_image_upload_under_quota(self):
data = b'x' * (self.user_storage_quota - 1)
self._upload_image_test(data, http.NO_CONTENT)
def test_image_upload_exceed_quota(self):
data = b'x' * (self.user_storage_quota + 1)
self._upload_image_test(data, http.REQUEST_ENTITY_TOO_LARGE)
def test_chunked_image_upload_under_quota(self):
def data_gen():
yield b'x' * (self.user_storage_quota - 1)
self._upload_image_test(data_gen(), http.NO_CONTENT)
def test_chunked_image_upload_exceed_quota(self):
def data_gen():
yield b'x' * (self.user_storage_quota + 1)
self._upload_image_test(data_gen(), http.REQUEST_ENTITY_TOO_LARGE)
class TestQuotasWithRegistry(TestQuotas):
def setUp(self):
super(TestQuotasWithRegistry, self).setUp()
self.api_server.data_api = (
'glance.tests.functional.v2.registry_data_api')
self.registry_server.deployment_flavor = 'trusted-auth'
glance-16.0.1/glance/tests/functional/v2/test_metadef_objects.py 0000666 0001750 0001750 00000026342 13267672245 024714 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import uuid
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
class TestMetadefObjects(functional.FunctionalTest):
def setUp(self):
super(TestMetadefObjects, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'admin',
}
base_headers.update(custom_headers or {})
return base_headers
def test_metadata_objects_lifecycle(self):
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a namespace
path = self._url('/v2/metadefs/namespaces')
headers = self._headers({'content-type': 'application/json'})
namespace_name = 'MyNamespace'
data = jsonutils.dumps({
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": False,
"owner": "The Test Owner"
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Metadata objects should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace/objects/object1')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a object
path = self._url('/v2/metadefs/namespaces/MyNamespace/objects')
headers = self._headers({'content-type': 'application/json'})
metadata_object_name = "object1"
data = jsonutils.dumps(
{
"name": metadata_object_name,
"description": "object1 description.",
"required": [
"property1"
],
"properties": {
"property1": {
"type": "integer",
"title": "property1",
"description": "property1 description",
"operators": [""],
"default": 100,
"minimum": 100,
"maximum": 30000369
},
"property2": {
"type": "string",
"title": "property2",
"description": "property2 description ",
"default": "value2",
"minLength": 2,
"maxLength": 50
}
}
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Attempt to insert a duplicate
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code)
# Get the metadata object created above
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.get(path,
headers=self._headers())
self.assertEqual(http.OK, response.status_code)
metadata_object = jsonutils.loads(response.text)
self.assertEqual("object1", metadata_object['name'])
# Returned object should match the created object
metadata_object = jsonutils.loads(response.text)
checked_keys = set([
u'name',
u'description',
u'properties',
u'required',
u'self',
u'schema',
u'created_at',
u'updated_at'
])
self.assertEqual(set(metadata_object.keys()), checked_keys)
expected_metadata_object = {
"name": metadata_object_name,
"description": "object1 description.",
"required": [
"property1"
],
"properties": {
'property1': {
'type': 'integer',
"title": "property1",
'description': 'property1 description',
'operators': [''],
'default': 100,
'minimum': 100,
'maximum': 30000369
},
"property2": {
"type": "string",
"title": "property2",
"description": "property2 description ",
"default": "value2",
"minLength": 2,
"maxLength": 50
}
},
"self": "/v2/metadefs/namespaces/%("
"namespace)s/objects/%(object)s" %
{'namespace': namespace_name,
'object': metadata_object_name},
"schema": "v2/schemas/metadefs/object"
}
# Simple key values
checked_values = set([
u'name',
u'description',
])
for key, value in expected_metadata_object.items():
if(key in checked_values):
self.assertEqual(metadata_object[key], value, key)
# Complex key values - properties
for key, value in (
expected_metadata_object["properties"]['property2'].items()):
self.assertEqual(
metadata_object["properties"]["property2"][key],
value, key
)
# The metadata_object should be mutable
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
metadata_object_name = "object1-UPDATED"
data = jsonutils.dumps(
{
"name": metadata_object_name,
"description": "desc-UPDATED",
"required": [
"property2"
],
"properties": {
'property1': {
'type': 'integer',
"title": "property1",
'description': 'p1 desc-UPDATED',
'default': 500,
'minimum': 500,
'maximum': 1369
},
"property2": {
"type": "string",
"title": "property2",
"description": "p2 desc-UPDATED",
'operators': [''],
"default": "value2-UPDATED",
"minLength": 5,
"maxLength": 150
}
}
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned metadata_object should reflect the changes
metadata_object = jsonutils.loads(response.text)
self.assertEqual('object1-UPDATED', metadata_object['name'])
self.assertEqual('desc-UPDATED', metadata_object['description'])
self.assertEqual('property2', metadata_object['required'][0])
updated_property1 = metadata_object['properties']['property1']
updated_property2 = metadata_object['properties']['property2']
self.assertEqual('integer', updated_property1['type'])
self.assertEqual('p1 desc-UPDATED', updated_property1['description'])
self.assertEqual('500', updated_property1['default'])
self.assertEqual(500, updated_property1['minimum'])
self.assertEqual(1369, updated_property1['maximum'])
self.assertEqual([''], updated_property2['operators'])
self.assertEqual('string', updated_property2['type'])
self.assertEqual('p2 desc-UPDATED', updated_property2['description'])
self.assertEqual('value2-UPDATED', updated_property2['default'])
self.assertEqual(5, updated_property2['minLength'])
self.assertEqual(150, updated_property2['maxLength'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
self.assertEqual('object1-UPDATED', metadata_object['name'])
self.assertEqual('desc-UPDATED', metadata_object['description'])
self.assertEqual('property2', metadata_object['required'][0])
updated_property1 = metadata_object['properties']['property1']
updated_property2 = metadata_object['properties']['property2']
self.assertEqual('integer', updated_property1['type'])
self.assertEqual('p1 desc-UPDATED', updated_property1['description'])
self.assertEqual('500', updated_property1['default'])
self.assertEqual(500, updated_property1['minimum'])
self.assertEqual(1369, updated_property1['maximum'])
self.assertEqual([''], updated_property2['operators'])
self.assertEqual('string', updated_property2['type'])
self.assertEqual('p2 desc-UPDATED', updated_property2['description'])
self.assertEqual('value2-UPDATED', updated_property2['default'])
self.assertEqual(5, updated_property2['minLength'])
self.assertEqual(150, updated_property2['maxLength'])
# Deletion of metadata_object object1
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# metadata_object object1 should not exist
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
glance-16.0.1/glance/tests/functional/v2/test_metadef_resourcetypes.py 0000666 0001750 0001750 00000025102 13267672245 026170 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import six
from six.moves import http_client as http
import webob.exc
from wsme.rest import json
from glance.api import policy
from glance.api.v2.model.metadef_resource_type import ResourceType
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociation
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociations
from glance.api.v2.model.metadef_resource_type import ResourceTypes
from glance.common import exception
from glance.common import wsgi
import glance.db
import glance.gateway
from glance.i18n import _, _LE
import glance.notifier
import glance.schema
LOG = logging.getLogger(__name__)
class ResourceTypeController(object):
def __init__(self, db_api=None, policy_enforcer=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
policy_enforcer=self.policy)
def index(self, req):
try:
filters = {'namespace': None}
rs_type_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
db_resource_type_list = rs_type_repo.list(filters=filters)
resource_type_list = [ResourceType.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
resource_types = ResourceTypes()
resource_types.resource_types = resource_type_list
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except Exception as e:
LOG.error(e)
raise webob.exc.HTTPInternalServerError(e)
return resource_types
def show(self, req, namespace):
try:
filters = {'namespace': namespace}
rs_type_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
db_resource_type_list = rs_type_repo.list(filters=filters)
resource_type_list = [ResourceTypeAssociation.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
resource_types = ResourceTypeAssociations()
resource_types.resource_type_associations = resource_type_list
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except Exception as e:
LOG.error(e)
raise webob.exc.HTTPInternalServerError(e)
return resource_types
def create(self, req, resource_type, namespace):
rs_type_factory = self.gateway.get_metadef_resource_type_factory(
req.context)
rs_type_repo = self.gateway.get_metadef_resource_type_repo(req.context)
try:
new_resource_type = rs_type_factory.new_resource_type(
namespace=namespace, **resource_type.to_dict())
rs_type_repo.add(new_resource_type)
except exception.Forbidden as e:
msg = (_LE("Forbidden to create resource type. "
"Reason: %(reason)s")
% {'reason': encodeutils.exception_to_unicode(e)})
LOG.error(msg)
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Duplicate as e:
raise webob.exc.HTTPConflict(explanation=e.msg)
except Exception as e:
LOG.error(e)
raise webob.exc.HTTPInternalServerError()
return ResourceTypeAssociation.to_wsme_model(new_resource_type)
def delete(self, req, namespace, resource_type):
rs_type_repo = self.gateway.get_metadef_resource_type_repo(req.context)
try:
filters = {}
found = False
filters['namespace'] = namespace
db_resource_type_list = rs_type_repo.list(filters=filters)
for db_resource_type in db_resource_type_list:
if db_resource_type.name == resource_type:
db_resource_type.delete()
rs_type_repo.remove(db_resource_type)
found = True
if not found:
raise exception.NotFound()
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
msg = (_("Failed to find resource type %(resourcetype)s to "
"delete") % {'resourcetype': resource_type})
LOG.error(msg)
raise webob.exc.HTTPNotFound(explanation=msg)
except Exception as e:
LOG.error(e)
raise webob.exc.HTTPInternalServerError()
class RequestDeserializer(wsgi.JSONRequestDeserializer):
_disallowed_properties = ['created_at', 'updated_at']
def __init__(self, schema=None):
super(RequestDeserializer, self).__init__()
self.schema = schema or get_schema()
def _get_request_body(self, request):
output = super(RequestDeserializer, self).default(request)
if 'body' not in output:
msg = _('Body expected in request.')
raise webob.exc.HTTPBadRequest(explanation=msg)
return output['body']
@classmethod
def _check_allowed(cls, image):
for key in cls._disallowed_properties:
if key in image:
msg = _("Attribute '%s' is read-only.") % key
raise webob.exc.HTTPForbidden(
explanation=encodeutils.exception_to_unicode(msg))
def create(self, request):
body = self._get_request_body(request)
self._check_allowed(body)
try:
self.schema.validate(body)
except exception.InvalidObject as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
resource_type = json.fromjson(ResourceTypeAssociation, body)
return dict(resource_type=resource_type)
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema = schema
def show(self, response, result):
resource_type_json = json.tojson(ResourceTypeAssociations, result)
body = jsonutils.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def index(self, response, result):
resource_type_json = json.tojson(ResourceTypes, result)
body = jsonutils.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def create(self, response, result):
resource_type_json = json.tojson(ResourceTypeAssociation, result)
response.status_int = http.CREATED
body = jsonutils.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def delete(self, response, result):
response.status_int = http.NO_CONTENT
def _get_base_properties():
return {
'name': {
'type': 'string',
'description': _('Resource type names should be aligned with Heat '
'resource types whenever possible: '
'http://docs.openstack.org/developer/heat/'
'template_guide/openstack.html'),
'maxLength': 80,
},
'prefix': {
'type': 'string',
'description': _('Specifies the prefix to use for the given '
'resource type. Any properties in the namespace '
'should be prefixed with this prefix when being '
'applied to the specified resource type. Must '
'include prefix separator (e.g. a colon :).'),
'maxLength': 80,
},
'properties_target': {
'type': 'string',
'description': _('Some resource types allow more than one key / '
'value pair per instance. For example, Cinder '
'allows user and image metadata on volumes. Only '
'the image properties metadata is evaluated by '
'Nova (scheduling or drivers). This property '
'allows a namespace target to remove the '
'ambiguity.'),
'maxLength': 80,
},
"created_at": {
"type": "string",
"readOnly": True,
"description": _("Date and time of resource type association"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"readOnly": True,
"description": _("Date and time of the last resource type "
"association modification"),
"format": "date-time"
}
}
def get_schema():
properties = _get_base_properties()
mandatory_attrs = ResourceTypeAssociation.get_mandatory_attrs()
schema = glance.schema.Schema(
'resource_type_association',
properties,
required=mandatory_attrs,
)
return schema
def get_collection_schema():
resource_type_schema = get_schema()
return glance.schema.CollectionSchema('resource_type_associations',
resource_type_schema)
def create_resource():
"""ResourceTypeAssociation resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = ResourceTypeController()
return wsgi.Resource(controller, deserializer, serializer)
glance-16.0.1/glance/tests/functional/v2/test_metadef_properties.py 0000666 0001750 0001750 00000022476 13267672245 025463 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import uuid
from oslo_serialization import jsonutils
import requests
from six.moves import http_client as http
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
class TestNamespaceProperties(functional.FunctionalTest):
def setUp(self):
super(TestNamespaceProperties, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'admin',
}
base_headers.update(custom_headers or {})
return base_headers
def test_properties_lifecycle(self):
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a namespace
path = self._url('/v2/metadefs/namespaces')
headers = self._headers({'content-type': 'application/json'})
namespace_name = 'MyNamespace'
resource_type_name = 'MyResourceType'
resource_type_prefix = 'MyPrefix'
data = jsonutils.dumps({
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": False,
"owner": "The Test Owner",
"resource_type_associations": [
{
"name": resource_type_name,
"prefix": resource_type_prefix
}
]
})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Property1 should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace/properties'
'/property1')
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Create a property
path = self._url('/v2/metadefs/namespaces/MyNamespace/properties')
headers = self._headers({'content-type': 'application/json'})
property_name = "property1"
data = jsonutils.dumps(
{
"name": property_name,
"type": "integer",
"title": "property1",
"description": "property1 description",
"default": 100,
"minimum": 100,
"maximum": 30000369,
"readonly": False,
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CREATED, response.status_code)
# Attempt to insert a duplicate
response = requests.post(path, headers=headers, data=data)
self.assertEqual(http.CONFLICT, response.status_code)
# Get the property created above
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
property_object = jsonutils.loads(response.text)
self.assertEqual("integer", property_object['type'])
self.assertEqual("property1", property_object['title'])
self.assertEqual("property1 description", property_object[
'description'])
self.assertEqual('100', property_object['default'])
self.assertEqual(100, property_object['minimum'])
self.assertEqual(30000369, property_object['maximum'])
# Get the property with specific resource type association
path = self._url('/v2/metadefs/namespaces/%s/properties/%s%s' % (
namespace_name, property_name, '='.join(['?resource_type',
resource_type_name])))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
# Get the property with prefix and specific resource type association
property_name_with_prefix = ''.join([resource_type_prefix,
property_name])
path = self._url('/v2/metadefs/namespaces/%s/properties/%s%s' % (
namespace_name, property_name_with_prefix, '='.join([
'?resource_type', resource_type_name])))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.OK, response.status_code)
property_object = jsonutils.loads(response.text)
self.assertEqual("integer", property_object['type'])
self.assertEqual("property1", property_object['title'])
self.assertEqual("property1 description", property_object[
'description'])
self.assertEqual('100', property_object['default'])
self.assertEqual(100, property_object['minimum'])
self.assertEqual(30000369, property_object['maximum'])
self.assertFalse(property_object['readonly'])
# Returned property should match the created property
property_object = jsonutils.loads(response.text)
checked_keys = set([
u'name',
u'type',
u'title',
u'description',
u'default',
u'minimum',
u'maximum',
u'readonly',
])
self.assertEqual(set(property_object.keys()), checked_keys)
expected_metadata_property = {
"type": "integer",
"title": "property1",
"description": "property1 description",
"default": '100',
"minimum": 100,
"maximum": 30000369,
"readonly": False,
}
for key, value in expected_metadata_property.items():
self.assertEqual(property_object[key], value, key)
# The property should be mutable
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
property_name = "property1-UPDATED"
data = jsonutils.dumps(
{
"name": property_name,
"type": "string",
"title": "string property",
"description": "desc-UPDATED",
"operators": [""],
"default": "value-UPDATED",
"minLength": 5,
"maxLength": 10,
"readonly": True,
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(http.OK, response.status_code, response.text)
# Returned property should reflect the changes
property_object = jsonutils.loads(response.text)
self.assertEqual('string', property_object['type'])
self.assertEqual('desc-UPDATED', property_object['description'])
self.assertEqual('value-UPDATED', property_object['default'])
self.assertEqual([""], property_object['operators'])
self.assertEqual(5, property_object['minLength'])
self.assertEqual(10, property_object['maxLength'])
self.assertTrue(property_object['readonly'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual('string', property_object['type'])
self.assertEqual('desc-UPDATED', property_object['description'])
self.assertEqual('value-UPDATED', property_object['default'])
self.assertEqual([""], property_object['operators'])
self.assertEqual(5, property_object['minLength'])
self.assertEqual(10, property_object['maxLength'])
# Deletion of property property1
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.delete(path, headers=self._headers())
self.assertEqual(http.NO_CONTENT, response.status_code)
# property1 should not exist
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
glance-16.0.1/glance/tests/functional/__init__.py 0000666 0001750 0001750 00000105613 13267672254 021746 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base test class for running non-stubbed tests (functional tests)
The FunctionalTest class contains helper methods for starting the API
and Registry server, grabbing the logs of each, cleaning up pidfiles,
and spinning down the servers.
"""
import atexit
import datetime
import errno
import os
import platform
import shutil
import signal
import socket
import sys
import tempfile
import time
import fixtures
from oslo_serialization import jsonutils
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
import six.moves.urllib.parse as urlparse
import testtools
from glance.common import utils
from glance.db.sqlalchemy import api as db_api
from glance import tests as glance_tests
from glance.tests import utils as test_utils
execute, get_unused_port = test_utils.execute, test_utils.get_unused_port
tracecmd_osmap = {'Linux': 'strace', 'FreeBSD': 'truss'}
class Server(object):
"""
Class used to easily manage starting and stopping
a server during functional test runs.
"""
def __init__(self, test_dir, port, sock=None):
"""
Creates a new Server object.
:param test_dir: The directory where all test stuff is kept. This is
passed from the FunctionalTestCase.
:param port: The port to start a server up on.
"""
self.debug = True
self.no_venv = False
self.test_dir = test_dir
self.bind_port = port
self.conf_file_name = None
self.conf_base = None
self.paste_conf_base = None
self.exec_env = None
self.deployment_flavor = ''
self.show_image_direct_url = False
self.show_multiple_locations = False
self.property_protection_file = ''
self.enable_v1_api = True
self.enable_v2_api = True
# TODO(rosmaita): remove in Queens when the option is removed
# also, don't forget to remove it from ApiServer.conf_base
self.enable_image_import = False
self.enable_v1_registry = True
self.enable_v2_registry = True
self.needs_database = False
self.log_file = None
self.sock = sock
self.fork_socket = True
self.process_pid = None
self.server_module = None
self.stop_kill = False
self.use_user_token = True
self.send_identity_credentials = False
def write_conf(self, **kwargs):
"""
Writes the configuration file for the server to its intended
destination. Returns the name of the configuration file and
the over-ridden config content (may be useful for populating
error messages).
"""
if not self.conf_base:
raise RuntimeError("Subclass did not populate config_base!")
conf_override = self.__dict__.copy()
if kwargs:
conf_override.update(**kwargs)
# A config file and paste.ini to use just for this test...we don't want
# to trample on currently-running Glance servers, now do we?
conf_dir = os.path.join(self.test_dir, 'etc')
conf_filepath = os.path.join(conf_dir, "%s.conf" % self.server_name)
if os.path.exists(conf_filepath):
os.unlink(conf_filepath)
paste_conf_filepath = conf_filepath.replace(".conf", "-paste.ini")
if os.path.exists(paste_conf_filepath):
os.unlink(paste_conf_filepath)
utils.safe_mkdirs(conf_dir)
def override_conf(filepath, overridden):
with open(filepath, 'w') as conf_file:
conf_file.write(overridden)
conf_file.flush()
return conf_file.name
overridden_core = self.conf_base % conf_override
self.conf_file_name = override_conf(conf_filepath, overridden_core)
overridden_paste = ''
if self.paste_conf_base:
overridden_paste = self.paste_conf_base % conf_override
override_conf(paste_conf_filepath, overridden_paste)
overridden = ('==Core config==\n%s\n==Paste config==\n%s' %
(overridden_core, overridden_paste))
return self.conf_file_name, overridden
def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
"""
Starts the server.
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the servers.
"""
# Ensure the configuration file is written
self.write_conf(**kwargs)
self.create_database()
cmd = ("%(server_module)s --config-file %(conf_file_name)s"
% {"server_module": self.server_module,
"conf_file_name": self.conf_file_name})
cmd = "%s -m %s" % (sys.executable, cmd)
# close the sock and release the unused port closer to start time
if self.exec_env:
exec_env = self.exec_env.copy()
else:
exec_env = {}
pass_fds = set()
if self.sock:
if not self.fork_socket:
self.sock.close()
self.sock = None
else:
fd = os.dup(self.sock.fileno())
exec_env[utils.GLANCE_TEST_SOCKET_FD_STR] = str(fd)
pass_fds.add(fd)
self.sock.close()
self.process_pid = test_utils.fork_exec(cmd,
logfile=os.devnull,
exec_env=exec_env,
pass_fds=pass_fds)
self.stop_kill = not expect_exit
if self.pid_file:
pf = open(self.pid_file, 'w')
pf.write('%d\n' % self.process_pid)
pf.close()
if not expect_exit:
rc = 0
try:
os.kill(self.process_pid, 0)
except OSError:
raise RuntimeError("The process did not start")
else:
rc = test_utils.wait_for_fork(
self.process_pid,
expected_exitcode=expected_exitcode)
# avoid an FD leak
if self.sock:
os.close(fd)
self.sock = None
return (rc, '', '')
def reload(self, expect_exit=True, expected_exitcode=0, **kwargs):
"""
Start and stop the service to reload
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the servers.
"""
self.stop()
return self.start(expect_exit=expect_exit,
expected_exitcode=expected_exitcode, **kwargs)
def create_database(self):
"""Create database if required for this server"""
if self.needs_database:
conf_dir = os.path.join(self.test_dir, 'etc')
utils.safe_mkdirs(conf_dir)
conf_filepath = os.path.join(conf_dir, 'glance-manage.conf')
with open(conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write('sql_connection = %s' % self.sql_connection)
conf_file.flush()
glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
if glance_db_env in os.environ:
# use the empty db created and cached as a tempfile
# instead of spending the time creating a new one
db_location = os.environ[glance_db_env]
os.system('cp %s %s/tests.sqlite'
% (db_location, self.test_dir))
else:
cmd = ('%s -m glance.cmd.manage --config-file %s db sync' %
(sys.executable, conf_filepath))
execute(cmd, no_venv=self.no_venv, exec_env=self.exec_env,
expect_exit=True)
# copy the clean db to a temp location so that it
# can be reused for future tests
(osf, db_location) = tempfile.mkstemp()
os.close(osf)
os.system('cp %s/tests.sqlite %s'
% (self.test_dir, db_location))
os.environ[glance_db_env] = db_location
# cleanup the temp file when the test suite is
# complete
def _delete_cached_db():
try:
os.remove(os.environ[glance_db_env])
except Exception:
glance_tests.logger.exception(
"Error cleaning up the file %s" %
os.environ[glance_db_env])
atexit.register(_delete_cached_db)
def stop(self):
"""
Spin down the server.
"""
if not self.process_pid:
raise Exception('why is this being called? %s' % self.server_name)
if self.stop_kill:
os.kill(self.process_pid, signal.SIGTERM)
rc = test_utils.wait_for_fork(self.process_pid, raise_error=False)
return (rc, '', '')
def dump_log(self):
if not self.log_file:
return "log_file not set for {name}".format(name=self.server_name)
elif not os.path.exists(self.log_file):
return "{log_file} for {name} did not exist".format(
log_file=self.log_file, name=self.server_name)
with open(self.log_file, 'r') as fptr:
return fptr.read().strip()
class ApiServer(Server):
"""
Server object that starts/stops/manages the API server
"""
def __init__(self, test_dir, port, policy_file, delayed_delete=False,
pid_file=None, sock=None, **kwargs):
super(ApiServer, self).__init__(test_dir, port, sock=sock)
self.server_name = 'api'
self.server_module = 'glance.cmd.%s' % self.server_name
self.default_store = kwargs.get("default_store", "file")
self.bind_host = "127.0.0.1"
self.registry_host = "127.0.0.1"
self.key_file = ""
self.cert_file = ""
self.metadata_encryption_key = "012345678901234567890123456789ab"
self.image_dir = os.path.join(self.test_dir, "images")
self.pid_file = pid_file or os.path.join(self.test_dir, "api.pid")
self.log_file = os.path.join(self.test_dir, "api.log")
self.image_size_cap = 1099511627776
self.delayed_delete = delayed_delete
self.owner_is_tenant = True
self.workers = 0
self.scrub_time = 5
self.image_cache_dir = os.path.join(self.test_dir,
'cache')
self.image_cache_driver = 'sqlite'
self.policy_file = policy_file
self.policy_default_rule = 'default'
self.property_protection_rule_format = 'roles'
self.image_member_quota = 10
self.image_property_quota = 10
self.image_tag_quota = 10
self.image_location_quota = 2
self.disable_path = None
self.needs_database = True
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection)
self.data_api = kwargs.get("data_api",
"glance.db.sqlalchemy.api")
self.user_storage_quota = '0'
self.lock_path = self.test_dir
self.location_strategy = 'location_order'
self.store_type_location_strategy_preference = ""
self.send_identity_headers = False
self.conf_base = """[DEFAULT]
debug = %(debug)s
default_log_levels = eventlet.wsgi.server=DEBUG
bind_host = %(bind_host)s
bind_port = %(bind_port)s
key_file = %(key_file)s
cert_file = %(cert_file)s
metadata_encryption_key = %(metadata_encryption_key)s
registry_host = %(registry_host)s
registry_port = %(registry_port)s
use_user_token = %(use_user_token)s
send_identity_credentials = %(send_identity_credentials)s
log_file = %(log_file)s
image_size_cap = %(image_size_cap)d
delayed_delete = %(delayed_delete)s
owner_is_tenant = %(owner_is_tenant)s
workers = %(workers)s
scrub_time = %(scrub_time)s
send_identity_headers = %(send_identity_headers)s
image_cache_dir = %(image_cache_dir)s
image_cache_driver = %(image_cache_driver)s
data_api = %(data_api)s
sql_connection = %(sql_connection)s
show_image_direct_url = %(show_image_direct_url)s
show_multiple_locations = %(show_multiple_locations)s
user_storage_quota = %(user_storage_quota)s
enable_v1_api = %(enable_v1_api)s
enable_v2_api = %(enable_v2_api)s
enable_image_import = %(enable_image_import)s
lock_path = %(lock_path)s
property_protection_file = %(property_protection_file)s
property_protection_rule_format = %(property_protection_rule_format)s
image_member_quota=%(image_member_quota)s
image_property_quota=%(image_property_quota)s
image_tag_quota=%(image_tag_quota)s
image_location_quota=%(image_location_quota)s
location_strategy=%(location_strategy)s
allow_additional_image_properties = True
[oslo_policy]
policy_file = %(policy_file)s
policy_default_rule = %(policy_default_rule)s
[paste_deploy]
flavor = %(deployment_flavor)s
[store_type_location_strategy]
store_type_preference = %(store_type_location_strategy_preference)s
[glance_store]
filesystem_store_datadir=%(image_dir)s
default_store = %(default_store)s
"""
self.paste_conf_base = """[pipeline:glance-api]
pipeline =
cors
healthcheck
versionnegotiation
gzip
unauthenticated-context
rootapp
[pipeline:glance-api-caching]
pipeline = cors healthcheck versionnegotiation gzip unauthenticated-context
cache rootapp
[pipeline:glance-api-cachemanagement]
pipeline =
cors
healthcheck
versionnegotiation
gzip
unauthenticated-context
cache
cache_manage
rootapp
[pipeline:glance-api-fakeauth]
pipeline = cors healthcheck versionnegotiation gzip fakeauth context rootapp
[pipeline:glance-api-noauth]
pipeline = cors healthcheck versionnegotiation gzip context rootapp
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
backends = disable_by_file
disable_by_file_path = %(disable_path)s
[filter:versionnegotiation]
paste.filter_factory =
glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cache_manage]
paste.filter_factory =
glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory =
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:fakeauth]
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
allowed_origin=http://valid.example.com
"""
class RegistryServer(Server):
"""
Server object that starts/stops/manages the Registry server
"""
def __init__(self, test_dir, port, policy_file, sock=None):
super(RegistryServer, self).__init__(test_dir, port, sock=sock)
self.server_name = 'registry'
self.server_module = 'glance.cmd.%s' % self.server_name
self.needs_database = True
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection)
self.bind_host = "127.0.0.1"
self.pid_file = os.path.join(self.test_dir, "registry.pid")
self.log_file = os.path.join(self.test_dir, "registry.log")
self.owner_is_tenant = True
self.workers = 0
self.api_version = 1
self.user_storage_quota = '0'
self.metadata_encryption_key = "012345678901234567890123456789ab"
self.policy_file = policy_file
self.policy_default_rule = 'default'
self.disable_path = None
self.conf_base = """[DEFAULT]
debug = %(debug)s
bind_host = %(bind_host)s
bind_port = %(bind_port)s
log_file = %(log_file)s
sql_connection = %(sql_connection)s
sql_idle_timeout = 3600
api_limit_max = 1000
limit_param_default = 25
owner_is_tenant = %(owner_is_tenant)s
enable_v2_registry = %(enable_v2_registry)s
workers = %(workers)s
user_storage_quota = %(user_storage_quota)s
metadata_encryption_key = %(metadata_encryption_key)s
[oslo_policy]
policy_file = %(policy_file)s
policy_default_rule = %(policy_default_rule)s
[paste_deploy]
flavor = %(deployment_flavor)s
"""
self.paste_conf_base = """[pipeline:glance-registry]
pipeline = healthcheck unauthenticated-context registryapp
[pipeline:glance-registry-fakeauth]
pipeline = healthcheck fakeauth context registryapp
[pipeline:glance-registry-trusted-auth]
pipeline = healthcheck context registryapp
[app:registryapp]
paste.app_factory = glance.registry.api:API.factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
backends = disable_by_file
disable_by_file_path = %(disable_path)s
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory =
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:fakeauth]
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
"""
class ScrubberDaemon(Server):
"""
Server object that starts/stops/manages the Scrubber server
"""
def __init__(self, test_dir, policy_file, daemon=False, **kwargs):
# NOTE(jkoelker): Set the port to 0 since we actually don't listen
super(ScrubberDaemon, self).__init__(test_dir, 0)
self.server_name = 'scrubber'
self.server_module = 'glance.cmd.%s' % self.server_name
self.daemon = daemon
self.registry_host = "127.0.0.1"
self.image_dir = os.path.join(self.test_dir, "images")
self.scrub_time = 5
self.pid_file = os.path.join(self.test_dir, "scrubber.pid")
self.log_file = os.path.join(self.test_dir, "scrubber.log")
self.metadata_encryption_key = "012345678901234567890123456789ab"
self.lock_path = self.test_dir
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection)
self.policy_file = policy_file
self.policy_default_rule = 'default'
self.send_identity_headers = False
self.admin_role = 'admin'
self.conf_base = """[DEFAULT]
debug = %(debug)s
log_file = %(log_file)s
daemon = %(daemon)s
wakeup_time = 2
scrub_time = %(scrub_time)s
registry_host = %(registry_host)s
registry_port = %(registry_port)s
metadata_encryption_key = %(metadata_encryption_key)s
lock_path = %(lock_path)s
sql_connection = %(sql_connection)s
sql_idle_timeout = 3600
send_identity_headers = %(send_identity_headers)s
admin_role = %(admin_role)s
[glance_store]
filesystem_store_datadir=%(image_dir)s
[oslo_policy]
policy_file = %(policy_file)s
policy_default_rule = %(policy_default_rule)s
"""
def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
if 'daemon' in kwargs:
expect_exit = False
return super(ScrubberDaemon, self).start(
expect_exit=expect_exit,
expected_exitcode=expected_exitcode,
**kwargs)
class FunctionalTest(test_utils.BaseTestCase):
"""
Base test class for any test that wants to test the actual
servers and clients and not just the stubbed out interfaces
"""
inited = False
disabled = False
launched_servers = []
def setUp(self):
super(FunctionalTest, self).setUp()
self.test_dir = self.useFixture(fixtures.TempDir()).path
self.api_protocol = 'http'
self.api_port, api_sock = test_utils.get_unused_port_and_socket()
self.registry_port, reg_sock = test_utils.get_unused_port_and_socket()
# NOTE: Scrubber is enabled by default for the functional tests.
# Please disbale it by explicitly setting 'self.include_scrubber' to
# False in the test SetUps that do not require Scrubber to run.
self.include_scrubber = True
self.tracecmd = tracecmd_osmap.get(platform.system())
conf_dir = os.path.join(self.test_dir, 'etc')
utils.safe_mkdirs(conf_dir)
self.copy_data_file('schema-image.json', conf_dir)
self.copy_data_file('policy.json', conf_dir)
self.copy_data_file('property-protections.conf', conf_dir)
self.copy_data_file('property-protections-policies.conf', conf_dir)
self.property_file_roles = os.path.join(conf_dir,
'property-protections.conf')
property_policies = 'property-protections-policies.conf'
self.property_file_policies = os.path.join(conf_dir,
property_policies)
self.policy_file = os.path.join(conf_dir, 'policy.json')
self.api_server = ApiServer(self.test_dir,
self.api_port,
self.policy_file,
sock=api_sock)
self.registry_server = RegistryServer(self.test_dir,
self.registry_port,
self.policy_file,
sock=reg_sock)
self.scrubber_daemon = ScrubberDaemon(self.test_dir, self.policy_file)
self.pid_files = [self.api_server.pid_file,
self.registry_server.pid_file,
self.scrubber_daemon.pid_file]
self.files_to_destroy = []
self.launched_servers = []
# Keep track of servers we've logged so we don't double-log them.
self._attached_server_logs = []
self.addOnException(self.add_log_details_on_exception)
if not self.disabled:
# We destroy the test data store between each test case,
# and recreate it, which ensures that we have no side-effects
# from the tests
self.addCleanup(
self._reset_database, self.registry_server.sql_connection)
self.addCleanup(
self._reset_database, self.api_server.sql_connection)
self.addCleanup(self.cleanup)
self._reset_database(self.registry_server.sql_connection)
self._reset_database(self.api_server.sql_connection)
def set_policy_rules(self, rules):
fap = open(self.policy_file, 'w')
fap.write(jsonutils.dumps(rules))
fap.close()
def _reset_database(self, conn_string):
conn_pieces = urlparse.urlparse(conn_string)
if conn_string.startswith('sqlite'):
# We leave behind the sqlite DB for failing tests to aid
# in diagnosis, as the file size is relatively small and
# won't interfere with subsequent tests as it's in a per-
# test directory (which is blown-away if the test is green)
pass
elif conn_string.startswith('mysql'):
# We can execute the MySQL client to destroy and re-create
# the MYSQL database, which is easier and less error-prone
# than using SQLAlchemy to do this via MetaData...trust me.
database = conn_pieces.path.strip('/')
loc_pieces = conn_pieces.netloc.split('@')
host = loc_pieces[1]
auth_pieces = loc_pieces[0].split(':')
user = auth_pieces[0]
password = ""
if len(auth_pieces) > 1:
if auth_pieces[1].strip():
password = "-p%s" % auth_pieces[1]
sql = ("drop database if exists %(database)s; "
"create database %(database)s;") % {'database': database}
cmd = ("mysql -u%(user)s %(password)s -h%(host)s "
"-e\"%(sql)s\"") % {'user': user, 'password': password,
'host': host, 'sql': sql}
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
def cleanup(self):
"""
Makes sure anything we created or started up in the
tests are destroyed or spun down
"""
# NOTE(jbresnah) call stop on each of the servers instead of
# checking the pid file. stop() will wait until the child
# server is dead. This eliminates the possibility of a race
# between a child process listening on a port actually dying
# and a new process being started
servers = [self.api_server,
self.registry_server,
self.scrubber_daemon]
for s in servers:
try:
s.stop()
except Exception:
pass
for f in self.files_to_destroy:
if os.path.exists(f):
os.unlink(f)
def start_server(self,
server,
expect_launch,
expect_exit=True,
expected_exitcode=0,
**kwargs):
"""
Starts a server on an unused port.
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the server.
:param server: the server to launch
:param expect_launch: true iff the server is expected to
successfully start
:param expect_exit: true iff the launched process is expected
to exit in a timely fashion
:param expected_exitcode: expected exitcode from the launcher
"""
self.cleanup()
# Start up the requested server
exitcode, out, err = server.start(expect_exit=expect_exit,
expected_exitcode=expected_exitcode,
**kwargs)
if expect_exit:
self.assertEqual(expected_exitcode, exitcode,
"Failed to spin up the requested server. "
"Got: %s" % err)
self.launched_servers.append(server)
launch_msg = self.wait_for_servers([server], expect_launch)
self.assertTrue(launch_msg is None, launch_msg)
def start_with_retry(self, server, port_name, max_retries,
expect_launch=True,
**kwargs):
"""
Starts a server, with retries if the server launches but
fails to start listening on the expected port.
:param server: the server to launch
:param port_name: the name of the port attribute
:param max_retries: the maximum number of attempts
:param expect_launch: true iff the server is expected to
successfully start
:param expect_exit: true iff the launched process is expected
to exit in a timely fashion
"""
launch_msg = None
for i in range(max_retries):
exitcode, out, err = server.start(expect_exit=not expect_launch,
**kwargs)
name = server.server_name
self.assertEqual(0, exitcode,
"Failed to spin up the %s server. "
"Got: %s" % (name, err))
launch_msg = self.wait_for_servers([server], expect_launch)
if launch_msg:
server.stop()
server.bind_port = get_unused_port()
setattr(self, port_name, server.bind_port)
else:
self.launched_servers.append(server)
break
self.assertTrue(launch_msg is None, launch_msg)
def start_servers(self, **kwargs):
"""
Starts the API and Registry servers (glance-control api start
& glance-control registry start) on unused ports. glance-control
should be installed into the python path
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the servers.
"""
self.cleanup()
# Start up the API and default registry server
# We start the registry server first, as the API server config
# depends on the registry port - this ordering allows for
# retrying the launch on a port clash
self.start_with_retry(self.registry_server, 'registry_port', 3,
**kwargs)
kwargs['registry_port'] = self.registry_server.bind_port
self.start_with_retry(self.api_server, 'api_port', 3, **kwargs)
if self.include_scrubber:
exitcode, out, err = self.scrubber_daemon.start(**kwargs)
self.assertEqual(0, exitcode,
"Failed to spin up the Scrubber daemon. "
"Got: %s" % err)
def ping_server(self, port):
"""
Simple ping on the port. If responsive, return True, else
return False.
:note We use raw sockets, not ping here, since ping uses ICMP and
has no concept of ports...
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(("127.0.0.1", port))
return True
except socket.error:
return False
finally:
s.close()
def ping_server_ipv6(self, port):
"""
Simple ping on the port. If responsive, return True, else
return False.
:note We use raw sockets, not ping here, since ping uses ICMP and
has no concept of ports...
The function uses IPv6 (therefore AF_INET6 and ::1).
"""
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
s.connect(("::1", port))
return True
except socket.error:
return False
finally:
s.close()
def wait_for_servers(self, servers, expect_launch=True, timeout=30):
"""
Tight loop, waiting for the given server port(s) to be available.
Returns when all are pingable. There is a timeout on waiting
for the servers to come up.
:param servers: Glance server ports to ping
:param expect_launch: Optional, true iff the server(s) are
expected to successfully start
:param timeout: Optional, defaults to 30 seconds
:returns: None if launch expectation is met, otherwise an
assertion message
"""
now = datetime.datetime.now()
timeout_time = now + datetime.timedelta(seconds=timeout)
replied = []
while (timeout_time > now):
pinged = 0
for server in servers:
if self.ping_server(server.bind_port):
pinged += 1
if server not in replied:
replied.append(server)
if pinged == len(servers):
msg = 'Unexpected server launch status'
return None if expect_launch else msg
now = datetime.datetime.now()
time.sleep(0.05)
failed = list(set(servers) - set(replied))
msg = 'Unexpected server launch status for: '
for f in failed:
msg += ('%s, ' % f.server_name)
if os.path.exists(f.pid_file):
pid = f.process_pid
trace = f.pid_file.replace('.pid', '.trace')
if self.tracecmd:
cmd = '%s -p %d -o %s' % (self.tracecmd, pid, trace)
try:
execute(cmd, raise_error=False, expect_exit=False)
except OSError as e:
if e.errno == errno.ENOENT:
raise RuntimeError('No executable found for "%s" '
'command.' % self.tracecmd)
else:
raise
time.sleep(0.5)
if os.path.exists(trace):
msg += ('\n%s:\n%s\n' % (self.tracecmd,
open(trace).read()))
self.add_log_details(failed)
return msg if expect_launch else None
def stop_server(self, server):
"""
Called to stop a single server in a normal fashion using the
glance-control stop method to gracefully shut the server down.
:param server: the server to stop
"""
# Spin down the requested server
server.stop()
def stop_servers(self):
"""
Called to stop the started servers in a normal fashion. Note
that cleanup() will stop the servers using a fairly draconian
method of sending a SIGTERM signal to the servers. Here, we use
the glance-control stop method to gracefully shut the server down.
This method also asserts that the shutdown was clean, and so it
is meant to be called during a normal test case sequence.
"""
# Spin down the API and default registry server
self.stop_server(self.api_server)
self.stop_server(self.registry_server)
if self.include_scrubber:
self.stop_server(self.scrubber_daemon)
self._reset_database(self.registry_server.sql_connection)
def run_sql_cmd(self, sql):
"""
Provides a crude mechanism to run manual SQL commands for backend
DB verification within the functional tests.
The raw result set is returned.
"""
engine = db_api.get_engine()
return engine.execute(sql)
def copy_data_file(self, file_name, dst_dir):
src_file_name = os.path.join('glance/tests/etc', file_name)
shutil.copy(src_file_name, dst_dir)
dst_file_name = os.path.join(dst_dir, file_name)
return dst_file_name
def add_log_details_on_exception(self, *args, **kwargs):
self.add_log_details()
def add_log_details(self, servers=None):
for s in servers or self.launched_servers:
if s.log_file not in self._attached_server_logs:
self._attached_server_logs.append(s.log_file)
self.addDetail(
s.server_name, testtools.content.text_content(s.dump_log()))
glance-16.0.1/glance/tests/functional/test_scrubber.py 0000666 0001750 0001750 00000031446 13267672254 023057 0 ustar zuul zuul 0000000 0000000 # Copyright 2011-2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
import time
import httplib2
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests import functional
from glance.tests.utils import execute
TEST_IMAGE_DATA = '*' * 5 * units.Ki
TEST_IMAGE_META = {
'name': 'test_image',
'is_public': False,
'disk_format': 'raw',
'container_format': 'ovf',
}
class TestScrubber(functional.FunctionalTest):
"""Test that delayed_delete works and the scrubber deletes"""
def _send_http_request(self, path, method, body=None):
headers = {
'x-image-meta-name': 'test_image',
'x-image-meta-is_public': 'true',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream'
}
return httplib2.Http().request(path, method, body, headers)
def test_delayed_delete(self):
"""
test that images don't get deleted immediately and that the scrubber
scrubs them
"""
self.cleanup()
self.start_servers(delayed_delete=True, daemon=True,
metadata_encryption_key='')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
response, content = self._send_http_request(path, 'POST', body='XXX')
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image['id'])
response, content = self._send_http_request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
response, content = self._send_http_request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
self.wait_for_scrub(path)
self.stop_servers()
def test_delayed_delete_with_trustedauth_registry(self):
"""
test that images don't get deleted immediately and that the scrubber
scrubs them when registry is operating in trustedauth mode
"""
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.registry_server.deployment_flavor = 'trusted-auth'
self.start_servers(delayed_delete=True, daemon=True,
metadata_encryption_key='',
send_identity_headers=True)
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': 'deae8923-075d-4287-924b-840fb2644874',
'X-Roles': 'admin',
}
headers = {
'x-image-meta-name': 'test_image',
'x-image-meta-is_public': 'true',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream',
}
headers.update(base_headers)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', body='XXX',
headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
image_id = image['id']
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE', headers=base_headers)
self.assertEqual(http_client.OK, response.status)
response, content = http.request(path, 'HEAD', headers=base_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
self.wait_for_scrub(path, headers=base_headers)
self.stop_servers()
def test_scrubber_app(self):
"""
test that the glance-scrubber script runs successfully when not in
daemon mode
"""
self.cleanup()
self.start_servers(delayed_delete=True, daemon=False,
metadata_encryption_key='')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
response, content = self._send_http_request(path, 'POST', body='XXX')
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image['id'])
response, content = self._send_http_request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
response, content = self._send_http_request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
# wait for the scrub time on the image to pass
time.sleep(self.api_server.scrub_time)
# scrub images and make sure they get deleted
exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
cmd = ("%s --config-file %s" %
(exe_cmd, self.scrubber_daemon.conf_file_name))
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(0, exitcode)
self.wait_for_scrub(path)
self.stop_servers()
def test_scrubber_app_with_trustedauth_registry(self):
"""
test that the glance-scrubber script runs successfully when not in
daemon mode and with a registry that operates in trustedauth mode
"""
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.registry_server.deployment_flavor = 'trusted-auth'
self.start_servers(delayed_delete=True, daemon=False,
metadata_encryption_key='',
send_identity_headers=True)
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': 'deae8923-075d-4287-924b-840fb2644874',
'X-Roles': 'admin',
}
headers = {
'x-image-meta-name': 'test_image',
'x-image-meta-is_public': 'true',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream',
}
headers.update(base_headers)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', body='XXX',
headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
image_id = image['id']
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE', headers=base_headers)
self.assertEqual(http_client.OK, response.status)
response, content = http.request(path, 'HEAD', headers=base_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
# wait for the scrub time on the image to pass
time.sleep(self.api_server.scrub_time)
# scrub images and make sure they get deleted
exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
cmd = ("%s --config-file %s" %
(exe_cmd, self.scrubber_daemon.conf_file_name))
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(0, exitcode)
self.wait_for_scrub(path, headers=base_headers)
self.stop_servers()
def test_scrubber_delete_handles_exception(self):
"""
Test that the scrubber handles the case where an
exception occurs when _delete() is called. The scrubber
should not write out queue files in this case.
"""
# Start servers.
self.cleanup()
self.start_servers(delayed_delete=True, daemon=False,
default_store='file')
# Check that we are using a file backend.
self.assertEqual(self.api_server.default_store, 'file')
# add an image
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
response, content = self._send_http_request(path, 'POST', body='XXX')
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
# delete the image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image['id'])
response, content = self._send_http_request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
# ensure the image is marked pending delete
response, content = self._send_http_request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('pending_delete', response['x-image-meta-status'])
# Remove the file from the backend.
file_path = os.path.join(self.api_server.image_dir, image['id'])
os.remove(file_path)
# Wait for the scrub time on the image to pass
time.sleep(self.api_server.scrub_time)
# run the scrubber app, and ensure it doesn't fall over
exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
cmd = ("%s --config-file %s" %
(exe_cmd, self.scrubber_daemon.conf_file_name))
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(0, exitcode)
self.wait_for_scrub(path)
self.stop_servers()
def test_scrubber_app_queue_errors_not_daemon(self):
"""
test that the glance-scrubber exits with an exit code > 0 when it
fails to lookup images, indicating a configuration error when not
in daemon mode.
Related-Bug: #1548289
"""
# Don't start the registry server to cause intended failure
# Don't start the api server to save time
exitcode, out, err = self.scrubber_daemon.start(
delayed_delete=True, daemon=False, registry_port=28890)
self.assertEqual(0, exitcode,
"Failed to spin up the Scrubber daemon. "
"Got: %s" % err)
# Run the Scrubber
exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
cmd = ("%s --config-file %s" %
(exe_cmd, self.scrubber_daemon.conf_file_name))
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(1, exitcode)
self.assertIn('Can not get scrub jobs from queue', str(err))
self.stop_server(self.scrubber_daemon)
def wait_for_scrub(self, path, headers=None):
"""
NOTE(jkoelker) The build servers sometimes take longer than 15 seconds
to scrub. Give it up to 5 min, checking checking every 15 seconds.
When/if it flips to deleted, bail immediately.
"""
http = httplib2.Http()
wait_for = 300 # seconds
check_every = 15 # seconds
for _ in range(wait_for // check_every):
time.sleep(check_every)
response, content = http.request(path, 'HEAD', headers=headers)
if (response['x-image-meta-status'] == 'deleted' and
response['x-image-meta-deleted'] == 'True'):
break
else:
continue
else:
self.fail('image was never scrubbed')
glance-16.0.1/glance/tests/functional/test_client_exceptions.py 0000666 0001750 0001750 00000010652 13267672245 024763 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# Copyright 2012 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test asserting strongly typed exceptions from glance client"""
import eventlet.patcher
import httplib2
from six.moves import http_client
import webob.dec
import webob.exc
from glance.common import client
from glance.common import exception
from glance.common import wsgi
from glance.tests import functional
from glance.tests import utils
eventlet.patcher.monkey_patch(socket=True)
class ExceptionTestApp(object):
"""
Test WSGI application which can respond with multiple kinds of HTTP
status codes
"""
@webob.dec.wsgify
def __call__(self, request):
path = request.path_qs
if path == "/rate-limit":
request.response = webob.exc.HTTPRequestEntityTooLarge()
elif path == "/rate-limit-retry":
request.response.retry_after = 10
request.response.status = http_client.REQUEST_ENTITY_TOO_LARGE
elif path == "/service-unavailable":
request.response = webob.exc.HTTPServiceUnavailable()
elif path == "/service-unavailable-retry":
request.response.retry_after = 10
request.response.status = http_client.SERVICE_UNAVAILABLE
elif path == "/expectation-failed":
request.response = webob.exc.HTTPExpectationFailed()
elif path == "/server-error":
request.response = webob.exc.HTTPServerError()
elif path == "/server-traceback":
raise exception.ServerError()
class TestClientExceptions(functional.FunctionalTest):
def setUp(self):
super(TestClientExceptions, self).setUp()
self.port = utils.get_unused_port()
server = wsgi.Server()
self.config(bind_host='127.0.0.1')
self.config(workers=0)
server.start(ExceptionTestApp(), self.port)
self.client = client.BaseClient("127.0.0.1", self.port)
def _do_test_exception(self, path, exc_type):
try:
self.client.do_request("GET", path)
self.fail('expected %s' % exc_type)
except exc_type as e:
if 'retry' in path:
self.assertEqual(10, e.retry_after)
def test_rate_limited(self):
"""
Test rate limited response
"""
self._do_test_exception('/rate-limit', exception.LimitExceeded)
def test_rate_limited_retry(self):
"""
Test rate limited response with retry
"""
self._do_test_exception('/rate-limit-retry', exception.LimitExceeded)
def test_service_unavailable(self):
"""
Test service unavailable response
"""
self._do_test_exception('/service-unavailable',
exception.ServiceUnavailable)
def test_service_unavailable_retry(self):
"""
Test service unavailable response with retry
"""
self._do_test_exception('/service-unavailable-retry',
exception.ServiceUnavailable)
def test_expectation_failed(self):
"""
Test expectation failed response
"""
self._do_test_exception('/expectation-failed',
exception.UnexpectedStatus)
def test_server_error(self):
"""
Test server error response
"""
self._do_test_exception('/server-error',
exception.ServerError)
def test_server_traceback(self):
"""
Verify that the wsgi server does not return tracebacks to the client on
500 errors (bug 1192132)
"""
http = httplib2.Http()
path = ('http://%s:%d/server-traceback' %
('127.0.0.1', self.port))
response, content = http.request(path, 'GET')
self.assertNotIn(b'ServerError', content)
self.assertEqual(http_client.INTERNAL_SERVER_ERROR, response.status)
glance-16.0.1/glance/tests/functional/v1/ 0000775 0001750 0001750 00000000000 13267672475 020160 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/v1/test_multiprocessing.py 0000666 0001750 0001750 00000005360 13267672254 025021 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
import httplib2
import psutil
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests import functional
from glance.tests.utils import execute
class TestMultiprocessing(functional.FunctionalTest):
"""Functional tests for the bin/glance CLI tool"""
def setUp(self):
self.workers = 2
super(TestMultiprocessing, self).setUp()
def test_multiprocessing(self):
"""Spin up the api servers with multiprocessing on"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(b'{"images": []}', content)
self.stop_servers()
def _get_children(self):
api_pid = self.api_server.process_pid
process = psutil.Process(api_pid)
try:
# psutils version >= 2
children = process.children()
except AttributeError:
# psutils version < 2
children = process.get_children()
pids = [str(child.pid) for child in children]
return pids
def test_interrupt_avoids_respawn_storm(self):
"""
Ensure an interrupt signal does not cause a respawn storm.
See bug #978130
"""
self.start_servers(**self.__dict__.copy())
children = self._get_children()
cmd = "kill -INT %s" % ' '.join(children)
execute(cmd, raise_error=True)
for _ in range(9):
# Yeah. This totally isn't a race condition. Randomly fails
# set at 0.05. Works most of the time at 0.10
time.sleep(0.10)
# ensure number of children hasn't grown
self.assertGreaterEqual(len(children), len(self._get_children()))
for child in self._get_children():
# ensure no new children spawned
self.assertIn(child, children, child)
self.stop_servers()
glance-16.0.1/glance/tests/functional/v1/__init__.py 0000666 0001750 0001750 00000000000 13267672254 022254 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/functional/v1/test_misc.py 0000666 0001750 0001750 00000011115 13267672254 022520 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import hashlib
import os
import httplib2
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
from glance.tests import functional
from glance.tests.utils import minimal_headers
FIVE_KB = 5 * units.Ki
FIVE_GB = 5 * units.Gi
class TestMiscellaneous(functional.FunctionalTest):
"""Some random tests for various bugs and stuff"""
def setUp(self):
super(TestMiscellaneous, self).setUp()
# NOTE(sirp): This is needed in case we are running the tests under an
# environment in which OS_AUTH_STRATEGY=keystone. The test server we
# spin up won't have keystone support, so we need to switch to the
# NoAuth strategy.
os.environ['OS_AUTH_STRATEGY'] = 'noauth'
os.environ['OS_AUTH_URL'] = ''
def test_api_response_when_image_deleted_from_filesystem(self):
"""
A test for LP bug #781410 -- glance should fail more gracefully
on requests for images that have been removed from the fs
"""
self.cleanup()
self.start_servers()
# 1. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
# 2. REMOVE the image from the filesystem
image_path = "%s/images/%s" % (self.test_dir, data['image']['id'])
os.remove(image_path)
# 3. HEAD /images/1
# Verify image found now
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
data['image']['id'])
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
# 4. GET /images/1
# Verify the api throws the appropriate 404 error
path = "http://%s:%d/v1/images/1" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.NOT_FOUND, response.status)
self.stop_servers()
def test_exception_not_eaten_from_registry_to_api(self):
"""
A test for LP bug #704854 -- Exception thrown by registry
server is consumed by API server.
We start both servers daemonized.
We then use Glance API to try adding an image that does not
meet validation requirements on the registry server and test
that the error returned from the API server is appropriate
"""
self.cleanup()
self.start_servers()
api_port = self.api_port
path = 'http://127.0.0.1:%d/v1/images' % api_port
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(b'{"images": []}', content)
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'ImageName',
'X-Image-Meta-Disk-Format': 'Invalid', }
ignored, content = http.request(path, 'POST', headers=headers)
self.assertIn(b'Invalid disk format', content,
"Could not find 'Invalid disk format' "
"in output: %s" % content)
self.stop_servers()
glance-16.0.1/glance/tests/functional/v1/test_copy_to_file.py 0000666 0001750 0001750 00000027067 13267672254 024255 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# Copyright 2012 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests copying images to a Glance API server which uses a filesystem-
based storage backend.
"""
import hashlib
import tempfile
import time
import httplib2
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests import functional
from glance.tests.functional.store_utils import get_http_uri
from glance.tests.functional.store_utils import setup_http
from glance.tests.utils import skip_if_disabled
FIVE_KB = 5 * units.Ki
class TestCopyToFile(functional.FunctionalTest):
"""
Functional tests for copying images from the HTTP storage
backend to file
"""
def _do_test_copy_from(self, from_store, get_uri):
"""
Ensure we can copy from an external image in from_store.
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
setup_http(self)
# POST /images with public image to be stored in from_store,
# to stand in for the 'external' image
image_data = b"*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'external',
'X-Image-Meta-Store': from_store,
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True'}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status, content)
data = jsonutils.loads(content)
original_image_id = data['image']['id']
copy_from = get_uri(self, original_image_id)
# POST /images with public image copied from_store (to file)
headers = {'X-Image-Meta-Name': 'copied',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Glance-API-Copy-From': copy_from}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status, content)
data = jsonutils.loads(content)
copy_image_id = data['image']['id']
self.assertNotEqual(copy_image_id, original_image_id)
# GET image and make sure image content is as expected
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
copy_image_id)
def _await_status(expected_status):
for i in range(100):
time.sleep(0.01)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
if response['x-image-meta-status'] == expected_status:
return
self.fail('unexpected image status %s' %
response['x-image-meta-status'])
_await_status('active')
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(str(FIVE_KB), response['content-length'])
self.assertEqual(image_data, content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
hashlib.md5(content).hexdigest())
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("copied", data['image']['name'])
# DELETE original image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
original_image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
# GET image again to make sure the existence of the original
# image in from_store is not depended on
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
copy_image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(str(FIVE_KB), response['content-length'])
self.assertEqual(image_data, content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
hashlib.md5(content).hexdigest())
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("copied", data['image']['name'])
# DELETE copied image
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
copy_image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
self.stop_servers()
@skip_if_disabled
def test_copy_from_http_store(self):
"""
Ensure we can copy from an external image in HTTP store.
"""
self._do_test_copy_from('file', get_http_uri)
@skip_if_disabled
def test_copy_from_http_exists(self):
"""Ensure we can copy from an external image in HTTP."""
self.cleanup()
self.start_servers(**self.__dict__.copy())
setup_http(self)
copy_from = get_http_uri(self, 'foobar')
# POST /images with public image copied from HTTP (to file)
headers = {'X-Image-Meta-Name': 'copied',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Glance-API-Copy-From': copy_from}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status, content)
data = jsonutils.loads(content)
copy_image_id = data['image']['id']
self.assertEqual('queued', data['image']['status'], content)
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
copy_image_id)
def _await_status(expected_status):
for i in range(100):
time.sleep(0.01)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
if response['x-image-meta-status'] == expected_status:
return
self.fail('unexpected image status %s' %
response['x-image-meta-status'])
_await_status('active')
# GET image and make sure image content is as expected
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(str(FIVE_KB), response['content-length'])
self.assertEqual(b"*" * FIVE_KB, content)
self.assertEqual(hashlib.md5(b"*" * FIVE_KB).hexdigest(),
hashlib.md5(content).hexdigest())
# DELETE copied image
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
self.stop_servers()
@skip_if_disabled
def test_copy_from_http_nonexistent_location_url(self):
# Ensure HTTP 404 response returned when try to create
# image with non-existent http location URL.
self.cleanup()
self.start_servers(**self.__dict__.copy())
setup_http(self)
uri = get_http_uri(self, 'foobar')
copy_from = uri.replace('images', 'snafu')
# POST /images with public image copied from HTTP (to file)
headers = {'X-Image-Meta-Name': 'copied',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Glance-API-Copy-From': copy_from}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.NOT_FOUND, response.status, content)
expected = 'HTTP datastore could not find image at URI.'
self.assertIn(expected, content.decode())
self.stop_servers()
@skip_if_disabled
def test_copy_from_file(self):
"""
Ensure we can't copy from file
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
with tempfile.NamedTemporaryFile() as image_file:
image_file.write(b"XXX")
image_file.flush()
copy_from = 'file://' + image_file.name
# POST /images with public image copied from file (to file)
headers = {'X-Image-Meta-Name': 'copied',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Glance-API-Copy-From': copy_from}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.BAD_REQUEST, response.status, content)
expected = 'External sources are not supported: \'%s\'' % copy_from
msg = 'expected "%s" in "%s"' % (expected, content)
self.assertIn(expected, content.decode(), msg)
self.stop_servers()
@skip_if_disabled
def test_copy_from_swift_config(self):
"""
Ensure we can't copy from swift+config
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# POST /images with public image copied from file (to file)
headers = {'X-Image-Meta-Name': 'copied',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Glance-API-Copy-From': 'swift+config://xxx'}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.BAD_REQUEST, response.status, content)
expected = 'External sources are not supported: \'swift+config://xxx\''
msg = 'expected "%s" in "%s"' % (expected, content)
self.assertIn(expected, content.decode(), msg)
self.stop_servers()
glance-16.0.1/glance/tests/functional/v1/test_api.py 0000666 0001750 0001750 00000123762 13267672254 022352 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test case that utilizes httplib2 against the API server"""
import hashlib
import httplib2
import sys
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests import functional
from glance.tests.utils import minimal_headers
from glance.tests.utils import skip_if_disabled
FIVE_KB = 5 * units.Ki
FIVE_GB = 5 * units.Gi
class TestApi(functional.FunctionalTest):
"""Functional tests using httplib2 against the API server"""
def _check_image_create(self, headers, status=http_client.CREATED,
image_data="*" * FIVE_KB):
# performs image_create request, checks the response and returns
# content
http = httplib2.Http()
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
response, content = http.request(
path, 'POST', headers=headers, body=image_data)
self.assertEqual(status, response.status)
return content.decode()
def test_checksum_32_chars_at_image_create(self):
self.cleanup()
self.start_servers(**self.__dict__.copy())
headers = minimal_headers('Image1')
image_data = b"*" * FIVE_KB
# checksum can be no longer that 32 characters (String(32))
headers['X-Image-Meta-Checksum'] = 'x' * 42
content = self._check_image_create(headers, http_client.BAD_REQUEST)
self.assertIn("Invalid checksum", content)
# test positive case as well
headers['X-Image-Meta-Checksum'] = hashlib.md5(image_data).hexdigest()
self._check_image_create(headers)
def test_param_int_too_large_at_create(self):
# currently 2 params min_disk/min_ram can cause DBError on save
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Integer field can't be greater than max 8-byte signed integer
for param in ['min_disk', 'min_ram']:
headers = minimal_headers('Image1')
# check that long numbers result in 400
headers['X-Image-Meta-%s' % param] = str(sys.maxsize + 1)
content = self._check_image_create(headers,
http_client.BAD_REQUEST)
self.assertIn("'%s' value out of range" % param, content)
# check that integers over 4 byte result in 400
headers['X-Image-Meta-%s' % param] = str(2 ** 31)
content = self._check_image_create(headers,
http_client.BAD_REQUEST)
self.assertIn("'%s' value out of range" % param, content)
# verify positive case as well
headers['X-Image-Meta-%s' % param] = str((2 ** 31) - 1)
self._check_image_create(headers)
def test_updating_is_public(self):
"""Verify that we can update the is_public attribute."""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# Verify no public images
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content.decode())
# Verify no public images
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content.decode())
# POST /images with private image named Image1
# attribute and no custom properties. Verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1', public=False)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertFalse(data['image']['is_public'])
# Retrieve image again to verify it was created appropriately
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image_headers = {
'x-image-meta-id': image_id,
'x-image-meta-name': 'Image1',
'x-image-meta-is_public': 'False',
'x-image-meta-status': 'active',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'x-image-meta-size': str(FIVE_KB)}
expected_std_headers = {
'content-length': str(FIVE_KB),
'content-type': 'application/octet-stream'}
for expected_key, expected_value in expected_image_headers.items():
self.assertEqual(expected_value, response[expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
response[expected_key]))
for expected_key, expected_value in expected_std_headers.items():
self.assertEqual(expected_value, response[expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
response[expected_key]))
self.assertEqual(image_data, content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
hashlib.md5(content).hexdigest())
# PUT image with custom properties to make public and then
# Verify 200 returned
headers = {'X-Image-Meta-is_public': 'True'}
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
is_public = image['image']['is_public']
self.assertTrue(
is_public,
"Expected image to be public but received %s" % is_public)
# PUT image with custom properties to make private and then
# Verify 200 returned
headers = {'X-Image-Meta-is_public': 'False'}
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
is_public = image['image']['is_public']
self.assertFalse(
is_public,
"Expected image to be private but received %s" % is_public)
@skip_if_disabled
def test_get_head_simple_post(self):
"""
We test the following sequential series of actions:
0. GET /images
- Verify no public images
1. GET /images/detail
- Verify no public images
2. POST /images with public image named Image1
and no custom properties
- Verify 201 returned
3. HEAD image
- Verify HTTP headers have correct information we just added
4. GET image
- Verify all information on image we just added is correct
5. GET /images
- Verify the image we just added is returned
6. GET /images/detail
- Verify the image we just added is returned
7. PUT image with custom properties of "distro" and "arch"
- Verify 200 returned
8. PUT image with too many custom properties
- Verify 413 returned
9. GET image
- Verify updated information about image was stored
10. PUT image
- Remove a previously existing property.
11. PUT image
- Add a previously deleted property.
12. PUT image/members/member1
- Add member1 to image
13. PUT image/members/member2
- Add member2 to image
14. GET image/members
- List image members
15. DELETE image/members/member1
- Delete image member1
16. PUT image/members
- Attempt to replace members with an overlimit amount
17. PUT image/members/member11
- Attempt to add a member while at limit
18. POST /images with another public image named Image2
- attribute and three custom properties, "distro", "arch" & "foo"
- Verify a 200 OK is returned
19. HEAD image2
- Verify image2 found now
20. GET /images
- Verify 2 public images
21. GET /images with filter on user-defined property "distro".
- Verify both images are returned
22. GET /images with filter on user-defined property 'distro' but
- with non-existent value. Verify no images are returned
23. GET /images with filter on non-existent user-defined property
- "boo". Verify no images are returned
24. GET /images with filter 'arch=i386'
- Verify only image2 is returned
25. GET /images with filter 'arch=x86_64'
- Verify only image1 is returned
26. GET /images with filter 'foo=bar'
- Verify only image2 is returned
27. DELETE image1
- Delete image
28. GET image/members
- List deleted image members
29. PUT image/members/member2
- Update existing member2 of deleted image
30. PUT image/members/member3
- Add member3 to deleted image
31. DELETE image/members/member2
- Delete member2 from deleted image
32. DELETE image2
- Delete image
33. GET /images
- Verify no images are listed
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# 0. GET /images
# Verify no public images
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content.decode())
# 1. GET /images/detail
# Verify no public images
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content.decode())
# 2. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
# 3. HEAD image
# Verify image found now
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
# 4. GET image
# Verify all information on image we just added is correct
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image_headers = {
'x-image-meta-id': image_id,
'x-image-meta-name': 'Image1',
'x-image-meta-is_public': 'True',
'x-image-meta-status': 'active',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'x-image-meta-size': str(FIVE_KB)}
expected_std_headers = {
'content-length': str(FIVE_KB),
'content-type': 'application/octet-stream'}
for expected_key, expected_value in expected_image_headers.items():
self.assertEqual(expected_value, response[expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
response[expected_key]))
for expected_key, expected_value in expected_std_headers.items():
self.assertEqual(expected_value, response[expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
response[expected_key]))
self.assertEqual(image_data, content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
hashlib.md5(content).hexdigest())
# 5. GET /images
# Verify one public image
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_result = {"images": [
{"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"name": "Image1",
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
"size": 5120}]}
self.assertEqual(expected_result, jsonutils.loads(content))
# 6. GET /images/detail
# Verify image and all its metadata
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image = {
"status": "active",
"name": "Image1",
"deleted": False,
"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"is_public": True,
"deleted_at": None,
"properties": {},
"size": 5120}
image = jsonutils.loads(content)
for expected_key, expected_value in expected_image.items():
self.assertEqual(expected_value, image['images'][0][expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
image['images'][0][expected_key]))
# 7. PUT image with custom properties of "distro" and "arch"
# Verify 200 returned
headers = {'X-Image-Meta-Property-Distro': 'Ubuntu',
'X-Image-Meta-Property-Arch': 'x86_64'}
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual("x86_64", data['image']['properties']['arch'])
self.assertEqual("Ubuntu", data['image']['properties']['distro'])
# 8. PUT image with too many custom properties
# Verify 413 returned
headers = {}
for i in range(11): # configured limit is 10
headers['X-Image-Meta-Property-foo%d' % i] = 'bar'
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE,
response.status)
# 9. GET /images/detail
# Verify image and all its metadata
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image = {
"status": "active",
"name": "Image1",
"deleted": False,
"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"is_public": True,
"deleted_at": None,
"properties": {'distro': 'Ubuntu', 'arch': 'x86_64'},
"size": 5120}
image = jsonutils.loads(content)
for expected_key, expected_value in expected_image.items():
self.assertEqual(expected_value, image['images'][0][expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
image['images'][0][expected_key]))
# 10. PUT image and remove a previously existing property.
headers = {'X-Image-Meta-Property-Arch': 'x86_64'}
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images'][0]
self.assertEqual(1, len(data['properties']))
self.assertEqual("x86_64", data['properties']['arch'])
# 11. PUT image and add a previously deleted property.
headers = {'X-Image-Meta-Property-Distro': 'Ubuntu',
'X-Image-Meta-Property-Arch': 'x86_64'}
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images'][0]
self.assertEqual(2, len(data['properties']))
self.assertEqual("x86_64", data['properties']['arch'])
self.assertEqual("Ubuntu", data['properties']['distro'])
self.assertNotEqual(data['created_at'], data['updated_at'])
# 12. Add member to image
path = ("http://%s:%d/v1/images/%s/members/pattieblack" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.NO_CONTENT, response.status)
# 13. Add member to image
path = ("http://%s:%d/v1/images/%s/members/pattiewhite" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.NO_CONTENT, response.status)
# 14. List image members
path = ("http://%s:%d/v1/images/%s/members" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['members']))
self.assertEqual('pattieblack', data['members'][0]['member_id'])
self.assertEqual('pattiewhite', data['members'][1]['member_id'])
# 15. Delete image member
path = ("http://%s:%d/v1/images/%s/members/pattieblack" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.NO_CONTENT, response.status)
# 16. Attempt to replace members with an overlimit amount
# Adding 11 image members should fail since configured limit is 10
path = ("http://%s:%d/v1/images/%s/members" %
("127.0.0.1", self.api_port, image_id))
memberships = []
for i in range(11):
member_id = "foo%d" % i
memberships.append(dict(member_id=member_id))
http = httplib2.Http()
body = jsonutils.dumps(dict(memberships=memberships))
response, content = http.request(path, 'PUT', body=body)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE,
response.status)
# 17. Attempt to add a member while at limit
# Adding an 11th member should fail since configured limit is 10
path = ("http://%s:%d/v1/images/%s/members" %
("127.0.0.1", self.api_port, image_id))
memberships = []
for i in range(10):
member_id = "foo%d" % i
memberships.append(dict(member_id=member_id))
http = httplib2.Http()
body = jsonutils.dumps(dict(memberships=memberships))
response, content = http.request(path, 'PUT', body=body)
self.assertEqual(http_client.NO_CONTENT, response.status)
path = ("http://%s:%d/v1/images/%s/members/fail_me" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE,
response.status)
# 18. POST /images with another public image named Image2
# attribute and three custom properties, "distro", "arch" & "foo".
# Verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image2')
headers['X-Image-Meta-Property-Distro'] = 'Ubuntu'
headers['X-Image-Meta-Property-Arch'] = 'i386'
headers['X-Image-Meta-Property-foo'] = 'bar'
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image2_id = data['image']['id']
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image2", data['image']['name'])
self.assertTrue(data['image']['is_public'])
self.assertEqual('Ubuntu', data['image']['properties']['distro'])
self.assertEqual('i386', data['image']['properties']['arch'])
self.assertEqual('bar', data['image']['properties']['foo'])
# 19. HEAD image2
# Verify image2 found now
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image2_id)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image2", response['x-image-meta-name'])
# 20. GET /images
# Verify 2 public images
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(2, len(images))
self.assertEqual(image2_id, images[0]['id'])
self.assertEqual(image_id, images[1]['id'])
# 21. GET /images with filter on user-defined property 'distro'.
# Verify both images are returned
path = "http://%s:%d/v1/images?property-distro=Ubuntu" % (
"127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(2, len(images))
self.assertEqual(image2_id, images[0]['id'])
self.assertEqual(image_id, images[1]['id'])
# 22. GET /images with filter on user-defined property 'distro' but
# with non-existent value. Verify no images are returned
path = "http://%s:%d/v1/images?property-distro=fedora" % (
"127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(0, len(images))
# 23. GET /images with filter on non-existent user-defined property
# 'boo'. Verify no images are returned
path = "http://%s:%d/v1/images?property-boo=bar" % ("127.0.0.1",
self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(0, len(images))
# 24. GET /images with filter 'arch=i386'
# Verify only image2 is returned
path = "http://%s:%d/v1/images?property-arch=i386" % ("127.0.0.1",
self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# 25. GET /images with filter 'arch=x86_64'
# Verify only image1 is returned
path = "http://%s:%d/v1/images?property-arch=x86_64" % ("127.0.0.1",
self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_id, images[0]['id'])
# 26. GET /images with filter 'foo=bar'
# Verify only image2 is returned
path = "http://%s:%d/v1/images?property-foo=bar" % ("127.0.0.1",
self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# 27. DELETE image1
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
# 28. Try to list members of deleted image
path = ("http://%s:%d/v1/images/%s/members" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.NOT_FOUND, response.status)
# 29. Try to update member of deleted image
path = ("http://%s:%d/v1/images/%s/members" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
fixture = [{'member_id': 'pattieblack', 'can_share': 'false'}]
body = jsonutils.dumps(dict(memberships=fixture))
response, content = http.request(path, 'PUT', body=body)
self.assertEqual(http_client.NOT_FOUND, response.status)
# 30. Try to add member to deleted image
path = ("http://%s:%d/v1/images/%s/members/chickenpattie" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'PUT')
self.assertEqual(http_client.NOT_FOUND, response.status)
# 31. Try to delete member of deleted image
path = ("http://%s:%d/v1/images/%s/members/pattieblack" %
("127.0.0.1", self.api_port, image_id))
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.NOT_FOUND, response.status)
# 32. DELETE image2
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image2_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
# 33. GET /images
# Verify no images are listed
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(0, len(images))
# 34. HEAD /images/detail
path = "http://%s:%d/v1/images/detail" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.METHOD_NOT_ALLOWED, response.status)
self.assertEqual('GET', response.get('allow'))
self.stop_servers()
def test_download_non_exists_image_raises_http_forbidden(self):
"""
We test the following sequential series of actions::
0. POST /images with public image named Image1
and no custom properties
- Verify 201 returned
1. HEAD image
- Verify HTTP headers have correct information we just added
2. GET image
- Verify all information on image we just added is correct
3. DELETE image1
- Delete the newly added image
4. GET image
- Verify that 403 HTTPForbidden exception is raised prior to
404 HTTPNotFound
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
# 1. HEAD image
# Verify image found now
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
# 2. GET /images
# Verify one public image
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_result = {"images": [
{"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"name": "Image1",
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
"size": 5120}]}
self.assertEqual(expected_result, jsonutils.loads(content))
# 3. DELETE image1
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
# 4. GET image
# Verify that 403 HTTPForbidden exception is raised prior to
# 404 HTTPNotFound
rules = {"download_image": '!'}
self.set_policy_rules(rules)
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.FORBIDDEN, response.status)
self.stop_servers()
def test_download_non_exists_image_raises_http_not_found(self):
"""
We test the following sequential series of actions:
0. POST /images with public image named Image1
and no custom properties
- Verify 201 returned
1. HEAD image
- Verify HTTP headers have correct information we just added
2. GET image
- Verify all information on image we just added is correct
3. DELETE image1
- Delete the newly added image
4. GET image
- Verify that 404 HTTPNotFound exception is raised
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
# 1. HEAD image
# Verify image found now
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
# 2. GET /images
# Verify one public image
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_result = {"images": [
{"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"name": "Image1",
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
"size": 5120}]}
self.assertEqual(expected_result, jsonutils.loads(content))
# 3. DELETE image1
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
# 4. GET image
# Verify that 404 HTTPNotFound exception is raised
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image_id)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.NOT_FOUND, response.status)
self.stop_servers()
def test_status_cannot_be_manipulated_directly(self):
self.cleanup()
self.start_servers(**self.__dict__.copy())
headers = minimal_headers('Image1')
# Create a 'queued' image
http = httplib2.Http()
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Disk-Format': 'raw',
'X-Image-Meta-Container-Format': 'bare'}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
response, content = http.request(path, 'POST', headers=headers,
body=None)
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('queued', image['status'])
# Ensure status of 'queued' image can't be changed
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
image['id'])
http = httplib2.Http()
headers = {'X-Image-Meta-Status': 'active'}
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('queued', response['x-image-meta-status'])
# We allow 'setting' to the same status
http = httplib2.Http()
headers = {'X-Image-Meta-Status': 'queued'}
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('queued', response['x-image-meta-status'])
# Make image active
http = httplib2.Http()
headers = {'Content-Type': 'application/octet-stream'}
response, content = http.request(path, 'PUT', headers=headers,
body='data')
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
# Ensure status of 'active' image can't be changed
http = httplib2.Http()
headers = {'X-Image-Meta-Status': 'queued'}
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('active', response['x-image-meta-status'])
# We allow 'setting' to the same status
http = httplib2.Http()
headers = {'X-Image-Meta-Status': 'active'}
response, content = http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
response, content = http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('active', response['x-image-meta-status'])
# Create a 'queued' image, ensure 'status' header is ignored
http = httplib2.Http()
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Status': 'active'}
response, content = http.request(path, 'POST', headers=headers,
body=None)
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('queued', image['status'])
# Create an 'active' image, ensure 'status' header is ignored
http = httplib2.Http()
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Disk-Format': 'raw',
'X-Image-Meta-Status': 'queued',
'X-Image-Meta-Container-Format': 'bare'}
response, content = http.request(path, 'POST', headers=headers,
body='data')
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
self.stop_servers()
glance-16.0.1/glance/tests/functional/store_utils.py 0000666 0001750 0001750 00000005450 13267672245 022561 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# Copyright 2012 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility methods to set testcases up for Swift tests.
"""
from __future__ import print_function
import threading
from oslo_utils import units
from six.moves import BaseHTTPServer
from six.moves import http_client as http
FIVE_KB = 5 * units.Ki
class RemoteImageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_HEAD(self):
"""
Respond to an image HEAD request fake metadata
"""
if 'images' in self.path:
self.send_response(http.OK)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('Content-Length', FIVE_KB)
self.end_headers()
return
else:
self.send_error(http.NOT_FOUND, 'File Not Found: %s' % self.path)
return
def do_GET(self):
"""
Respond to an image GET request with fake image content.
"""
if 'images' in self.path:
self.send_response(http.OK)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('Content-Length', FIVE_KB)
self.end_headers()
image_data = b'*' * FIVE_KB
self.wfile.write(image_data)
self.wfile.close()
return
else:
self.send_error(http.NOT_FOUND, 'File Not Found: %s' % self.path)
return
def log_message(self, format, *args):
"""
Simple override to prevent writing crap to stderr...
"""
pass
def setup_http(test):
server_class = BaseHTTPServer.HTTPServer
remote_server = server_class(('127.0.0.1', 0), RemoteImageHandler)
remote_ip, remote_port = remote_server.server_address
def serve_requests(httpd):
httpd.serve_forever()
threading.Thread(target=serve_requests, args=(remote_server,)).start()
test.http_server = remote_server
test.http_ip = remote_ip
test.http_port = remote_port
test.addCleanup(test.http_server.shutdown)
def get_http_uri(test, image_id):
uri = ('http://%(http_ip)s:%(http_port)d/images/' %
{'http_ip': test.http_ip, 'http_port': test.http_port})
uri += image_id
return uri
glance-16.0.1/glance/tests/functional/test_glance_manage.py 0000666 0001750 0001750 00000016064 13267672245 024010 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test cases for glance-manage"""
import os
import sys
from oslo_config import cfg
from oslo_db import options as db_options
from glance.common import utils
from glance.db import migration as db_migration
from glance.db.sqlalchemy import alembic_migrations
from glance.db.sqlalchemy.alembic_migrations import data_migrations
from glance.db.sqlalchemy import api as db_api
from glance.tests import functional
from glance.tests.utils import depends_on_exe
from glance.tests.utils import execute
from glance.tests.utils import skip_if_disabled
CONF = cfg.CONF
class TestGlanceManage(functional.FunctionalTest):
"""Functional tests for glance-manage"""
def setUp(self):
super(TestGlanceManage, self).setUp()
conf_dir = os.path.join(self.test_dir, 'etc')
utils.safe_mkdirs(conf_dir)
self.conf_filepath = os.path.join(conf_dir, 'glance-manage.conf')
self.db_filepath = os.path.join(self.test_dir, 'tests.sqlite')
self.connection = ('sql_connection = sqlite:///%s' %
self.db_filepath)
db_options.set_defaults(CONF, connection='sqlite:///%s' %
self.db_filepath)
def _db_command(self, db_method):
with open(self.conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write(self.connection)
conf_file.flush()
cmd = ('%s -m glance.cmd.manage --config-file %s db %s' %
(sys.executable, self.conf_filepath, db_method))
return execute(cmd, raise_error=True)
def _check_db(self, expected_exitcode):
with open(self.conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write(self.connection)
conf_file.flush()
cmd = ('%s -m glance.cmd.manage --config-file %s db check' %
(sys.executable, self.conf_filepath))
exitcode, out, err = execute(cmd, raise_error=True,
expected_exitcode=expected_exitcode)
return exitcode, out
def _assert_table_exists(self, db_table):
cmd = ("sqlite3 {0} \"SELECT name FROM sqlite_master WHERE "
"type='table' AND name='{1}'\"").format(self.db_filepath,
db_table)
exitcode, out, err = execute(cmd, raise_error=True)
msg = "Expected table {0} was not found in the schema".format(db_table)
self.assertEqual(out.rstrip().decode("utf-8"), db_table, msg)
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_db_creation(self):
"""Test schema creation by db_sync on a fresh DB"""
self._db_command(db_method='sync')
for table in ['images', 'image_tags', 'image_locations',
'image_members', 'image_properties']:
self._assert_table_exists(table)
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_sync(self):
"""Test DB sync which internally calls EMC"""
self._db_command(db_method='sync')
contract_head = alembic_migrations.get_alembic_branch_head(
db_migration.CONTRACT_BRANCH)
cmd = ("sqlite3 {0} \"SELECT version_num FROM alembic_version\""
).format(self.db_filepath)
exitcode, out, err = execute(cmd, raise_error=True)
self.assertEqual(contract_head, out.rstrip().decode("utf-8"))
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_check(self):
exitcode, out = self._check_db(3)
self.assertEqual(3, exitcode)
self._db_command(db_method='expand')
if data_migrations.has_pending_migrations(db_api.get_engine()):
exitcode, out = self._check_db(4)
self.assertEqual(4, exitcode)
self._db_command(db_method='migrate')
exitcode, out = self._check_db(5)
self.assertEqual(5, exitcode)
self._db_command(db_method='contract')
exitcode, out = self._check_db(0)
self.assertEqual(0, exitcode)
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_expand(self):
"""Test DB expand"""
self._db_command(db_method='expand')
expand_head = alembic_migrations.get_alembic_branch_head(
db_migration.EXPAND_BRANCH)
cmd = ("sqlite3 {0} \"SELECT version_num FROM alembic_version\""
).format(self.db_filepath)
exitcode, out, err = execute(cmd, raise_error=True)
self.assertEqual(expand_head, out.rstrip().decode("utf-8"))
exitcode, out, err = self._db_command(db_method='expand')
self.assertIn('Database expansion is up to date. '
'No expansion needed.', out)
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_migrate(self):
"""Test DB migrate"""
self._db_command(db_method='expand')
if data_migrations.has_pending_migrations(db_api.get_engine()):
self._db_command(db_method='migrate')
expand_head = alembic_migrations.get_alembic_branch_head(
db_migration.EXPAND_BRANCH)
cmd = ("sqlite3 {0} \"SELECT version_num FROM alembic_version\""
).format(self.db_filepath)
exitcode, out, err = execute(cmd, raise_error=True)
self.assertEqual(expand_head, out.rstrip().decode("utf-8"))
self.assertEqual(False, data_migrations.has_pending_migrations(
db_api.get_engine()))
if data_migrations.has_pending_migrations(db_api.get_engine()):
exitcode, out, err = self._db_command(db_method='migrate')
self.assertIn('Database migration is up to date. No migration '
'needed.', out)
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_contract(self):
"""Test DB contract"""
self._db_command(db_method='expand')
if data_migrations.has_pending_migrations(db_api.get_engine()):
self._db_command(db_method='migrate')
self._db_command(db_method='contract')
contract_head = alembic_migrations.get_alembic_branch_head(
db_migration.CONTRACT_BRANCH)
cmd = ("sqlite3 {0} \"SELECT version_num FROM alembic_version\""
).format(self.db_filepath)
exitcode, out, err = execute(cmd, raise_error=True)
self.assertEqual(contract_head, out.rstrip().decode("utf-8"))
exitcode, out, err = self._db_command(db_method='contract')
self.assertIn('Database is up to date. No migrations needed.', out)
glance-16.0.1/glance/tests/functional/test_wsgi.py 0000666 0001750 0001750 00000003506 13267672245 022215 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for `glance.wsgi`."""
import socket
import time
from oslo_config import cfg
import testtools
from glance.common import wsgi
CONF = cfg.CONF
class TestWSGIServer(testtools.TestCase):
"""WSGI server tests."""
def test_client_socket_timeout(self):
CONF.set_default("workers", 0)
CONF.set_default("client_socket_timeout", 1)
"""Verify connections are timed out as per 'client_socket_timeout'"""
greetings = b'Hello, World!!!'
def hello_world(env, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return [greetings]
server = wsgi.Server()
server.start(hello_world, 0)
port = server.sock.getsockname()[1]
def get_request(delay=0.0):
sock = socket.socket()
sock.connect(('127.0.0.1', port))
time.sleep(delay)
sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
return sock.recv(1024)
# Should succeed - no timeout
self.assertIn(greetings, get_request())
# Should fail - connection timed out so we get nothing from the server
self.assertFalse(get_request(delay=1.1))
glance-16.0.1/glance/tests/functional/test_client_redirects.py 0000666 0001750 0001750 00000011672 13267672245 024571 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test cases testing glance client redirect-following."""
import eventlet.patcher
from six.moves import http_client as http
import webob.dec
import webob.exc
from glance.common import client
from glance.common import exception
from glance.common import wsgi
from glance.tests import functional
from glance.tests import utils
eventlet.patcher.monkey_patch(socket=True)
def RedirectTestApp(name):
class App(object):
"""
Test WSGI application which can respond with multiple kinds of HTTP
redirects and is used to verify Glance client redirects.
"""
def __init__(self):
"""
Initialize app with a name and port.
"""
self.name = name
@webob.dec.wsgify
def __call__(self, request):
"""
Handles all requests to the application.
"""
base = "http://%s" % request.host
path = request.path_qs
if path == "/":
return "root"
elif path == "/302":
url = "%s/success" % base
raise webob.exc.HTTPFound(location=url)
elif path == "/302?with_qs=yes":
url = "%s/success?with_qs=yes" % base
raise webob.exc.HTTPFound(location=url)
elif path == "/infinite_302":
raise webob.exc.HTTPFound(location=request.url)
elif path.startswith("/redirect-to"):
url = "http://127.0.0.1:%s/success" % path.split("-")[-1]
raise webob.exc.HTTPFound(location=url)
elif path == "/success":
return "success_from_host_%s" % self.name
elif path == "/success?with_qs=yes":
return "success_with_qs"
return "fail"
return App
class TestClientRedirects(functional.FunctionalTest):
def setUp(self):
super(TestClientRedirects, self).setUp()
self.port_one = utils.get_unused_port()
self.port_two = utils.get_unused_port()
server_one = wsgi.Server()
server_two = wsgi.Server()
self.config(bind_host='127.0.0.1')
self.config(workers=0)
server_one.start(RedirectTestApp("one")(), self.port_one)
server_two.start(RedirectTestApp("two")(), self.port_two)
self.client = client.BaseClient("127.0.0.1", self.port_one)
def test_get_without_redirect(self):
"""
Test GET with no redirect
"""
response = self.client.do_request("GET", "/")
self.assertEqual(http.OK, response.status)
self.assertEqual(b"root", response.read())
def test_get_with_one_redirect(self):
"""
Test GET with one 302 FOUND redirect
"""
response = self.client.do_request("GET", "/302")
self.assertEqual(http.OK, response.status)
self.assertEqual(b"success_from_host_one", response.read())
def test_get_with_one_redirect_query_string(self):
"""
Test GET with one 302 FOUND redirect w/ a query string
"""
response = self.client.do_request("GET", "/302",
params={'with_qs': 'yes'})
self.assertEqual(http.OK, response.status)
self.assertEqual(b"success_with_qs", response.read())
def test_get_with_max_redirects(self):
"""
Test we don't redirect forever.
"""
self.assertRaises(exception.MaxRedirectsExceeded,
self.client.do_request,
"GET",
"/infinite_302")
def test_post_redirect(self):
"""
Test POST with 302 redirect
"""
response = self.client.do_request("POST", "/302")
self.assertEqual(http.OK, response.status)
self.assertEqual(b"success_from_host_one", response.read())
def test_redirect_to_new_host(self):
"""
Test redirect to one host and then another.
"""
url = "/redirect-to-%d" % self.port_two
response = self.client.do_request("POST", url)
self.assertEqual(http.OK, response.status)
self.assertEqual(b"success_from_host_two", response.read())
response = self.client.do_request("POST", "/success")
self.assertEqual(http.OK, response.status)
self.assertEqual(b"success_from_host_one", response.read())
glance-16.0.1/glance/tests/functional/test_bin_glance_cache_manage.py 0000666 0001750 0001750 00000027161 13267672245 025763 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test case that utilizes the bin/glance-cache-manage CLI tool"""
import datetime
import hashlib
import os
import sys
import httplib2
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests import functional
from glance.tests.utils import execute
from glance.tests.utils import minimal_headers
FIVE_KB = 5 * units.Ki
class TestBinGlanceCacheManage(functional.FunctionalTest):
"""Functional tests for the bin/glance CLI tool"""
def setUp(self):
self.image_cache_driver = "sqlite"
super(TestBinGlanceCacheManage, self).setUp()
self.api_server.deployment_flavor = "cachemanagement"
# NOTE(sirp): This is needed in case we are running the tests under an
# environment in which OS_AUTH_STRATEGY=keystone. The test server we
# spin up won't have keystone support, so we need to switch to the
# NoAuth strategy.
os.environ['OS_AUTH_STRATEGY'] = 'noauth'
os.environ['OS_AUTH_URL'] = ''
def add_image(self, name):
"""
Adds an image with supplied name and returns the newly-created
image identifier.
"""
image_data = b"*" * FIVE_KB
headers = minimal_headers(name)
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual(name, data['image']['name'])
self.assertTrue(data['image']['is_public'])
return data['image']['id']
def is_image_cached(self, image_id):
"""
Return True if supplied image ID is cached, False otherwise
"""
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
cmd = "%s --port=%d list-cached" % (exe_cmd, self.api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
out = out.decode('utf-8')
return image_id in out
def iso_date(self, image_id):
"""
Return True if supplied image ID is cached, False otherwise
"""
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
cmd = "%s --port=%d list-cached" % (exe_cmd, self.api_port)
exitcode, out, err = execute(cmd)
out = out.decode('utf-8')
return datetime.datetime.utcnow().strftime("%Y-%m-%d") in out
def test_no_cache_enabled(self):
"""
Test that cache index command works
"""
self.cleanup()
self.api_server.deployment_flavor = ''
self.start_servers() # Not passing in cache_manage in pipeline...
api_port = self.api_port
# Verify decent error message returned
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(1, exitcode)
self.assertIn(b'Cache management middleware not enabled on host',
out.strip())
self.stop_servers()
def test_cache_index(self):
"""
Test that cache index command works
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
api_port = self.api_port
# Verify no cached images
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'No cached images', out.strip())
ids = {}
# Add a few images and cache the second one of them
# by GETing the image...
for x in range(4):
ids[x] = self.add_image("Image%s" % x)
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", api_port,
ids[1])
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertTrue(self.is_image_cached(ids[1]),
"%s is not cached." % ids[1])
self.assertTrue(self.iso_date(ids[1]))
self.stop_servers()
def test_queue(self):
"""
Test that we can queue and fetch images using the
CLI utility
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
api_port = self.api_port
# Verify no cached images
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'No cached images', out.strip())
# Verify no queued images
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'No queued images', out.strip())
ids = {}
# Add a few images and cache the second one of them
# by GETing the image...
for x in range(4):
ids[x] = self.add_image("Image%s" % x)
# Queue second image and then cache it
cmd = "%s --port=%d --force queue-image %s" % (
exe_cmd, api_port, ids[1])
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
# Verify queued second image
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
out = out.decode('utf-8')
self.assertIn(ids[1], out, 'Image %s was not queued!' % ids[1])
# Cache images in the queue by running the prefetcher
cache_config_filepath = os.path.join(self.test_dir, 'etc',
'glance-cache.conf')
cache_file_options = {
'image_cache_dir': self.api_server.image_cache_dir,
'image_cache_driver': self.image_cache_driver,
'registry_port': self.registry_server.bind_port,
'lock_path': self.test_dir,
'log_file': os.path.join(self.test_dir, 'cache.log'),
'metadata_encryption_key': "012345678901234567890123456789ab",
'filesystem_store_datadir': self.test_dir
}
with open(cache_config_filepath, 'w') as cache_file:
cache_file.write("""[DEFAULT]
debug = True
lock_path = %(lock_path)s
image_cache_dir = %(image_cache_dir)s
image_cache_driver = %(image_cache_driver)s
registry_host = 127.0.0.1
registry_port = %(registry_port)s
metadata_encryption_key = %(metadata_encryption_key)s
log_file = %(log_file)s
[glance_store]
filesystem_store_datadir=%(filesystem_store_datadir)s
""" % cache_file_options)
cmd = ("%s -m glance.cmd.cache_prefetcher --config-file %s" %
(sys.executable, cache_config_filepath))
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertEqual(b'', out.strip(), out)
# Verify no queued images
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'No queued images', out.strip())
# Verify second image now cached
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
out = out.decode('utf-8')
self.assertIn(ids[1], out, 'Image %s was not cached!' % ids[1])
# Queue third image and then delete it from queue
cmd = "%s --port=%d --force queue-image %s" % (
exe_cmd, api_port, ids[2])
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
# Verify queued third image
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
out = out.decode('utf-8')
self.assertIn(ids[2], out, 'Image %s was not queued!' % ids[2])
# Delete the image from the queue
cmd = ("%s --port=%d --force "
"delete-queued-image %s") % (exe_cmd, api_port, ids[2])
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
# Verify no queued images
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'No queued images', out.strip())
# Queue all images
for x in range(4):
cmd = ("%s --port=%d --force "
"queue-image %s") % (exe_cmd, api_port, ids[x])
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
# Verify queued third image
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'Found 3 queued images', out)
# Delete the image from the queue
cmd = ("%s --port=%d --force "
"delete-all-queued-images") % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
# Verify nothing in queue anymore
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
self.assertIn(b'No queued images', out.strip())
# verify two image id when queue-image
cmd = ("%s --port=%d --force "
"queue-image %s %s") % (exe_cmd, api_port, ids[0], ids[1])
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(1, exitcode)
self.assertIn(b'Please specify one and only ID of '
b'the image you wish to ', out.strip())
# verify two image id when delete-queued-image
cmd = ("%s --port=%d --force delete-queued-image "
"%s %s") % (exe_cmd, api_port, ids[0], ids[1])
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(1, exitcode)
self.assertIn(b'Please specify one and only ID of '
b'the image you wish to ', out.strip())
# verify two image id when delete-cached-image
cmd = ("%s --port=%d --force delete-cached-image "
"%s %s") % (exe_cmd, api_port, ids[0], ids[1])
exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(1, exitcode)
self.assertIn(b'Please specify one and only ID of '
b'the image you wish to ', out.strip())
self.stop_servers()
glance-16.0.1/glance/tests/functional/test_glance_replicator.py 0000666 0001750 0001750 00000002327 13267672245 024721 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test cases for glance-replicator"""
import sys
from glance.tests import functional
from glance.tests.utils import execute
class TestGlanceReplicator(functional.FunctionalTest):
"""Functional tests for glance-replicator"""
def test_compare(self):
# Test for issue: https://bugs.launchpad.net/glance/+bug/1598928
cmd = ('%s -m glance.cmd.replicator '
'compare az1:9292 az2:9292 --debug' %
(sys.executable,))
exitcode, out, err = execute(cmd, raise_error=False)
self.assertIn(
b'Request: GET http://az1:9292/v1/images/detail?is_public=None',
err
)
glance-16.0.1/glance/tests/functional/test_cors_middleware.py 0000666 0001750 0001750 00000006102 13267672245 024402 0 ustar zuul zuul 0000000 0000000 # All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests cors middleware."""
import httplib2
from six.moves import http_client
from glance.tests import functional
class TestCORSMiddleware(functional.FunctionalTest):
'''Provide a basic smoke test to ensure CORS middleware is active.
The tests below provide minimal confirmation that the CORS middleware
is active, and may be configured. For comprehensive tests, please consult
the test suite in oslo_middleware.
'''
def setUp(self):
super(TestCORSMiddleware, self).setUp()
# Cleanup is handled in teardown of the parent class.
self.start_servers(**self.__dict__.copy())
self.http = httplib2.Http()
self.api_path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
def test_valid_cors_options_request(self):
(r_headers, content) = self.http.request(
self.api_path,
'OPTIONS',
headers={
'Origin': 'http://valid.example.com',
'Access-Control-Request-Method': 'GET'
})
self.assertEqual(http_client.OK, r_headers.status)
self.assertIn('access-control-allow-origin', r_headers)
self.assertEqual('http://valid.example.com',
r_headers['access-control-allow-origin'])
def test_invalid_cors_options_request(self):
(r_headers, content) = self.http.request(
self.api_path,
'OPTIONS',
headers={
'Origin': 'http://invalid.example.com',
'Access-Control-Request-Method': 'GET'
})
self.assertEqual(http_client.OK, r_headers.status)
self.assertNotIn('access-control-allow-origin', r_headers)
def test_valid_cors_get_request(self):
(r_headers, content) = self.http.request(
self.api_path,
'GET',
headers={
'Origin': 'http://valid.example.com'
})
self.assertEqual(http_client.OK, r_headers.status)
self.assertIn('access-control-allow-origin', r_headers)
self.assertEqual('http://valid.example.com',
r_headers['access-control-allow-origin'])
def test_invalid_cors_get_request(self):
(r_headers, content) = self.http.request(
self.api_path,
'GET',
headers={
'Origin': 'http://invalid.example.com'
})
self.assertEqual(http_client.OK, r_headers.status)
self.assertNotIn('access-control-allow-origin', r_headers)
glance-16.0.1/glance/tests/functional/test_api.py 0000666 0001750 0001750 00000030473 13267672245 022020 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Version-independent api tests"""
import httplib2
from oslo_serialization import jsonutils
from six.moves import http_client
from glance.tests import functional
# TODO(rosmaita): all the EXPERIMENTAL stuff in this file can be ripped out
# when v2.6 becomes CURRENT in Queens
def _generate_v1_versions(url):
v1_versions = {'versions': [
{
'id': 'v1.1',
'status': 'DEPRECATED',
'links': [{'rel': 'self', 'href': url % '1'}],
},
{
'id': 'v1.0',
'status': 'DEPRECATED',
'links': [{'rel': 'self', 'href': url % '1'}],
},
]}
return v1_versions
def _generate_v2_versions(url):
version_list = []
version_list.extend([
{
'id': 'v2.6',
'status': 'CURRENT',
'links': [{'rel': 'self', 'href': url % '2'}],
},
{
'id': 'v2.5',
'status': 'SUPPORTED',
'links': [{'rel': 'self', 'href': url % '2'}],
},
{
'id': 'v2.4',
'status': 'SUPPORTED',
'links': [{'rel': 'self', 'href': url % '2'}],
},
{
'id': 'v2.3',
'status': 'SUPPORTED',
'links': [{'rel': 'self', 'href': url % '2'}],
},
{
'id': 'v2.2',
'status': 'SUPPORTED',
'links': [{'rel': 'self', 'href': url % '2'}],
},
{
'id': 'v2.1',
'status': 'SUPPORTED',
'links': [{'rel': 'self', 'href': url % '2'}],
},
{
'id': 'v2.0',
'status': 'SUPPORTED',
'links': [{'rel': 'self', 'href': url % '2'}],
}
])
v2_versions = {'versions': version_list}
return v2_versions
def _generate_all_versions(url):
v1 = _generate_v1_versions(url)
v2 = _generate_v2_versions(url)
all_versions = {'versions': v2['versions'] + v1['versions']}
return all_versions
class TestApiVersions(functional.FunctionalTest):
def test_version_configurations(self):
"""Test that versioning is handled properly through all channels"""
# v1 and v2 api enabled
self.start_servers(**self.__dict__.copy())
url = 'http://127.0.0.1:%d/v%%s/' % self.api_port
versions = _generate_all_versions(url)
# Verify version choices returned.
path = 'http://%s:%d' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(versions, content)
def test_v2_api_configuration(self):
self.api_server.enable_v1_api = False
self.api_server.enable_v2_api = True
self.start_servers(**self.__dict__.copy())
url = 'http://127.0.0.1:%d/v%%s/' % self.api_port
versions = _generate_v2_versions(url)
# Verify version choices returned.
path = 'http://%s:%d' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(versions, content)
def test_v1_api_configuration(self):
self.api_server.enable_v1_api = True
self.api_server.enable_v2_api = False
self.start_servers(**self.__dict__.copy())
url = 'http://127.0.0.1:%d/v%%s/' % self.api_port
versions = _generate_v1_versions(url)
# Verify version choices returned.
path = 'http://%s:%d' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(versions, content)
class TestApiPaths(functional.FunctionalTest):
def setUp(self):
super(TestApiPaths, self).setUp()
self.start_servers(**self.__dict__.copy())
url = 'http://127.0.0.1:%d/v%%s/' % self.api_port
self.versions = _generate_all_versions(url)
images = {'images': []}
self.images_json = jsonutils.dumps(images)
def test_get_root_path(self):
"""Assert GET / with `no Accept:` header.
Verify version choices returned.
Bug lp:803260 no Accept header causes a 500 in glance-api
"""
path = 'http://%s:%d' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_images_path(self):
"""Assert GET /images with `no Accept:` header.
Verify version choices returned.
"""
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_v1_images_path(self):
"""GET /v1/images with `no Accept:` header.
Verify empty images list returned.
"""
path = 'http://%s:%d/v1/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
def test_get_root_path_with_unknown_header(self):
"""Assert GET / with Accept: unknown header
Verify version choices returned. Verify message in API log about
unknown accept header.
"""
path = 'http://%s:%d/' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
headers = {'Accept': 'unknown'}
response, content_json = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_root_path_with_openstack_header(self):
"""Assert GET / with an Accept: application/vnd.openstack.images-v1
Verify empty image list returned
"""
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
headers = {'Accept': 'application/vnd.openstack.images-v1'}
response, content = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual(self.images_json, content.decode())
def test_get_images_path_with_openstack_header(self):
"""Assert GET /images with a
`Accept: application/vnd.openstack.compute-v1` header.
Verify version choices returned. Verify message in API log
about unknown accept header.
"""
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
headers = {'Accept': 'application/vnd.openstack.compute-v1'}
response, content_json = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_v10_images_path(self):
"""Assert GET /v1.0/images with no Accept: header
Verify version choices returned
"""
path = 'http://%s:%d/v1.a/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
def test_get_v1a_images_path(self):
"""Assert GET /v1.a/images with no Accept: header
Verify version choices returned
"""
path = 'http://%s:%d/v1.a/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
def test_get_va1_images_path(self):
"""Assert GET /va.1/images with no Accept: header
Verify version choices returned
"""
path = 'http://%s:%d/va.1/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_versions_path(self):
"""Assert GET /versions with no Accept: header
Verify version choices returned
"""
path = 'http://%s:%d/versions' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_versions_path_with_openstack_header(self):
"""Assert GET /versions with the
`Accept: application/vnd.openstack.images-v1` header.
Verify version choices returned.
"""
path = 'http://%s:%d/versions' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
headers = {'Accept': 'application/vnd.openstack.images-v1'}
response, content_json = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.OK, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_v1_versions_path(self):
"""Assert GET /v1/versions with `no Accept:` header
Verify 404 returned
"""
path = 'http://%s:%d/v1/versions' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'GET')
self.assertEqual(http_client.NOT_FOUND, response.status)
def test_get_versions_choices(self):
"""Verify version choices returned"""
path = 'http://%s:%d/v10' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_images_path_with_openstack_v2_header(self):
"""Assert GET /images with a
`Accept: application/vnd.openstack.compute-v2` header.
Verify version choices returned. Verify message in API log
about unknown version in accept header.
"""
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
headers = {'Accept': 'application/vnd.openstack.images-v10'}
response, content_json = http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
def test_get_v12_images_path(self):
"""Assert GET /v1.2/images with `no Accept:` header
Verify version choices returned
"""
path = 'http://%s:%d/v1.2/images' % ('127.0.0.1', self.api_port)
http = httplib2.Http()
response, content_json = http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
content = jsonutils.loads(content_json.decode())
self.assertEqual(self.versions, content)
glance-16.0.1/glance/tests/functional/test_ssl.py 0000666 0001750 0001750 00000005620 13267672245 022044 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import unittest
import httplib2
import six
from six.moves import http_client as http
from glance.tests import functional
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', 'var'))
class TestSSL(functional.FunctionalTest):
"""Functional tests verifying SSL communication"""
def setUp(self):
super(TestSSL, self).setUp()
if getattr(self, 'inited', False):
return
self.inited = False
self.disabled = True
# NOTE (stevelle): Test key/cert/CA file created as per:
# http://nrocco.github.io/2013/01/25/
# self-signed-ssl-certificate-chains.html
# For these tests certificate.crt must be created with 'Common Name'
# set to 127.0.0.1
self.key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key')
if not os.path.exists(self.key_file):
self.disabled_message = ("Could not find private key file %s" %
self.key_file)
self.inited = True
return
self.cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
if not os.path.exists(self.cert_file):
self.disabled_message = ("Could not find certificate file %s" %
self.cert_file)
self.inited = True
return
self.ca_file = os.path.join(TEST_VAR_DIR, 'ca.crt')
if not os.path.exists(self.ca_file):
self.disabled_message = ("Could not find CA file %s" %
self.ca_file)
self.inited = True
return
self.inited = True
self.disabled = False
def tearDown(self):
super(TestSSL, self).tearDown()
if getattr(self, 'inited', False):
return
@unittest.skipIf(six.PY3, 'SSL handshakes are broken in PY3')
def test_ssl_ok(self):
"""Make sure the public API works with HTTPS."""
self.cleanup()
self.start_servers(**self.__dict__.copy())
path = "https://%s:%d/versions" % ("127.0.0.1", self.api_port)
https = httplib2.Http(ca_certs=self.ca_file)
response, content = https.request(path, 'GET')
self.assertEqual(http.OK, response.status)
glance-16.0.1/glance/tests/functional/test_gzip_middleware.py 0000666 0001750 0001750 00000003231 13267672245 024405 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests gzip middleware."""
import httplib2
from glance.tests import functional
from glance.tests import utils
class GzipMiddlewareTest(functional.FunctionalTest):
@utils.skip_if_disabled
def test_gzip_requests(self):
self.cleanup()
self.start_servers(**self.__dict__.copy())
def request(path, headers=None):
# We don't care what version we're using here so,
# sticking with latest
url = 'http://127.0.0.1:%s/v2/%s' % (self.api_port, path)
http = httplib2.Http()
return http.request(url, 'GET', headers=headers)
# Accept-Encoding: Identity
headers = {'Accept-Encoding': 'identity'}
response, content = request('images', headers=headers)
self.assertIsNone(response.get("-content-encoding"))
# Accept-Encoding: gzip
headers = {'Accept-Encoding': 'gzip'}
response, content = request('images', headers=headers)
self.assertEqual('gzip', response.get("-content-encoding"))
self.stop_servers()
glance-16.0.1/glance/tests/functional/test_logging.py 0000666 0001750 0001750 00000006010 13267672245 022663 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test case that tests logging output"""
import os
import stat
import httplib2
from six.moves import http_client as http
from glance.tests import functional
class TestLogging(functional.FunctionalTest):
"""Functional tests for Glance's logging output"""
def test_debug(self):
"""
Test logging output proper when debug is on.
"""
self.cleanup()
self.start_servers()
# The default functional test case has both debug on. Let's verify
# that debug statements appear in both the API and registry logs.
self.assertTrue(os.path.exists(self.api_server.log_file))
with open(self.api_server.log_file, 'r') as f:
api_log_out = f.read()
self.assertIn('DEBUG glance', api_log_out)
self.assertTrue(os.path.exists(self.registry_server.log_file))
with open(self.registry_server.log_file, 'r') as freg:
registry_log_out = freg.read()
self.assertIn('DEBUG glance', registry_log_out)
self.stop_servers()
def test_no_debug(self):
"""
Test logging output proper when debug is off.
"""
self.cleanup()
self.start_servers(debug=False)
self.assertTrue(os.path.exists(self.api_server.log_file))
with open(self.api_server.log_file, 'r') as f:
api_log_out = f.read()
self.assertNotIn('DEBUG glance', api_log_out)
self.assertTrue(os.path.exists(self.registry_server.log_file))
with open(self.registry_server.log_file, 'r') as freg:
registry_log_out = freg.read()
self.assertNotIn('DEBUG glance', registry_log_out)
self.stop_servers()
def assertNotEmptyFile(self, path):
self.assertTrue(os.path.exists(path))
self.assertNotEqual(os.stat(path)[stat.ST_SIZE], 0)
def test_logrotate(self):
"""
Test that we notice when our log file has been rotated
"""
self.cleanup()
self.start_servers()
self.assertNotEmptyFile(self.api_server.log_file)
os.rename(self.api_server.log_file, self.api_server.log_file + ".1")
path = "http://%s:%d/" % ("127.0.0.1", self.api_port)
response, content = httplib2.Http().request(path, 'GET')
self.assertEqual(http.MULTIPLE_CHOICES, response.status)
self.assertNotEmptyFile(self.api_server.log_file)
self.stop_servers()
glance-16.0.1/glance/tests/integration/ 0000775 0001750 0001750 00000000000 13267672475 020013 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/integration/v2/ 0000775 0001750 0001750 00000000000 13267672475 020342 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/integration/v2/__init__.py 0000666 0001750 0001750 00000000000 13267672245 022436 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/integration/v2/base.py 0000666 0001750 0001750 00000015736 13267672245 021637 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Rackspace Hosting
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import atexit
import os.path
import tempfile
import fixtures
import glance_store
from oslo_config import cfg
from oslo_db import options
import glance.common.client
from glance.common import config
import glance.db.sqlalchemy.api
import glance.registry.client.v1.client
from glance import tests as glance_tests
from glance.tests import utils as test_utils
TESTING_API_PASTE_CONF = """
[pipeline:glance-api]
pipeline = versionnegotiation gzip unauthenticated-context rootapp
[pipeline:glance-api-caching]
pipeline = versionnegotiation gzip unauthenticated-context cache rootapp
[pipeline:glance-api-cachemanagement]
pipeline =
versionnegotiation
gzip
unauthenticated-context
cache
cache_manage
rootapp
[pipeline:glance-api-fakeauth]
pipeline = versionnegotiation gzip fakeauth context rootapp
[pipeline:glance-api-noauth]
pipeline = versionnegotiation gzip context rootapp
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[filter:versionnegotiation]
paste.filter_factory =
glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cache_manage]
paste.filter_factory =
glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory =
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:fakeauth]
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
"""
TESTING_REGISTRY_PASTE_CONF = """
[pipeline:glance-registry]
pipeline = unauthenticated-context registryapp
[pipeline:glance-registry-fakeauth]
pipeline = fakeauth context registryapp
[app:registryapp]
paste.app_factory = glance.registry.api.v1:API.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory =
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:fakeauth]
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
"""
CONF = cfg.CONF
class ApiTest(test_utils.BaseTestCase):
def setUp(self):
super(ApiTest, self).setUp()
self.test_dir = self.useFixture(fixtures.TempDir()).path
self._configure_logging()
self._setup_database()
self._setup_stores()
self._setup_property_protection()
self.glance_registry_app = self._load_paste_app(
'glance-registry',
flavor=getattr(self, 'registry_flavor', ''),
conf=getattr(self, 'registry_paste_conf',
TESTING_REGISTRY_PASTE_CONF),
)
self._connect_registry_client()
self.glance_api_app = self._load_paste_app(
'glance-api',
flavor=getattr(self, 'api_flavor', ''),
conf=getattr(self, 'api_paste_conf', TESTING_API_PASTE_CONF),
)
self.http = test_utils.Httplib2WsgiAdapter(self.glance_api_app)
def _setup_property_protection(self):
self._copy_data_file('property-protections.conf', self.test_dir)
self.property_file = os.path.join(self.test_dir,
'property-protections.conf')
def _configure_logging(self):
self.config(default_log_levels=[
'amqplib=WARN',
'sqlalchemy=WARN',
'boto=WARN',
'suds=INFO',
'keystone=INFO',
'eventlet.wsgi.server=DEBUG'
])
def _setup_database(self):
sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
options.set_defaults(CONF, connection=sql_connection)
glance.db.sqlalchemy.api.clear_db_env()
glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
if glance_db_env in os.environ:
# use the empty db created and cached as a tempfile
# instead of spending the time creating a new one
db_location = os.environ[glance_db_env]
test_utils.execute('cp %s %s/tests.sqlite'
% (db_location, self.test_dir))
else:
test_utils.db_sync()
# copy the clean db to a temp location so that it
# can be reused for future tests
(osf, db_location) = tempfile.mkstemp()
os.close(osf)
test_utils.execute('cp %s/tests.sqlite %s'
% (self.test_dir, db_location))
os.environ[glance_db_env] = db_location
# cleanup the temp file when the test suite is
# complete
def _delete_cached_db():
try:
os.remove(os.environ[glance_db_env])
except Exception:
glance_tests.logger.exception(
"Error cleaning up the file %s" %
os.environ[glance_db_env])
atexit.register(_delete_cached_db)
def _setup_stores(self):
glance_store.register_opts(CONF)
image_dir = os.path.join(self.test_dir, "images")
self.config(group='glance_store',
filesystem_store_datadir=image_dir)
glance_store.create_stores()
def _load_paste_app(self, name, flavor, conf):
conf_file_path = os.path.join(self.test_dir, '%s-paste.ini' % name)
with open(conf_file_path, 'w') as conf_file:
conf_file.write(conf)
conf_file.flush()
return config.load_paste_app(name, flavor=flavor,
conf_file=conf_file_path)
def _connect_registry_client(self):
def get_connection_type(self2):
def wrapped(*args, **kwargs):
return test_utils.HttplibWsgiAdapter(self.glance_registry_app)
return wrapped
self.stubs.Set(glance.common.client.BaseClient,
'get_connection_type', get_connection_type)
def tearDown(self):
glance.db.sqlalchemy.api.clear_db_env()
super(ApiTest, self).tearDown()
glance-16.0.1/glance/tests/integration/v2/test_tasks_api.py 0000666 0001750 0001750 00000050570 13267672245 023735 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Rackspace Hosting
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import eventlet
from oslo_serialization import jsonutils as json
from six.moves import http_client
from glance.api.v2 import tasks
from glance.common import timeutils
from glance.tests.integration.v2 import base
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
def minimal_task_headers(owner='tenant1'):
headers = {
'X-Auth-Token': 'user1:%s:admin' % owner,
'Content-Type': 'application/json',
}
return headers
def _new_task_fixture(**kwargs):
task_data = {
"type": "import",
"input": {
"import_from": "http://example.com",
"import_from_format": "qcow2",
"image_properties": {
'disk_format': 'vhd',
'container_format': 'ovf'
}
}
}
task_data.update(kwargs)
return task_data
class TestTasksApi(base.ApiTest):
def __init__(self, *args, **kwargs):
super(TestTasksApi, self).__init__(*args, **kwargs)
self.api_flavor = 'fakeauth'
self.registry_flavor = 'fakeauth'
def _wait_on_task_execution(self, max_wait=5):
"""Wait until all the tasks have finished execution and are in
state of success or failure.
"""
start = timeutils.utcnow()
# wait for maximum of seconds defined by max_wait
while timeutils.delta_seconds(start, timeutils.utcnow()) < max_wait:
wait = False
# Verify that no task is in status of pending or processing
path = "/v2/tasks"
res, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
content_dict = json.loads(content)
self.assertEqual(http_client.OK, res.status)
res_tasks = content_dict['tasks']
if len(res_tasks) != 0:
for task in res_tasks:
if task['status'] in ('pending', 'processing'):
wait = True
break
if wait:
# Bug #1541487: we must give time to the server to execute the
# task, but the server is run in the same process than the
# test. Use eventlet to give the control to the pending server
# task.
eventlet.sleep(0.05)
continue
else:
break
def _post_new_task(self, **kwargs):
task_owner = kwargs.get('owner')
headers = minimal_task_headers(task_owner)
task_data = _new_task_fixture()
task_data['input']['import_from'] = "http://example.com"
body_content = json.dumps(task_data)
path = "/v2/tasks"
response, content = self.http.request(path, 'POST',
headers=headers,
body=body_content)
self.assertEqual(http_client.CREATED, response.status)
task = json.loads(content)
task_id = task['id']
self.assertIsNotNone(task_id)
self.assertEqual(task_owner, task['owner'])
self.assertEqual(task_data['type'], task['type'])
self.assertEqual(task_data['input'], task['input'])
self.assertEqual("http://localhost" + path + "/" + task_id,
response.webob_resp.headers['Location'])
return task, task_data
def test_all_task_api(self):
# 0. GET /tasks
# Verify no tasks
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
content_dict = json.loads(content)
self.assertEqual(http_client.OK, response.status)
self.assertFalse(content_dict['tasks'])
# 1. GET /tasks/{task_id}
# Verify non-existent task
task_id = 'NON_EXISTENT_TASK'
path = "/v2/tasks/%s" % task_id
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.NOT_FOUND, response.status)
# 2. POST /tasks
# Create a new task
task_owner = 'tenant1'
data, req_input = self._post_new_task(owner=task_owner)
# 3. GET /tasks/{task_id}
# Get an existing task
task_id = data['id']
path = "/v2/tasks/%s" % task_id
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
# NOTE(sabari): wait for all task executions to finish before checking
# task status.
self._wait_on_task_execution(max_wait=10)
# 4. GET /tasks
# Get all tasks (not deleted)
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
self.assertIsNotNone(content)
data = json.loads(content)
self.assertIsNotNone(data)
self.assertEqual(1, len(data['tasks']))
# NOTE(venkatesh) find a way to get expected_keys from tasks controller
expected_keys = set(['id', 'expires_at', 'type', 'owner', 'status',
'created_at', 'updated_at', 'self', 'schema'])
task = data['tasks'][0]
self.assertEqual(expected_keys, set(task.keys()))
self.assertEqual(req_input['type'], task['type'])
self.assertEqual(task_owner, task['owner'])
self.assertEqual('success', task['status'])
self.assertIsNotNone(task['created_at'])
self.assertIsNotNone(task['updated_at'])
def test_task_schema_api(self):
# 0. GET /schemas/task
# Verify schema for task
path = "/v2/schemas/task"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
schema = tasks.get_task_schema()
expected_schema = schema.minimal()
data = json.loads(content)
self.assertIsNotNone(data)
self.assertEqual(expected_schema, data)
# 1. GET /schemas/tasks
# Verify schema for tasks
path = "/v2/schemas/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
schema = tasks.get_collection_schema()
expected_schema = schema.minimal()
data = json.loads(content)
self.assertIsNotNone(data)
self.assertEqual(expected_schema, data)
# NOTE(nikhil): wait for all task executions to finish before exiting
# else there is a risk of running into deadlock
self._wait_on_task_execution()
def test_create_new_task(self):
# 0. POST /tasks
# Create a new task with valid input and type
task_data = _new_task_fixture()
task_owner = 'tenant1'
body_content = json.dumps(task_data)
path = "/v2/tasks"
response, content = self.http.request(
path, 'POST', headers=minimal_task_headers(task_owner),
body=body_content)
self.assertEqual(http_client.CREATED, response.status)
data = json.loads(content)
task_id = data['id']
self.assertIsNotNone(task_id)
self.assertEqual(task_owner, data['owner'])
self.assertEqual(task_data['type'], data['type'])
self.assertEqual(task_data['input'], data['input'])
# 1. POST /tasks
# Create a new task with invalid type
# Expect BadRequest(400) Error as response
task_data = _new_task_fixture(type='invalid')
task_owner = 'tenant1'
body_content = json.dumps(task_data)
path = "/v2/tasks"
response, content = self.http.request(
path, 'POST', headers=minimal_task_headers(task_owner),
body=body_content)
self.assertEqual(http_client.BAD_REQUEST, response.status)
# 1. POST /tasks
# Create a new task with invalid input for type 'import'
# Expect BadRequest(400) Error as response
task_data = _new_task_fixture(task_input='{something: invalid}')
task_owner = 'tenant1'
body_content = json.dumps(task_data)
path = "/v2/tasks"
response, content = self.http.request(
path, 'POST', headers=minimal_task_headers(task_owner),
body=body_content)
self.assertEqual(http_client.BAD_REQUEST, response.status)
# NOTE(nikhil): wait for all task executions to finish before exiting
# else there is a risk of running into deadlock
self._wait_on_task_execution()
def test_tasks_with_filter(self):
# 0. GET /v2/tasks
# Verify no tasks
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
content_dict = json.loads(content)
self.assertFalse(content_dict['tasks'])
task_ids = []
# 1. Make 2 POST requests on /tasks with various attributes
task_owner = TENANT1
data, req_input1 = self._post_new_task(owner=task_owner)
task_ids.append(data['id'])
task_owner = TENANT2
data, req_input2 = self._post_new_task(owner=task_owner)
task_ids.append(data['id'])
# 2. GET /tasks
# Verify two import tasks
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
content_dict = json.loads(content)
self.assertEqual(2, len(content_dict['tasks']))
# 3. GET /tasks with owner filter
# Verify correct task returned with owner
params = "owner=%s" % TENANT1
path = "/v2/tasks?%s" % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
content_dict = json.loads(content)
self.assertEqual(1, len(content_dict['tasks']))
self.assertEqual(TENANT1, content_dict['tasks'][0]['owner'])
# Check the same for different owner.
params = "owner=%s" % TENANT2
path = "/v2/tasks?%s" % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
content_dict = json.loads(content)
self.assertEqual(1, len(content_dict['tasks']))
self.assertEqual(TENANT2, content_dict['tasks'][0]['owner'])
# 4. GET /tasks with type filter
# Verify correct task returned with type
params = "type=import"
path = "/v2/tasks?%s" % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
content_dict = json.loads(content)
self.assertEqual(2, len(content_dict['tasks']))
actual_task_ids = [task['id'] for task in content_dict['tasks']]
self.assertEqual(set(task_ids), set(actual_task_ids))
# NOTE(nikhil): wait for all task executions to finish before exiting
# else there is a risk of running into deadlock
self._wait_on_task_execution()
def test_limited_tasks(self):
"""
Ensure marker and limit query params work
"""
# 0. GET /tasks
# Verify no tasks
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
tasks = json.loads(content)
self.assertFalse(tasks['tasks'])
task_ids = []
# 1. POST /tasks with three tasks with various attributes
task, _ = self._post_new_task(owner=TENANT1)
task_ids.append(task['id'])
task, _ = self._post_new_task(owner=TENANT2)
task_ids.append(task['id'])
task, _ = self._post_new_task(owner=TENANT3)
task_ids.append(task['id'])
# 2. GET /tasks
# Verify 3 tasks are returned
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
tasks = json.loads(content)['tasks']
self.assertEqual(3, len(tasks))
# 3. GET /tasks with limit of 2
# Verify only two tasks were returned
params = "limit=2"
path = "/v2/tasks?%s" % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
actual_tasks = json.loads(content)['tasks']
self.assertEqual(2, len(actual_tasks))
self.assertEqual(tasks[0]['id'], actual_tasks[0]['id'])
self.assertEqual(tasks[1]['id'], actual_tasks[1]['id'])
# 4. GET /tasks with marker
# Verify only two tasks were returned
params = "marker=%s" % tasks[0]['id']
path = "/v2/tasks?%s" % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
actual_tasks = json.loads(content)['tasks']
self.assertEqual(2, len(actual_tasks))
self.assertEqual(tasks[1]['id'], actual_tasks[0]['id'])
self.assertEqual(tasks[2]['id'], actual_tasks[1]['id'])
# 5. GET /tasks with marker and limit
# Verify only one task was returned with the correct id
params = "limit=1&marker=%s" % tasks[1]['id']
path = "/v2/tasks?%s" % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
actual_tasks = json.loads(content)['tasks']
self.assertEqual(1, len(actual_tasks))
self.assertEqual(tasks[2]['id'], actual_tasks[0]['id'])
# NOTE(nikhil): wait for all task executions to finish before exiting
# else there is a risk of running into deadlock
self._wait_on_task_execution()
def test_ordered_tasks(self):
# 0. GET /tasks
# Verify no tasks
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
tasks = json.loads(content)
self.assertFalse(tasks['tasks'])
task_ids = []
# 1. POST /tasks with three tasks with various attributes
task, _ = self._post_new_task(owner=TENANT1)
task_ids.append(task['id'])
task, _ = self._post_new_task(owner=TENANT2)
task_ids.append(task['id'])
task, _ = self._post_new_task(owner=TENANT3)
task_ids.append(task['id'])
# 2. GET /tasks with no query params
# Verify three tasks sorted by created_at desc
# 2. GET /tasks
# Verify 3 tasks are returned
path = "/v2/tasks"
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
actual_tasks = json.loads(content)['tasks']
self.assertEqual(3, len(actual_tasks))
self.assertEqual(task_ids[2], actual_tasks[0]['id'])
self.assertEqual(task_ids[1], actual_tasks[1]['id'])
self.assertEqual(task_ids[0], actual_tasks[2]['id'])
# 3. GET /tasks sorted by owner asc
params = 'sort_key=owner&sort_dir=asc'
path = '/v2/tasks?%s' % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
expected_task_owners = [TENANT1, TENANT2, TENANT3]
expected_task_owners.sort()
actual_tasks = json.loads(content)['tasks']
self.assertEqual(3, len(actual_tasks))
self.assertEqual(expected_task_owners,
[t['owner'] for t in actual_tasks])
# 4. GET /tasks sorted by owner desc with a marker
params = 'sort_key=owner&sort_dir=desc&marker=%s' % task_ids[0]
path = '/v2/tasks?%s' % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
actual_tasks = json.loads(content)['tasks']
self.assertEqual(2, len(actual_tasks))
self.assertEqual(task_ids[2], actual_tasks[0]['id'])
self.assertEqual(task_ids[1], actual_tasks[1]['id'])
self.assertEqual(TENANT3, actual_tasks[0]['owner'])
self.assertEqual(TENANT2, actual_tasks[1]['owner'])
# 5. GET /tasks sorted by owner asc with a marker
params = 'sort_key=owner&sort_dir=asc&marker=%s' % task_ids[0]
path = '/v2/tasks?%s' % params
response, content = self.http.request(path, 'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
actual_tasks = json.loads(content)['tasks']
self.assertEqual(0, len(actual_tasks))
# NOTE(nikhil): wait for all task executions to finish before exiting
# else there is a risk of running into deadlock
self._wait_on_task_execution()
def test_delete_task(self):
# 0. POST /tasks
# Create a new task with valid input and type
task_data = _new_task_fixture()
task_owner = 'tenant1'
body_content = json.dumps(task_data)
path = "/v2/tasks"
response, content = self.http.request(
path, 'POST', headers=minimal_task_headers(task_owner),
body=body_content)
self.assertEqual(http_client.CREATED, response.status)
data = json.loads(content)
task_id = data['id']
# 1. DELETE on /tasks/{task_id}
# Attempt to delete a task
path = "/v2/tasks/%s" % task_id
response, content = self.http.request(path,
'DELETE',
headers=minimal_task_headers())
self.assertEqual(http_client.METHOD_NOT_ALLOWED, response.status)
self.assertEqual('GET', response.webob_resp.headers.get('Allow'))
self.assertEqual(('GET',), response.webob_resp.allow)
self.assertEqual(('GET',), response.allow)
# 2. GET /tasks/{task_id}
# Ensure that methods mentioned in the Allow header work
path = "/v2/tasks/%s" % task_id
response, content = self.http.request(path,
'GET',
headers=minimal_task_headers())
self.assertEqual(http_client.OK, response.status)
self.assertIsNotNone(content)
# NOTE(nikhil): wait for all task executions to finish before exiting
# else there is a risk of running into deadlock
self._wait_on_task_execution()
glance-16.0.1/glance/tests/integration/v2/test_property_quota_violations.py 0000666 0001750 0001750 00000012671 13267672245 027323 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_serialization import jsonutils
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.tests.integration.v2 import base
CONF = cfg.CONF
class TestPropertyQuotaViolations(base.ApiTest):
def __init__(self, *args, **kwargs):
super(TestPropertyQuotaViolations, self).__init__(*args, **kwargs)
self.api_flavor = 'noauth'
self.registry_flavor = 'fakeauth'
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': "foo",
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def _get(self, image_id=""):
path = ('/v2/images/%s' % image_id).rstrip('/')
rsp, content = self.http.request(path, 'GET', headers=self._headers())
self.assertEqual(http_client.OK, rsp.status)
content = jsonutils.loads(content)
return content
def _create_image(self, body):
path = '/v2/images'
headers = self._headers({'content-type': 'application/json'})
rsp, content = self.http.request(path, 'POST', headers=headers,
body=jsonutils.dumps(body))
self.assertEqual(http_client.CREATED, rsp.status)
return jsonutils.loads(content)
def _patch(self, image_id, body, expected_status):
path = '/v2/images/%s' % image_id
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
rsp, content = self.http.request(path, 'PATCH', headers=headers,
body=jsonutils.dumps(body))
self.assertEqual(expected_status, rsp.status, content)
return content
def test_property_ops_when_quota_violated(self):
# Image list must be empty to begin with
image_list = self._get()['images']
self.assertEqual(0, len(image_list))
orig_property_quota = 10
CONF.set_override('image_property_quota', orig_property_quota)
# Create an image (with deployer-defined properties)
req_body = {'name': 'testimg',
'disk_format': 'aki',
'container_format': 'aki'}
for i in range(orig_property_quota):
req_body['k_%d' % i] = 'v_%d' % i
image = self._create_image(req_body)
image_id = image['id']
for i in range(orig_property_quota):
self.assertEqual('v_%d' % i, image['k_%d' % i])
# Now reduce property quota. We should be allowed to modify/delete
# existing properties (even if the result still exceeds property quota)
# but not add new properties nor replace existing properties with new
# properties (as long as we're over the quota)
self.config(image_property_quota=2)
patch_body = [{'op': 'replace', 'path': '/k_4', 'value': 'v_4.new'}]
image = jsonutils.loads(self._patch(image_id, patch_body,
http_client.OK))
self.assertEqual('v_4.new', image['k_4'])
patch_body = [{'op': 'remove', 'path': '/k_7'}]
image = jsonutils.loads(self._patch(image_id, patch_body,
http_client.OK))
self.assertNotIn('k_7', image)
patch_body = [{'op': 'add', 'path': '/k_100', 'value': 'v_100'}]
self._patch(image_id, patch_body, http_client.REQUEST_ENTITY_TOO_LARGE)
image = self._get(image_id)
self.assertNotIn('k_100', image)
patch_body = [
{'op': 'remove', 'path': '/k_5'},
{'op': 'add', 'path': '/k_100', 'value': 'v_100'},
]
self._patch(image_id, patch_body, http_client.REQUEST_ENTITY_TOO_LARGE)
image = self._get(image_id)
self.assertNotIn('k_100', image)
self.assertIn('k_5', image)
# temporary violations to property quota should be allowed as long as
# it's within one PATCH request and the end result does not violate
# quotas.
patch_body = [{'op': 'add', 'path': '/k_100', 'value': 'v_100'},
{'op': 'add', 'path': '/k_99', 'value': 'v_99'}]
to_rm = ['k_%d' % i for i in range(orig_property_quota) if i != 7]
patch_body.extend([{'op': 'remove', 'path': '/%s' % k} for k in to_rm])
image = jsonutils.loads(self._patch(image_id, patch_body,
http_client.OK))
self.assertEqual('v_99', image['k_99'])
self.assertEqual('v_100', image['k_100'])
for k in to_rm:
self.assertNotIn(k, image)
glance-16.0.1/glance/tests/integration/__init__.py 0000666 0001750 0001750 00000000000 13267672245 022107 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/integration/legacy_functional/ 0000775 0001750 0001750 00000000000 13267672475 023501 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/integration/legacy_functional/__init__.py 0000666 0001750 0001750 00000000000 13267672245 025575 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/integration/legacy_functional/base.py 0000666 0001750 0001750 00000016231 13267672245 024765 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import atexit
import os.path
import tempfile
import fixtures
import glance_store
from oslo_config import cfg
from oslo_db import options
import glance.common.client
from glance.common import config
import glance.db.sqlalchemy.api
import glance.registry.client.v1.client
from glance import tests as glance_tests
from glance.tests import utils as test_utils
TESTING_API_PASTE_CONF = """
[pipeline:glance-api]
pipeline = versionnegotiation gzip unauthenticated-context rootapp
[pipeline:glance-api-caching]
pipeline = versionnegotiation gzip unauthenticated-context cache rootapp
[pipeline:glance-api-cachemanagement]
pipeline =
versionnegotiation
gzip
unauthenticated-context
cache
cache_manage
rootapp
[pipeline:glance-api-fakeauth]
pipeline = versionnegotiation gzip fakeauth context rootapp
[pipeline:glance-api-noauth]
pipeline = versionnegotiation gzip context rootapp
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
[app:apiversions]
paste.app_factory = glance.api.versions:create_resource
[app:apiv1app]
paste.app_factory = glance.api.v1.router:API.factory
[app:apiv2app]
paste.app_factory = glance.api.v2.router:API.factory
[filter:versionnegotiation]
paste.filter_factory =
glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:gzip]
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
[filter:cache]
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
[filter:cache_manage]
paste.filter_factory =
glance.api.middleware.cache_manage:CacheManageFilter.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory =
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:fakeauth]
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
"""
TESTING_REGISTRY_PASTE_CONF = """
[pipeline:glance-registry]
pipeline = unauthenticated-context registryapp
[pipeline:glance-registry-fakeauth]
pipeline = fakeauth context registryapp
[app:registryapp]
paste.app_factory = glance.registry.api.v1:API.factory
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[filter:unauthenticated-context]
paste.filter_factory =
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
[filter:fakeauth]
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
"""
CONF = cfg.CONF
class ApiTest(test_utils.BaseTestCase):
def setUp(self):
super(ApiTest, self).setUp()
self.init()
def init(self):
self.test_dir = self.useFixture(fixtures.TempDir()).path
self._configure_logging()
self._configure_policy()
self._setup_database()
self._setup_stores()
self._setup_property_protection()
self.glance_registry_app = self._load_paste_app(
'glance-registry',
flavor=getattr(self, 'registry_flavor', ''),
conf=getattr(self, 'registry_paste_conf',
TESTING_REGISTRY_PASTE_CONF),
)
self._connect_registry_client()
self.glance_api_app = self._load_paste_app(
'glance-api',
flavor=getattr(self, 'api_flavor', ''),
conf=getattr(self, 'api_paste_conf', TESTING_API_PASTE_CONF),
)
self.http = test_utils.Httplib2WsgiAdapter(self.glance_api_app)
def _setup_property_protection(self):
self._copy_data_file('property-protections.conf', self.test_dir)
self.property_file = os.path.join(self.test_dir,
'property-protections.conf')
def _configure_policy(self):
policy_file = self._copy_data_file('policy.json', self.test_dir)
self.config(policy_file=policy_file, group='oslo_policy')
def _configure_logging(self):
self.config(default_log_levels=[
'amqplib=WARN',
'sqlalchemy=WARN',
'boto=WARN',
'suds=INFO',
'keystone=INFO',
'eventlet.wsgi.server=DEBUG'
])
def _setup_database(self):
sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
options.set_defaults(CONF, connection=sql_connection)
glance.db.sqlalchemy.api.clear_db_env()
glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
if glance_db_env in os.environ:
# use the empty db created and cached as a tempfile
# instead of spending the time creating a new one
db_location = os.environ[glance_db_env]
test_utils.execute('cp %s %s/tests.sqlite'
% (db_location, self.test_dir))
else:
test_utils.db_sync()
# copy the clean db to a temp location so that it
# can be reused for future tests
(osf, db_location) = tempfile.mkstemp()
os.close(osf)
test_utils.execute('cp %s/tests.sqlite %s'
% (self.test_dir, db_location))
os.environ[glance_db_env] = db_location
# cleanup the temp file when the test suite is
# complete
def _delete_cached_db():
try:
os.remove(os.environ[glance_db_env])
except Exception:
glance_tests.logger.exception(
"Error cleaning up the file %s" %
os.environ[glance_db_env])
atexit.register(_delete_cached_db)
def _setup_stores(self):
glance_store.register_opts(CONF)
image_dir = os.path.join(self.test_dir, "images")
self.config(group='glance_store',
filesystem_store_datadir=image_dir)
glance_store.create_stores()
def _load_paste_app(self, name, flavor, conf):
conf_file_path = os.path.join(self.test_dir, '%s-paste.ini' % name)
with open(conf_file_path, 'w') as conf_file:
conf_file.write(conf)
conf_file.flush()
return config.load_paste_app(name, flavor=flavor,
conf_file=conf_file_path)
def _connect_registry_client(self):
def get_connection_type(self2):
def wrapped(*args, **kwargs):
return test_utils.HttplibWsgiAdapter(self.glance_registry_app)
return wrapped
self.stubs.Set(glance.common.client.BaseClient,
'get_connection_type', get_connection_type)
def tearDown(self):
glance.db.sqlalchemy.api.clear_db_env()
super(ApiTest, self).tearDown()
glance-16.0.1/glance/tests/integration/legacy_functional/test_v1_api.py 0000666 0001750 0001750 00000221642 13267672245 026275 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import hashlib
import os
import tempfile
from oslo_serialization import jsonutils
from oslo_utils import units
from six.moves import http_client
import testtools
from glance.common import timeutils
from glance.tests.integration.legacy_functional import base
from glance.tests.utils import minimal_headers
FIVE_KB = 5 * units.Ki
FIVE_GB = 5 * units.Gi
class TestApi(base.ApiTest):
def test_get_head_simple_post(self):
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
# 1. GET /images/detail
# Verify no public images
path = "/v1/images/detail"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
# 2. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned
image_data = b"*" * FIVE_KB
headers = minimal_headers('Image1')
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
# 3. HEAD image
# Verify image found now
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
# 4. GET image
# Verify all information on image we just added is correct
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image_headers = {
'x-image-meta-id': image_id,
'x-image-meta-name': 'Image1',
'x-image-meta-is_public': 'True',
'x-image-meta-status': 'active',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'x-image-meta-size': str(FIVE_KB)}
expected_std_headers = {
'content-length': str(FIVE_KB),
'content-type': 'application/octet-stream'}
for expected_key, expected_value in expected_image_headers.items():
self.assertEqual(expected_value, response[expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
response[expected_key]))
for expected_key, expected_value in expected_std_headers.items():
self.assertEqual(expected_value, response[expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
response[expected_key]))
content = content.encode('utf-8')
self.assertEqual(image_data, content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
hashlib.md5(content).hexdigest())
# 5. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_result = {"images": [
{"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"name": "Image1",
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
"size": 5120}]}
self.assertEqual(expected_result, jsonutils.loads(content))
# 6. GET /images/detail
# Verify image and all its metadata
path = "/v1/images/detail"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image = {
"status": "active",
"name": "Image1",
"deleted": False,
"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"is_public": True,
"deleted_at": None,
"properties": {},
"size": 5120}
image = jsonutils.loads(content)
for expected_key, expected_value in expected_image.items():
self.assertEqual(expected_value, image['images'][0][expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
image['images'][0][expected_key]))
# 7. PUT image with custom properties of "distro" and "arch"
# Verify 200 returned
headers = {'X-Image-Meta-Property-Distro': 'Ubuntu',
'X-Image-Meta-Property-Arch': 'x86_64'}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual("x86_64", data['image']['properties']['arch'])
self.assertEqual("Ubuntu", data['image']['properties']['distro'])
# 8. GET /images/detail
# Verify image and all its metadata
path = "/v1/images/detail"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
expected_image = {
"status": "active",
"name": "Image1",
"deleted": False,
"container_format": "ovf",
"disk_format": "raw",
"id": image_id,
"is_public": True,
"deleted_at": None,
"properties": {'distro': 'Ubuntu', 'arch': 'x86_64'},
"size": 5120}
image = jsonutils.loads(content)
for expected_key, expected_value in expected_image.items():
self.assertEqual(expected_value, image['images'][0][expected_key],
"For key '%s' expected header value '%s'. "
"Got '%s'" % (expected_key,
expected_value,
image['images'][0][expected_key]))
# 9. PUT image and remove a previously existing property.
headers = {'X-Image-Meta-Property-Arch': 'x86_64'}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
path = "/v1/images/detail"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images'][0]
self.assertEqual(1, len(data['properties']))
self.assertEqual("x86_64", data['properties']['arch'])
# 10. PUT image and add a previously deleted property.
headers = {'X-Image-Meta-Property-Distro': 'Ubuntu',
'X-Image-Meta-Property-Arch': 'x86_64'}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT', headers=headers)
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
path = "/v1/images/detail"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images'][0]
self.assertEqual(2, len(data['properties']))
self.assertEqual("x86_64", data['properties']['arch'])
self.assertEqual("Ubuntu", data['properties']['distro'])
self.assertNotEqual(data['created_at'], data['updated_at'])
# DELETE image
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
def test_queued_process_flow(self):
"""
We test the process flow where a user registers an image
with Glance but does not immediately upload an image file.
Later, the user uploads an image file using a PUT operation.
We track the changing of image status throughout this process.
0. GET /images
- Verify no public images
1. POST /images with public image named Image1 with no location
attribute and no image data.
- Verify 201 returned
2. GET /images
- Verify one public image
3. HEAD image
- Verify image now in queued status
4. PUT image with image data
- Verify 200 returned
5. HEAD images
- Verify image now in active status
6. GET /images
- Verify one public image
"""
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
# 1. POST /images with public image named Image1
# with no location or image data
headers = minimal_headers('Image1')
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertIsNone(data['image']['checksum'])
self.assertEqual(0, data['image']['size'])
self.assertEqual('ovf', data['image']['container_format'])
self.assertEqual('raw', data['image']['disk_format'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
image_id = data['image']['id']
# 2. GET /images
# Verify 1 public image
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(image_id, data['images'][0]['id'])
self.assertIsNone(data['images'][0]['checksum'])
self.assertEqual(0, data['images'][0]['size'])
self.assertEqual('ovf', data['images'][0]['container_format'])
self.assertEqual('raw', data['images'][0]['disk_format'])
self.assertEqual("Image1", data['images'][0]['name'])
# 3. HEAD /images
# Verify status is in queued
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
self.assertEqual("queued", response['x-image-meta-status'])
self.assertEqual('0', response['x-image-meta-size'])
self.assertEqual(image_id, response['x-image-meta-id'])
# 4. PUT image with image data, verify 200 returned
image_data = b"*" * FIVE_KB
headers = {'Content-Type': 'application/octet-stream'}
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'PUT', headers=headers,
body=image_data)
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['image']['checksum'])
self.assertEqual(FIVE_KB, data['image']['size'])
self.assertEqual("Image1", data['image']['name'])
self.assertTrue(data['image']['is_public'])
# 5. HEAD /images
# Verify status is in active
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual("Image1", response['x-image-meta-name'])
self.assertEqual("active", response['x-image-meta-status'])
# 6. GET /images
# Verify 1 public image still...
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(hashlib.md5(image_data).hexdigest(),
data['images'][0]['checksum'])
self.assertEqual(image_id, data['images'][0]['id'])
self.assertEqual(FIVE_KB, data['images'][0]['size'])
self.assertEqual('ovf', data['images'][0]['container_format'])
self.assertEqual('raw', data['images'][0]['disk_format'])
self.assertEqual("Image1", data['images'][0]['name'])
# DELETE image
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
def test_v1_not_enabled(self):
self.config(enable_v1_api=False)
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
def test_v1_enabled(self):
self.config(enable_v1_api=True)
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
def test_zero_initial_size(self):
"""
A test to ensure that an image with size explicitly set to zero
has status that immediately transitions to active.
"""
# 1. POST /images with public image named Image1
# attribute and a size of zero.
# Verify a 201 OK is returned
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Size': '0',
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-disk_format': 'raw',
'X-image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
self.assertEqual('active', image['status'])
# 2. HEAD image-location
# Verify image size is zero and the status is active
path = response.get('location')
response, content = self.http.request(path, 'HEAD')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('0', response['x-image-meta-size'])
self.assertEqual('active', response['x-image-meta-status'])
# 3. GET image-location
# Verify image content is empty
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual(0, len(content))
def test_traceback_not_consumed(self):
"""
A test that errors coming from the POST API do not
get consumed and print the actual error message, and
not something like <traceback object at 0x1918d40>
:see https://bugs.launchpad.net/glance/+bug/755912
"""
# POST /images with binary data, but not setting
# Content-Type to application/octet-stream, verify a
# 400 returned and that the error is readable.
with tempfile.NamedTemporaryFile() as test_data_file:
test_data_file.write(b"XXX")
test_data_file.flush()
path = "/v1/images"
headers = minimal_headers('Image1')
headers['Content-Type'] = 'not octet-stream'
response, content = self.http.request(path, 'POST',
body=test_data_file.name,
headers=headers)
self.assertEqual(http_client.BAD_REQUEST, response.status)
expected = "Content-Type must be application/octet-stream"
self.assertIn(expected, content,
"Could not find '%s' in '%s'" % (expected, content))
def test_filtered_images(self):
"""
Set up four test images and ensure each query param filter works
"""
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
image_ids = []
# 1. POST /images with three public images, and one private image
# with various attributes
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'ovf',
'X-Image-Meta-Disk-Format': 'vdi',
'X-Image-Meta-Size': '19',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Protected': 'True',
'X-Image-Meta-Property-pants': 'are on'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual("are on", data['image']['properties']['pants'])
self.assertTrue(data['image']['is_public'])
image_ids.append(data['image']['id'])
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'My Image!',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'ovf',
'X-Image-Meta-Disk-Format': 'vhd',
'X-Image-Meta-Size': '20',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Protected': 'False',
'X-Image-Meta-Property-pants': 'are on'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual("are on", data['image']['properties']['pants'])
self.assertTrue(data['image']['is_public'])
image_ids.append(data['image']['id'])
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'My Image!',
'X-Image-Meta-Status': 'saving',
'X-Image-Meta-Container-Format': 'ami',
'X-Image-Meta-Disk-Format': 'ami',
'X-Image-Meta-Size': '21',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Protected': 'False',
'X-Image-Meta-Property-pants': 'are off'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertEqual("are off", data['image']['properties']['pants'])
self.assertTrue(data['image']['is_public'])
image_ids.append(data['image']['id'])
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'My Private Image',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'ami',
'X-Image-Meta-Disk-Format': 'ami',
'X-Image-Meta-Size': '22',
'X-Image-Meta-Is-Public': 'False',
'X-Image-Meta-Protected': 'False'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
self.assertFalse(data['image']['is_public'])
image_ids.append(data['image']['id'])
# 2. GET /images
# Verify three public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
# 3. GET /images with name filter
# Verify correct images returned with name
params = "name=My%20Image!"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
for image in data['images']:
self.assertEqual("My Image!", image['name'])
# 4. GET /images with status filter
# Verify correct images returned with status
params = "status=queued"
path = "/v1/images/detail?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
for image in data['images']:
self.assertEqual("queued", image['status'])
params = "status=active"
path = "/v1/images/detail?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(0, len(data['images']))
# 5. GET /images with container_format filter
# Verify correct images returned with container_format
params = "container_format=ovf"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
for image in data['images']:
self.assertEqual("ovf", image['container_format'])
# 6. GET /images with disk_format filter
# Verify correct images returned with disk_format
params = "disk_format=vdi"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(1, len(data['images']))
for image in data['images']:
self.assertEqual("vdi", image['disk_format'])
# 7. GET /images with size_max filter
# Verify correct images returned with size <= expected
params = "size_max=20"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
for image in data['images']:
self.assertLessEqual(image['size'], 20)
# 8. GET /images with size_min filter
# Verify correct images returned with size >= expected
params = "size_min=20"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
for image in data['images']:
self.assertGreaterEqual(image['size'], 20)
# 9. Get /images with is_public=None filter
# Verify correct images returned with property
# Bug lp:803656 Support is_public in filtering
params = "is_public=None"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(4, len(data['images']))
# 10. Get /images with is_public=False filter
# Verify correct images returned with property
# Bug lp:803656 Support is_public in filtering
params = "is_public=False"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(1, len(data['images']))
for image in data['images']:
self.assertEqual("My Private Image", image['name'])
# 11. Get /images with is_public=True filter
# Verify correct images returned with property
# Bug lp:803656 Support is_public in filtering
params = "is_public=True"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
for image in data['images']:
self.assertNotEqual(image['name'], "My Private Image")
# 12. Get /images with protected=False filter
# Verify correct images returned with property
params = "protected=False"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
for image in data['images']:
self.assertNotEqual(image['name'], "Image1")
# 13. Get /images with protected=True filter
# Verify correct images returned with property
params = "protected=True"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(1, len(data['images']))
for image in data['images']:
self.assertEqual("Image1", image['name'])
# 14. GET /images with property filter
# Verify correct images returned with property
params = "property-pants=are%20on"
path = "/v1/images/detail?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
for image in data['images']:
self.assertEqual("are on", image['properties']['pants'])
# 15. GET /images with property filter and name filter
# Verify correct images returned with property and name
# Make sure you quote the url when using more than one param!
params = "name=My%20Image!&property-pants=are%20on"
path = "/v1/images/detail?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(1, len(data['images']))
for image in data['images']:
self.assertEqual("are on", image['properties']['pants'])
self.assertEqual("My Image!", image['name'])
# 16. GET /images with past changes-since filter
yesterday = timeutils.isotime(timeutils.utcnow() -
datetime.timedelta(1))
params = "changes-since=%s" % yesterday
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
# one timezone west of Greenwich equates to an hour ago
# taking care to pre-urlencode '+' as '%2B', otherwise the timezone
# '+' is wrongly decoded as a space
# TODO(eglynn): investigate '+' --> decoding, an artifact
# of WSGI/webob dispatch?
now = timeutils.utcnow()
hour_ago = now.strftime('%Y-%m-%dT%H:%M:%S%%2B01:00')
params = "changes-since=%s" % hour_ago
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
# 17. GET /images with future changes-since filter
tomorrow = timeutils.isotime(timeutils.utcnow() +
datetime.timedelta(1))
params = "changes-since=%s" % tomorrow
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(0, len(data['images']))
# one timezone east of Greenwich equates to an hour from now
now = timeutils.utcnow()
hour_hence = now.strftime('%Y-%m-%dT%H:%M:%S-01:00')
params = "changes-since=%s" % hour_hence
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(0, len(data['images']))
# 18. GET /images with size_min filter
# Verify correct images returned with size >= expected
params = "size_min=-1"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.BAD_REQUEST, response.status)
self.assertIn("filter size_min got -1", content)
# 19. GET /images with size_min filter
# Verify correct images returned with size >= expected
params = "size_max=-1"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.BAD_REQUEST, response.status)
self.assertIn("filter size_max got -1", content)
# 20. GET /images with size_min filter
# Verify correct images returned with size >= expected
params = "min_ram=-1"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.BAD_REQUEST, response.status)
self.assertIn("Bad value passed to filter min_ram got -1", content)
# 21. GET /images with size_min filter
# Verify correct images returned with size >= expected
params = "protected=imalittleteapot"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.BAD_REQUEST, response.status)
self.assertIn("protected got imalittleteapot", content)
# 22. GET /images with size_min filter
# Verify correct images returned with size >= expected
params = "is_public=imalittleteapot"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.BAD_REQUEST, response.status)
self.assertIn("is_public got imalittleteapot", content)
def test_limited_images(self):
"""
Ensure marker and limit query params work
"""
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
image_ids = []
# 1. POST /images with three public images with various attributes
headers = minimal_headers('Image1')
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image_ids.append(jsonutils.loads(content)['image']['id'])
headers = minimal_headers('Image2')
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image_ids.append(jsonutils.loads(content)['image']['id'])
headers = minimal_headers('Image3')
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image_ids.append(jsonutils.loads(content)['image']['id'])
# 2. GET /images with all images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(3, len(images))
# 3. GET /images with limit of 2
# Verify only two images were returned
params = "limit=2"
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images']
self.assertEqual(2, len(data))
self.assertEqual(images[0]['id'], data[0]['id'])
self.assertEqual(images[1]['id'], data[1]['id'])
# 4. GET /images with marker
# Verify only two images were returned
params = "marker=%s" % images[0]['id']
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images']
self.assertEqual(2, len(data))
self.assertEqual(images[1]['id'], data[0]['id'])
self.assertEqual(images[2]['id'], data[1]['id'])
# 5. GET /images with marker and limit
# Verify only one image was returned with the correct id
params = "limit=1&marker=%s" % images[1]['id']
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images']
self.assertEqual(1, len(data))
self.assertEqual(images[2]['id'], data[0]['id'])
# 6. GET /images/detail with marker and limit
# Verify only one image was returned with the correct id
params = "limit=1&marker=%s" % images[1]['id']
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)['images']
self.assertEqual(1, len(data))
self.assertEqual(images[2]['id'], data[0]['id'])
# DELETE images
for image_id in image_ids:
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
def test_ordered_images(self):
"""
Set up three test images and ensure each query param filter works
"""
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
# 1. POST /images with three public images with various attributes
image_ids = []
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'ovf',
'X-Image-Meta-Disk-Format': 'vdi',
'X-Image-Meta-Size': '19',
'X-Image-Meta-Is-Public': 'True'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image_ids.append(jsonutils.loads(content)['image']['id'])
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'ASDF',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'bare',
'X-Image-Meta-Disk-Format': 'iso',
'X-Image-Meta-Size': '2',
'X-Image-Meta-Is-Public': 'True'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image_ids.append(jsonutils.loads(content)['image']['id'])
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'XYZ',
'X-Image-Meta-Status': 'saving',
'X-Image-Meta-Container-Format': 'ami',
'X-Image-Meta-Disk-Format': 'ami',
'X-Image-Meta-Size': '5',
'X-Image-Meta-Is-Public': 'True'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image_ids.append(jsonutils.loads(content)['image']['id'])
# 2. GET /images with no query params
# Verify three public images sorted by created_at desc
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
self.assertEqual(image_ids[2], data['images'][0]['id'])
self.assertEqual(image_ids[1], data['images'][1]['id'])
self.assertEqual(image_ids[0], data['images'][2]['id'])
# 3. GET /images sorted by name asc
params = 'sort_key=name&sort_dir=asc'
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
self.assertEqual(image_ids[1], data['images'][0]['id'])
self.assertEqual(image_ids[0], data['images'][1]['id'])
self.assertEqual(image_ids[2], data['images'][2]['id'])
# 4. GET /images sorted by size desc
params = 'sort_key=size&sort_dir=desc'
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(3, len(data['images']))
self.assertEqual(image_ids[0], data['images'][0]['id'])
self.assertEqual(image_ids[2], data['images'][1]['id'])
self.assertEqual(image_ids[1], data['images'][2]['id'])
# 5. GET /images sorted by size desc with a marker
params = 'sort_key=size&sort_dir=desc&marker=%s' % image_ids[0]
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(2, len(data['images']))
self.assertEqual(image_ids[2], data['images'][0]['id'])
self.assertEqual(image_ids[1], data['images'][1]['id'])
# 6. GET /images sorted by name asc with a marker
params = 'sort_key=name&sort_dir=asc&marker=%s' % image_ids[2]
path = "/v1/images?%s" % (params)
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
data = jsonutils.loads(content)
self.assertEqual(0, len(data['images']))
# DELETE images
for image_id in image_ids:
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'DELETE')
self.assertEqual(http_client.OK, response.status)
def test_duplicate_image_upload(self):
"""
Upload initial image, then attempt to upload duplicate image
"""
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
# 1. POST /images with public image named Image1
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'ovf',
'X-Image-Meta-Disk-Format': 'vdi',
'X-Image-Meta-Size': '19',
'X-Image-Meta-Is-Public': 'True'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
image = jsonutils.loads(content)['image']
# 2. POST /images with public image named Image1, and ID: 1
headers = {'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': 'Image1 Update',
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Container-Format': 'ovf',
'X-Image-Meta-Disk-Format': 'vdi',
'X-Image-Meta-Size': '19',
'X-Image-Meta-Id': image['id'],
'X-Image-Meta-Is-Public': 'True'}
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CONFLICT, response.status)
def test_delete_not_existing(self):
"""
We test the following:
0. GET /images/1
- Verify 404
1. DELETE /images/1
- Verify 404
"""
# 0. GET /images
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
self.assertEqual('{"images": []}', content)
# 1. DELETE /images/1
# Verify 404 returned
path = "/v1/images/1"
response, content = self.http.request(path, 'DELETE')
self.assertEqual(http_client.NOT_FOUND, response.status)
def _do_test_post_image_content_bad_format(self, format):
"""
We test that missing container/disk format fails with 400 "Bad Request"
:see https://bugs.launchpad.net/glance/+bug/933702
"""
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(0, len(images))
path = "/v1/images"
# POST /images without given format being specified
headers = minimal_headers('Image1')
headers['X-Image-Meta-' + format] = 'bad_value'
with tempfile.NamedTemporaryFile() as test_data_file:
test_data_file.write(b"XXX")
test_data_file.flush()
response, content = self.http.request(path, 'POST',
headers=headers,
body=test_data_file.name)
self.assertEqual(http_client.BAD_REQUEST, response.status)
type = format.replace('_format', '')
expected = "Invalid %s format 'bad_value' for image" % type
self.assertIn(expected, content,
"Could not find '%s' in '%s'" % (expected, content))
# make sure the image was not created
# Verify no public images
path = "/v1/images"
response, content = self.http.request(path, 'GET')
self.assertEqual(http_client.OK, response.status)
images = jsonutils.loads(content)['images']
self.assertEqual(0, len(images))
def test_post_image_content_bad_container_format(self):
self._do_test_post_image_content_bad_format('container_format')
def test_post_image_content_bad_disk_format(self):
self._do_test_post_image_content_bad_format('disk_format')
def _do_test_put_image_content_missing_format(self, format):
"""
We test that missing container/disk format only fails with
400 "Bad Request" when the image content is PUT (i.e. not
on the original POST of a queued image).
:see https://bugs.launchpad.net/glance/+bug/937216
"""
# POST queued image
path = "/v1/images"
headers = {
'X-Image-Meta-Name': 'Image1',
'X-Image-Meta-Is-Public': 'True',
}
response, content = self.http.request(path, 'POST', headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
self.addDetail('image_data', testtools.content.json_content(data))
# PUT image content images without given format being specified
path = "/v1/images/%s" % (image_id)
headers = minimal_headers('Image1')
del headers['X-Image-Meta-' + format]
with tempfile.NamedTemporaryFile() as test_data_file:
test_data_file.write(b"XXX")
test_data_file.flush()
response, content = self.http.request(path, 'PUT',
headers=headers,
body=test_data_file.name)
self.assertEqual(http_client.BAD_REQUEST, response.status)
type = format.replace('_format', '').capitalize()
expected = "%s format is not specified" % type
self.assertIn(expected, content,
"Could not find '%s' in '%s'" % (expected, content))
def test_put_image_content_bad_container_format(self):
self._do_test_put_image_content_missing_format('container_format')
def test_put_image_content_bad_disk_format(self):
self._do_test_put_image_content_missing_format('disk_format')
def _do_test_mismatched_attribute(self, attribute, value):
"""
Test mismatched attribute.
"""
image_data = "*" * FIVE_KB
headers = minimal_headers('Image1')
headers[attribute] = value
path = "/v1/images"
response, content = self.http.request(path, 'POST', headers=headers,
body=image_data)
self.assertEqual(http_client.BAD_REQUEST, response.status)
images_dir = os.path.join(self.test_dir, 'images')
image_count = len([name for name in os.listdir(images_dir)
if os.path.isfile(os.path.join(images_dir, name))])
self.assertEqual(0, image_count)
def test_mismatched_size(self):
"""
Test mismatched size.
"""
self._do_test_mismatched_attribute('x-image-meta-size',
str(FIVE_KB + 1))
def test_mismatched_checksum(self):
"""
Test mismatched checksum.
"""
self._do_test_mismatched_attribute('x-image-meta-checksum',
'foobar')
class TestApiWithFakeAuth(base.ApiTest):
def __init__(self, *args, **kwargs):
super(TestApiWithFakeAuth, self).__init__(*args, **kwargs)
self.api_flavor = 'fakeauth'
self.registry_flavor = 'fakeauth'
def test_ownership(self):
# Add an image with admin privileges and ensure the owner
# can be set to something other than what was used to authenticate
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
create_headers = {
'X-Image-Meta-Name': 'MyImage',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Owner': 'tenant2',
}
create_headers.update(auth_headers)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=create_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('tenant2', response['x-image-meta-owner'])
# Now add an image without admin privileges and ensure the owner
# cannot be set to something other than what was used to authenticate
auth_headers = {
'X-Auth-Token': 'user1:tenant1:role1',
}
create_headers.update(auth_headers)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=create_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
# We have to be admin to see the owner
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
create_headers.update(auth_headers)
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('tenant1', response['x-image-meta-owner'])
# Make sure the non-privileged user can't update their owner either
update_headers = {
'X-Image-Meta-Name': 'MyImage2',
'X-Image-Meta-Owner': 'tenant2',
'X-Auth-Token': 'user1:tenant1:role1',
}
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'PUT',
headers=update_headers)
self.assertEqual(http_client.OK, response.status)
# We have to be admin to see the owner
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('tenant1', response['x-image-meta-owner'])
# An admin user should be able to update the owner
auth_headers = {
'X-Auth-Token': 'user1:tenant3:admin',
}
update_headers = {
'X-Image-Meta-Name': 'MyImage2',
'X-Image-Meta-Owner': 'tenant2',
}
update_headers.update(auth_headers)
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'PUT',
headers=update_headers)
self.assertEqual(http_client.OK, response.status)
path = "/v1/images/%s" % (image_id)
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('tenant2', response['x-image-meta-owner'])
def test_image_visibility_to_different_users(self):
owners = ['admin', 'tenant1', 'tenant2', 'none']
visibilities = {'public': 'True', 'private': 'False'}
image_ids = {}
for owner in owners:
for visibility, is_public in visibilities.items():
name = '%s-%s' % (owner, visibility)
headers = {
'Content-Type': 'application/octet-stream',
'X-Image-Meta-Name': name,
'X-Image-Meta-Status': 'active',
'X-Image-Meta-Is-Public': is_public,
'X-Image-Meta-Owner': owner,
'X-Auth-Token': 'createuser:createtenant:admin',
}
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_ids[name] = data['image']['id']
def list_images(tenant, role='', is_public=None):
auth_token = 'user:%s:%s' % (tenant, role)
headers = {'X-Auth-Token': auth_token}
path = "/v1/images/detail"
if is_public is not None:
path += '?is_public=%s' % is_public
response, content = self.http.request(path, 'GET', headers=headers)
self.assertEqual(http_client.OK, response.status)
return jsonutils.loads(content)['images']
# 1. Known user sees public and their own images
images = list_images('tenant1')
self.assertEqual(5, len(images))
for image in images:
self.assertTrue(image['is_public'] or image['owner'] == 'tenant1')
# 2. Unknown user sees only public images
images = list_images('none')
self.assertEqual(4, len(images))
for image in images:
self.assertTrue(image['is_public'])
# 3. Unknown admin sees only public images
images = list_images('none', role='admin')
self.assertEqual(4, len(images))
for image in images:
self.assertTrue(image['is_public'])
# 4. Unknown admin, is_public=none, shows all images
images = list_images('none', role='admin', is_public='none')
self.assertEqual(8, len(images))
# 5. Unknown admin, is_public=true, shows only public images
images = list_images('none', role='admin', is_public='true')
self.assertEqual(4, len(images))
for image in images:
self.assertTrue(image['is_public'])
# 6. Unknown admin, is_public=false, sees only private images
images = list_images('none', role='admin', is_public='false')
self.assertEqual(4, len(images))
for image in images:
self.assertFalse(image['is_public'])
# 7. Known admin sees public and their own images
images = list_images('admin', role='admin')
self.assertEqual(5, len(images))
for image in images:
self.assertTrue(image['is_public'] or image['owner'] == 'admin')
# 8. Known admin, is_public=none, shows all images
images = list_images('admin', role='admin', is_public='none')
self.assertEqual(8, len(images))
# 9. Known admin, is_public=true, sees all public and their images
images = list_images('admin', role='admin', is_public='true')
self.assertEqual(5, len(images))
for image in images:
self.assertTrue(image['is_public'] or image['owner'] == 'admin')
# 10. Known admin, is_public=false, sees all private images
images = list_images('admin', role='admin', is_public='false')
self.assertEqual(4, len(images))
for image in images:
self.assertFalse(image['is_public'])
def test_property_protections(self):
# Enable property protection
self.config(property_protection_file=self.property_file)
self.init()
CREATE_HEADERS = {
'X-Image-Meta-Name': 'MyImage',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Owner': 'tenant2',
}
# Create an image for role member with extra properties
# Raises 403 since user is not allowed to create 'foo'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:member',
}
custom_props = {
'x-image-meta-property-foo': 'bar'
}
auth_headers.update(custom_props)
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
# Create an image for role member without 'foo'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:member',
}
custom_props = {
'x-image-meta-property-x_owner_foo': 'o_s_bar',
}
auth_headers.update(custom_props)
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.CREATED, response.status)
# Returned image entity should have 'x_owner_foo'
data = jsonutils.loads(content)
self.assertEqual('o_s_bar',
data['image']['properties']['x_owner_foo'])
# Create an image for role spl_role with extra properties
auth_headers = {
'X-Auth-Token': 'user1:tenant1:spl_role',
}
custom_props = {
'X-Image-Meta-Property-spl_create_prop': 'create_bar',
'X-Image-Meta-Property-spl_read_prop': 'read_bar',
'X-Image-Meta-Property-spl_update_prop': 'update_bar',
'X-Image-Meta-Property-spl_delete_prop': 'delete_bar'
}
auth_headers.update(custom_props)
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
# Attempt to update two properties, one protected(spl_read_prop), the
# other not(spl_update_prop). Request should be forbidden.
auth_headers = {
'X-Auth-Token': 'user1:tenant1:spl_role',
}
custom_props = {
'X-Image-Meta-Property-spl_read_prop': 'r',
'X-Image-Meta-Property-spl_update_prop': 'u',
'X-Glance-Registry-Purge-Props': 'False'
}
auth_headers.update(auth_headers)
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
# Attempt to create properties which are forbidden
auth_headers = {
'X-Auth-Token': 'user1:tenant1:spl_role',
}
custom_props = {
'X-Image-Meta-Property-spl_new_prop': 'new',
'X-Glance-Registry-Purge-Props': 'True'
}
auth_headers.update(auth_headers)
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
# Attempt to update, create and delete properties
auth_headers = {
'X-Auth-Token': 'user1:tenant1:spl_role',
}
custom_props = {
'X-Image-Meta-Property-spl_create_prop': 'create_bar',
'X-Image-Meta-Property-spl_read_prop': 'read_bar',
'X-Image-Meta-Property-spl_update_prop': 'u',
'X-Glance-Registry-Purge-Props': 'True'
}
auth_headers.update(auth_headers)
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
# Returned image entity should reflect the changes
image = jsonutils.loads(content)
# 'spl_update_prop' has update permission for spl_role
# hence the value has changed
self.assertEqual('u', image['image']['properties']['spl_update_prop'])
# 'spl_delete_prop' has delete permission for spl_role
# hence the property has been deleted
self.assertNotIn('spl_delete_prop', image['image']['properties'])
# 'spl_create_prop' has create permission for spl_role
# hence the property has been created
self.assertEqual('create_bar',
image['image']['properties']['spl_create_prop'])
# Image Deletion should work
auth_headers = {
'X-Auth-Token': 'user1:tenant1:spl_role',
}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'DELETE',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
# This image should be no longer be directly accessible
auth_headers = {
'X-Auth-Token': 'user1:tenant1:spl_role',
}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.NOT_FOUND, response.status)
def test_property_protections_special_chars(self):
# Enable property protection
self.config(property_protection_file=self.property_file)
self.init()
CREATE_HEADERS = {
'X-Image-Meta-Name': 'MyImage',
'X-Image-Meta-disk_format': 'raw',
'X-Image-Meta-container_format': 'ovf',
'X-Image-Meta-Is-Public': 'True',
'X-Image-Meta-Owner': 'tenant2',
'X-Image-Meta-Size': '0',
}
# Create an image
auth_headers = {
'X-Auth-Token': 'user1:tenant1:member',
}
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
# Verify both admin and unknown role can create properties marked with
# '@'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_all_permitted_admin': '1'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
self.assertEqual('1',
image['image']['properties']['x_all_permitted_admin'])
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
custom_props = {
'X-Image-Meta-Property-x_all_permitted_joe_soap': '1',
'X-Glance-Registry-Purge-Props': 'False'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
self.assertEqual(
'1', image['image']['properties']['x_all_permitted_joe_soap'])
# Verify both admin and unknown role can read properties marked with
# '@'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('1', response.get(
'x-image-meta-property-x_all_permitted_admin'))
self.assertEqual('1', response.get(
'x-image-meta-property-x_all_permitted_joe_soap'))
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertEqual('1', response.get(
'x-image-meta-property-x_all_permitted_admin'))
self.assertEqual('1', response.get(
'x-image-meta-property-x_all_permitted_joe_soap'))
# Verify both admin and unknown role can update properties marked with
# '@'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_all_permitted_admin': '2',
'X-Glance-Registry-Purge-Props': 'False'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
self.assertEqual('2',
image['image']['properties']['x_all_permitted_admin'])
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
custom_props = {
'X-Image-Meta-Property-x_all_permitted_joe_soap': '2',
'X-Glance-Registry-Purge-Props': 'False'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
self.assertEqual(
'2', image['image']['properties']['x_all_permitted_joe_soap'])
# Verify both admin and unknown role can delete properties marked with
# '@'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_all_permitted_joe_soap': '2',
'X-Glance-Registry-Purge-Props': 'True'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
self.assertNotIn('x_all_permitted_admin', image['image']['properties'])
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
custom_props = {
'X-Glance-Registry-Purge-Props': 'True'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
image = jsonutils.loads(content)
self.assertNotIn('x_all_permitted_joe_soap',
image['image']['properties'])
# Verify neither admin nor unknown role can create a property protected
# with '!'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_none_permitted_admin': '1'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
custom_props = {
'X-Image-Meta-Property-x_none_permitted_joe_soap': '1'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
# Verify neither admin nor unknown role can read properties marked with
# '!'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_none_read': '1'
}
auth_headers.update(custom_props)
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertRaises(KeyError,
response.get, 'X-Image-Meta-Property-x_none_read')
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'HEAD',
headers=auth_headers)
self.assertEqual(http_client.OK, response.status)
self.assertRaises(KeyError,
response.get, 'X-Image-Meta-Property-x_none_read')
# Verify neither admin nor unknown role can update properties marked
# with '!'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_none_update': '1'
}
auth_headers.update(custom_props)
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_none_update': '2'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
custom_props = {
'X-Image-Meta-Property-x_none_update': '2'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
# Verify neither admin nor unknown role can delete properties marked
# with '!'
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Image-Meta-Property-x_none_delete': '1'
}
auth_headers.update(custom_props)
auth_headers.update(CREATE_HEADERS)
path = "/v1/images"
response, content = self.http.request(path, 'POST',
headers=auth_headers)
self.assertEqual(http_client.CREATED, response.status)
data = jsonutils.loads(content)
image_id = data['image']['id']
auth_headers = {
'X-Auth-Token': 'user1:tenant1:admin',
}
custom_props = {
'X-Glance-Registry-Purge-Props': 'True'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
auth_headers = {
'X-Auth-Token': 'user1:tenant1:joe_soap',
}
custom_props = {
'X-Glance-Registry-Purge-Props': 'True'
}
auth_headers.update(custom_props)
path = "/v1/images/%s" % image_id
response, content = self.http.request(path, 'PUT',
headers=auth_headers)
self.assertEqual(http_client.FORBIDDEN, response.status)
glance-16.0.1/glance/tests/unit/ 0000775 0001750 0001750 00000000000 13267672475 016447 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/test_cache_middleware.py 0000666 0001750 0001750 00000101013 13267672245 023311 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import patch
from oslo_policy import policy
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import http_client as http
from six.moves import range
import testtools
import webob
import glance.api.middleware.cache
import glance.api.policy
from glance.common import exception
from glance import context
import glance.registry.client.v1.api as registry
from glance.tests.unit import base
from glance.tests.unit import utils as unit_test_utils
class ImageStub(object):
def __init__(self, image_id, extra_properties=None, visibility='private'):
if extra_properties is None:
extra_properties = {}
self.image_id = image_id
self.visibility = visibility
self.status = 'active'
self.extra_properties = extra_properties
self.checksum = 'c1234'
self.size = 123456789
class TestCacheMiddlewareURLMatching(testtools.TestCase):
def test_v1_no_match_detail(self):
req = webob.Request.blank('/v1/images/detail')
out = glance.api.middleware.cache.CacheFilter._match_request(req)
self.assertIsNone(out)
def test_v1_no_match_detail_with_query_params(self):
req = webob.Request.blank('/v1/images/detail?limit=10')
out = glance.api.middleware.cache.CacheFilter._match_request(req)
self.assertIsNone(out)
def test_v1_match_id_with_query_param(self):
req = webob.Request.blank('/v1/images/asdf?ping=pong')
out = glance.api.middleware.cache.CacheFilter._match_request(req)
self.assertEqual(('v1', 'GET', 'asdf'), out)
def test_v2_match_id(self):
req = webob.Request.blank('/v2/images/asdf/file')
out = glance.api.middleware.cache.CacheFilter._match_request(req)
self.assertEqual(('v2', 'GET', 'asdf'), out)
def test_v2_no_match_bad_path(self):
req = webob.Request.blank('/v2/images/asdf')
out = glance.api.middleware.cache.CacheFilter._match_request(req)
self.assertIsNone(out)
def test_no_match_unknown_version(self):
req = webob.Request.blank('/v3/images/asdf')
out = glance.api.middleware.cache.CacheFilter._match_request(req)
self.assertIsNone(out)
class TestCacheMiddlewareRequestStashCacheInfo(testtools.TestCase):
def setUp(self):
super(TestCacheMiddlewareRequestStashCacheInfo, self).setUp()
self.request = webob.Request.blank('')
self.middleware = glance.api.middleware.cache.CacheFilter
def test_stash_cache_request_info(self):
self.middleware._stash_request_info(self.request, 'asdf', 'GET', 'v2')
self.assertEqual('asdf', self.request.environ['api.cache.image_id'])
self.assertEqual('GET', self.request.environ['api.cache.method'])
self.assertEqual('v2', self.request.environ['api.cache.version'])
def test_fetch_cache_request_info(self):
self.request.environ['api.cache.image_id'] = 'asdf'
self.request.environ['api.cache.method'] = 'GET'
self.request.environ['api.cache.version'] = 'v2'
(image_id, method, version) = self.middleware._fetch_request_info(
self.request)
self.assertEqual('asdf', image_id)
self.assertEqual('GET', method)
self.assertEqual('v2', version)
def test_fetch_cache_request_info_unset(self):
out = self.middleware._fetch_request_info(self.request)
self.assertIsNone(out)
class ChecksumTestCacheFilter(glance.api.middleware.cache.CacheFilter):
def __init__(self):
class DummyCache(object):
def get_caching_iter(self, image_id, image_checksum, app_iter):
self.image_checksum = image_checksum
self.cache = DummyCache()
self.policy = unit_test_utils.FakePolicyEnforcer()
class TestCacheMiddlewareChecksumVerification(base.IsolatedUnitTest):
def setUp(self):
super(TestCacheMiddlewareChecksumVerification, self).setUp()
self.context = context.RequestContext(is_admin=True)
self.request = webob.Request.blank('')
self.request.context = self.context
def test_checksum_v1_header(self):
cache_filter = ChecksumTestCacheFilter()
headers = {"x-image-meta-checksum": "1234567890"}
resp = webob.Response(request=self.request, headers=headers)
cache_filter._process_GET_response(resp, None)
self.assertEqual("1234567890", cache_filter.cache.image_checksum)
def test_checksum_v2_header(self):
cache_filter = ChecksumTestCacheFilter()
headers = {
"x-image-meta-checksum": "1234567890",
"Content-MD5": "abcdefghi"
}
resp = webob.Response(request=self.request, headers=headers)
cache_filter._process_GET_response(resp, None)
self.assertEqual("abcdefghi", cache_filter.cache.image_checksum)
def test_checksum_missing_header(self):
cache_filter = ChecksumTestCacheFilter()
resp = webob.Response(request=self.request)
cache_filter._process_GET_response(resp, None)
self.assertIsNone(cache_filter.cache.image_checksum)
class FakeImageSerializer(object):
def show(self, response, raw_response):
return True
class ProcessRequestTestCacheFilter(glance.api.middleware.cache.CacheFilter):
def __init__(self):
self.serializer = FakeImageSerializer()
class DummyCache(object):
def __init__(self):
self.deleted_images = []
def is_cached(self, image_id):
return True
def get_caching_iter(self, image_id, image_checksum, app_iter):
pass
def delete_cached_image(self, image_id):
self.deleted_images.append(image_id)
def get_image_size(self, image_id):
pass
self.cache = DummyCache()
self.policy = unit_test_utils.FakePolicyEnforcer()
class TestCacheMiddlewareProcessRequest(base.IsolatedUnitTest):
def _enforcer_from_rules(self, unparsed_rules):
rules = policy.Rules.from_dict(unparsed_rules)
enforcer = glance.api.policy.Enforcer()
enforcer.set_rules(rules, overwrite=True)
return enforcer
def test_v1_deleted_image_fetch(self):
"""
Test for determining that when an admin tries to download a deleted
image it returns 404 Not Found error.
"""
def dummy_img_iterator():
for i in range(3):
yield i
image_id = 'test1'
image_meta = {
'id': image_id,
'name': 'fake_image',
'status': 'deleted',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': True,
'updated_at': '',
'properties': {},
}
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
self.assertRaises(exception.NotFound, cache_filter._process_v1_request,
request, image_id, dummy_img_iterator, image_meta)
def test_process_v1_request_for_deleted_but_cached_image(self):
"""
Test for determining image is deleted from cache when it is not found
in Glance Registry.
"""
def fake_process_v1_request(request, image_id, image_iterator,
image_meta):
raise exception.ImageNotFound()
def fake_get_v1_image_metadata(request, image_id):
return {'status': 'active', 'properties': {}}
image_id = 'test1'
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
self.stubs.Set(cache_filter, '_get_v1_image_metadata',
fake_get_v1_image_metadata)
self.stubs.Set(cache_filter, '_process_v1_request',
fake_process_v1_request)
cache_filter.process_request(request)
self.assertIn(image_id, cache_filter.cache.deleted_images)
def test_v1_process_request_image_fetch(self):
def dummy_img_iterator():
for i in range(3):
yield i
image_id = 'test1'
image_meta = {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'properties': {},
}
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
actual = cache_filter._process_v1_request(
request, image_id, dummy_img_iterator, image_meta)
self.assertTrue(actual)
def test_v1_remove_location_image_fetch(self):
class CheckNoLocationDataSerializer(object):
def show(self, response, raw_response):
return 'location_data' in raw_response['image_meta']
def dummy_img_iterator():
for i in range(3):
yield i
image_id = 'test1'
image_meta = {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'properties': {},
}
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
cache_filter.serializer = CheckNoLocationDataSerializer()
actual = cache_filter._process_v1_request(
request, image_id, dummy_img_iterator, image_meta)
self.assertFalse(actual)
def test_verify_metadata_deleted_image(self):
"""
Test verify_metadata raises exception.NotFound for a deleted image
"""
image_meta = {'status': 'deleted', 'is_public': True, 'deleted': True}
cache_filter = ProcessRequestTestCacheFilter()
self.assertRaises(exception.NotFound,
cache_filter._verify_metadata, image_meta)
def _test_verify_metadata_zero_size(self, image_meta):
"""
Test verify_metadata updates metadata with cached image size for images
with 0 size.
:param image_meta: Image metadata, which may be either an ImageTarget
instance or a legacy v1 dict.
"""
image_size = 1
cache_filter = ProcessRequestTestCacheFilter()
with patch.object(cache_filter.cache, 'get_image_size',
return_value=image_size):
cache_filter._verify_metadata(image_meta)
self.assertEqual(image_size, image_meta['size'])
def test_verify_metadata_zero_size(self):
"""
Test verify_metadata updates metadata with cached image size for images
with 0 size
"""
image_meta = {'size': 0, 'deleted': False, 'id': 'test1',
'status': 'active'}
self._test_verify_metadata_zero_size(image_meta)
def test_verify_metadata_is_image_target_instance_with_zero_size(self):
"""
Test verify_metadata updates metadata which is ImageTarget instance
"""
image = ImageStub('test1')
image.size = 0
image_meta = glance.api.policy.ImageTarget(image)
self._test_verify_metadata_zero_size(image_meta)
def test_v2_process_request_response_headers(self):
def dummy_img_iterator():
for i in range(3):
yield i
image_id = 'test1'
request = webob.Request.blank('/v2/images/test1/file')
request.context = context.RequestContext()
request.environ['api.cache.image'] = ImageStub(image_id)
image_meta = {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'properties': {},
}
cache_filter = ProcessRequestTestCacheFilter()
response = cache_filter._process_v2_request(
request, image_id, dummy_img_iterator, image_meta)
self.assertEqual('application/octet-stream',
response.headers['Content-Type'])
self.assertEqual('c1234', response.headers['Content-MD5'])
self.assertEqual('123456789', response.headers['Content-Length'])
def test_v2_process_request_without_checksum(self):
def dummy_img_iterator():
for i in range(3):
yield i
image_id = 'test1'
request = webob.Request.blank('/v2/images/test1/file')
request.context = context.RequestContext()
image = ImageStub(image_id)
image.checksum = None
request.environ['api.cache.image'] = image
image_meta = {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'size': '123456789',
}
cache_filter = ProcessRequestTestCacheFilter()
response = cache_filter._process_v2_request(
request, image_id, dummy_img_iterator, image_meta)
self.assertNotIn('Content-MD5', response.headers.keys())
def test_process_request_without_download_image_policy(self):
"""
Test for cache middleware skip processing when request
context has not 'download_image' role.
"""
def fake_get_v1_image_metadata(*args, **kwargs):
return {'status': 'active', 'properties': {}}
image_id = 'test1'
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
enforcer = self._enforcer_from_rules({'download_image': '!'})
cache_filter.policy = enforcer
self.assertRaises(webob.exc.HTTPForbidden,
cache_filter.process_request, request)
def test_v1_process_request_download_restricted(self):
"""
Test process_request for v1 api where _member_ role not able to
download the image with custom property.
"""
image_id = 'test1'
def fake_get_v1_image_metadata(*args, **kwargs):
return {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'x_test_key': 'test_1234'
}
enforcer = self._enforcer_from_rules({
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
})
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext(roles=['_member_'])
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
cache_filter.policy = enforcer
self.assertRaises(webob.exc.HTTPForbidden,
cache_filter.process_request, request)
def test_v1_process_request_download_permitted(self):
"""
Test process_request for v1 api where member role able to
download the image with custom property.
"""
image_id = 'test1'
def fake_get_v1_image_metadata(*args, **kwargs):
return {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'x_test_key': 'test_1234'
}
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext(roles=['member'])
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
actual = cache_filter.process_request(request)
self.assertTrue(actual)
def test_v1_process_request_image_meta_not_found(self):
"""
Test process_request for v1 api where registry raises NotFound
exception as image metadata not found.
"""
image_id = 'test1'
def fake_get_v1_image_metadata(*args, **kwargs):
raise exception.NotFound()
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext(roles=['_member_'])
cache_filter = ProcessRequestTestCacheFilter()
self.stubs.Set(registry, 'get_image_metadata',
fake_get_v1_image_metadata)
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
self.assertRaises(webob.exc.HTTPNotFound,
cache_filter.process_request, request)
def test_v2_process_request_download_restricted(self):
"""
Test process_request for v2 api where _member_ role not able to
download the image with custom property.
"""
image_id = 'test1'
extra_properties = {
'x_test_key': 'test_1234'
}
def fake_get_v2_image_metadata(*args, **kwargs):
image = ImageStub(image_id, extra_properties=extra_properties)
request.environ['api.cache.image'] = image
return glance.api.policy.ImageTarget(image)
enforcer = self._enforcer_from_rules({
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
})
request = webob.Request.blank('/v2/images/test1/file')
request.context = context.RequestContext(roles=['_member_'])
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._get_v2_image_metadata = fake_get_v2_image_metadata
cache_filter.policy = enforcer
self.assertRaises(webob.exc.HTTPForbidden,
cache_filter.process_request, request)
def test_v2_process_request_download_permitted(self):
"""
Test process_request for v2 api where member role able to
download the image with custom property.
"""
image_id = 'test1'
extra_properties = {
'x_test_key': 'test_1234'
}
def fake_get_v2_image_metadata(*args, **kwargs):
image = ImageStub(image_id, extra_properties=extra_properties)
request.environ['api.cache.image'] = image
return glance.api.policy.ImageTarget(image)
request = webob.Request.blank('/v2/images/test1/file')
request.context = context.RequestContext(roles=['member'])
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._get_v2_image_metadata = fake_get_v2_image_metadata
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
actual = cache_filter.process_request(request)
self.assertTrue(actual)
class TestCacheMiddlewareProcessResponse(base.IsolatedUnitTest):
def test_process_v1_DELETE_response(self):
image_id = 'test1'
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
headers = {"x-image-meta-deleted": True}
resp = webob.Response(request=request, headers=headers)
actual = cache_filter._process_DELETE_response(resp, image_id)
self.assertEqual(resp, actual)
def test_get_status_code(self):
headers = {"x-image-meta-deleted": True}
resp = webob.Response(headers=headers)
cache_filter = ProcessRequestTestCacheFilter()
actual = cache_filter.get_status_code(resp)
self.assertEqual(http.OK, actual)
def test_process_response(self):
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v1')
def fake_get_v1_image_metadata(*args, **kwargs):
return {'properties': {}}
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
image_id = 'test1'
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
headers = {"x-image-meta-deleted": True}
resp = webob.Response(request=request, headers=headers)
actual = cache_filter.process_response(resp)
self.assertEqual(resp, actual)
def test_process_response_without_download_image_policy(self):
"""
Test for cache middleware raise webob.exc.HTTPForbidden directly
when request context has not 'download_image' role.
"""
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v1')
def fake_get_v1_image_metadata(*args, **kwargs):
return {'properties': {}}
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
rules = {'download_image': '!'}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
image_id = 'test1'
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
resp = webob.Response(request=request)
self.assertRaises(webob.exc.HTTPForbidden,
cache_filter.process_response, resp)
self.assertEqual([b''], resp.app_iter)
def test_v1_process_response_download_restricted(self):
"""
Test process_response for v1 api where _member_ role not able to
download the image with custom property.
"""
image_id = 'test1'
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v1')
def fake_get_v1_image_metadata(*args, **kwargs):
return {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'x_test_key': 'test_1234'
}
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext(roles=['_member_'])
resp = webob.Response(request=request)
self.assertRaises(webob.exc.HTTPForbidden,
cache_filter.process_response, resp)
def test_v1_process_response_download_permitted(self):
"""
Test process_response for v1 api where member role able to
download the image with custom property.
"""
image_id = 'test1'
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v1')
def fake_get_v1_image_metadata(*args, **kwargs):
return {
'id': image_id,
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': False,
'updated_at': '',
'x_test_key': 'test_1234'
}
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext(roles=['member'])
resp = webob.Response(request=request)
actual = cache_filter.process_response(resp)
self.assertEqual(resp, actual)
def test_v1_process_response_image_meta_not_found(self):
"""
Test process_response for v1 api where registry raises NotFound
exception as image metadata not found.
"""
image_id = 'test1'
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v1')
def fake_get_v1_image_metadata(*args, **kwargs):
raise exception.NotFound()
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
self.stubs.Set(registry, 'get_image_metadata',
fake_get_v1_image_metadata)
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext(roles=['_member_'])
resp = webob.Response(request=request)
self.assertRaises(webob.exc.HTTPNotFound,
cache_filter.process_response, resp)
def test_v2_process_response_download_restricted(self):
"""
Test process_response for v2 api where _member_ role not able to
download the image with custom property.
"""
image_id = 'test1'
extra_properties = {
'x_test_key': 'test_1234'
}
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v2')
def fake_get_v2_image_metadata(*args, **kwargs):
image = ImageStub(image_id, extra_properties=extra_properties)
request.environ['api.cache.image'] = image
return glance.api.policy.ImageTarget(image)
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
cache_filter._get_v2_image_metadata = fake_get_v2_image_metadata
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
request = webob.Request.blank('/v2/images/test1/file')
request.context = context.RequestContext(roles=['_member_'])
resp = webob.Response(request=request)
self.assertRaises(webob.exc.HTTPForbidden,
cache_filter.process_response, resp)
def test_v2_process_response_download_permitted(self):
"""
Test process_response for v2 api where member role able to
download the image with custom property.
"""
image_id = 'test1'
extra_properties = {
'x_test_key': 'test_1234'
}
def fake_fetch_request_info(*args, **kwargs):
return ('test1', 'GET', 'v2')
def fake_get_v2_image_metadata(*args, **kwargs):
image = ImageStub(image_id, extra_properties=extra_properties)
request.environ['api.cache.image'] = image
return glance.api.policy.ImageTarget(image)
cache_filter = ProcessRequestTestCacheFilter()
cache_filter._fetch_request_info = fake_fetch_request_info
cache_filter._get_v2_image_metadata = fake_get_v2_image_metadata
rules = {
"restricted":
"not ('test_1234':%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
cache_filter.policy = glance.api.policy.Enforcer()
request = webob.Request.blank('/v2/images/test1/file')
request.context = context.RequestContext(roles=['member'])
resp = webob.Response(request=request)
actual = cache_filter.process_response(resp)
self.assertEqual(resp, actual)
glance-16.0.1/glance/tests/unit/async/ 0000775 0001750 0001750 00000000000 13267672475 017564 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/async/test_async.py 0000666 0001750 0001750 00000003230 13267672245 022305 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import glance.async
import glance.tests.utils as test_utils
class TestTaskExecutor(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskExecutor, self).setUp()
self.context = mock.Mock()
self.task_repo = mock.Mock()
self.image_repo = mock.Mock()
self.image_factory = mock.Mock()
self.executor = glance.async.TaskExecutor(self.context,
self.task_repo,
self.image_repo,
self.image_factory)
def test_begin_processing(self):
# setup
task_id = mock.ANY
task_type = mock.ANY
task = mock.Mock()
with mock.patch.object(
glance.async.TaskExecutor,
'_run') as mock_run:
self.task_repo.get.return_value = task
self.executor.begin_processing(task_id)
# assert the call
mock_run.assert_called_once_with(task_id, task_type)
glance-16.0.1/glance/tests/unit/async/__init__.py 0000666 0001750 0001750 00000000000 13267672245 021660 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/async/flows/ 0000775 0001750 0001750 00000000000 13267672475 020716 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/async/flows/test_api_image_import.py 0000666 0001750 0001750 00000006440 13267672245 025635 0 ustar zuul zuul 0000000 0000000 # Copyright 2018 Verizon Wireless
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_config import cfg
import glance.async.flows.api_image_import as import_flow
import glance.tests.utils as test_utils
CONF = cfg.CONF
TASK_TYPE = 'api_image_import'
TASK_ID1 = 'dbbe7231-020f-4311-87e1-5aaa6da56c02'
IMAGE_ID1 = '41f5b3b0-f54c-4cef-bd45-ce3e376a142f'
class TestApiImageImportTask(test_utils.BaseTestCase):
def setUp(self):
super(TestApiImageImportTask, self).setUp()
self.wd_task_input = {
"import_req": {
"method": {
"name": "web-download",
"uri": "http://example.com/image.browncow"
}
}
}
self.gd_task_input = {
"import_req": {
"method": {
"name": "glance-direct"
}
}
}
self.mock_task_repo = mock.MagicMock()
self.mock_image_repo = mock.MagicMock()
@mock.patch('glance.async.flows.api_image_import._VerifyStaging.__init__')
@mock.patch('taskflow.patterns.linear_flow.Flow.add')
@mock.patch('taskflow.patterns.linear_flow.__init__')
def _pass_uri(self, mock_lf_init, mock_flow_add, mock_VS_init,
uri, file_uri, import_req):
flow_kwargs = {"task_id": TASK_ID1,
"task_type": TASK_TYPE,
"task_repo": self.mock_task_repo,
"image_repo": self.mock_image_repo,
"image_id": IMAGE_ID1,
"import_req": import_req}
mock_lf_init.return_value = None
mock_VS_init.return_value = None
self.config(node_staging_uri=uri)
import_flow.get_flow(**flow_kwargs)
mock_VS_init.assert_called_with(TASK_ID1, TASK_TYPE,
self.mock_task_repo,
file_uri)
def test_get_flow_handles_node_uri_with_ending_slash(self):
test_uri = 'file:///some/where/'
expected_uri = '{0}{1}'.format(test_uri, IMAGE_ID1)
self._pass_uri(uri=test_uri, file_uri=expected_uri,
import_req=self.gd_task_input['import_req'])
self._pass_uri(uri=test_uri, file_uri=expected_uri,
import_req=self.wd_task_input['import_req'])
def test_get_flow_handles_node_uri_without_ending_slash(self):
test_uri = 'file:///some/where'
expected_uri = '{0}/{1}'.format(test_uri, IMAGE_ID1)
self._pass_uri(uri=test_uri, file_uri=expected_uri,
import_req=self.wd_task_input['import_req'])
self._pass_uri(uri=test_uri, file_uri=expected_uri,
import_req=self.gd_task_input['import_req'])
glance-16.0.1/glance/tests/unit/async/flows/__init__.py 0000666 0001750 0001750 00000000000 13267672245 023012 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/async/flows/plugins/ 0000775 0001750 0001750 00000000000 13267672475 022377 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/async/flows/plugins/__init__.py 0000666 0001750 0001750 00000000000 13267672245 024473 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/async/flows/plugins/test_inject_image_metadata.py 0000666 0001750 0001750 00000011753 13267672245 030272 0 ustar zuul zuul 0000000 0000000 # Copyright 2018 NTT DATA, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import os
import glance_store
from oslo_config import cfg
import glance.async.flows.plugins.inject_image_metadata as inject_metadata
from glance.common import utils
from glance import domain
from glance import gateway
from glance.tests.unit import utils as test_unit_utils
import glance.tests.utils as test_utils
CONF = cfg.CONF
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class TestInjectImageMetadataTask(test_utils.BaseTestCase):
def setUp(self):
super(TestInjectImageMetadataTask, self).setUp()
glance_store.register_opts(CONF)
self.config(default_store='file',
stores=['file', 'http'],
filesystem_store_datadir=self.test_dir,
group="glance_store")
glance_store.create_stores(CONF)
self.work_dir = os.path.join(self.test_dir, 'work_dir')
utils.safe_mkdirs(self.work_dir)
self.config(work_dir=self.work_dir, group='task')
self.context = mock.MagicMock()
self.img_repo = mock.MagicMock()
self.task_repo = mock.MagicMock()
self.image_id = mock.MagicMock()
self.gateway = gateway.Gateway()
self.task_factory = domain.TaskFactory()
self.img_factory = self.gateway.get_image_factory(self.context)
self.image = self.img_factory.new_image(image_id=UUID1,
disk_format='qcow2',
container_format='bare')
task_input = {
"import_from": "http://cloud.foo/image.qcow2",
"import_from_format": "qcow2",
"image_properties": {'disk_format': 'qcow2',
'container_format': 'bare'}
}
task_ttl = CONF.task.task_time_to_live
self.task_type = 'import'
self.task = self.task_factory.new_task(self.task_type, TENANT1,
task_time_to_live=task_ttl,
task_input=task_input)
def test_inject_image_metadata_using_non_admin_user(self):
context = test_unit_utils.get_fake_context(roles='member')
inject_image_metadata = inject_metadata._InjectMetadataProperties(
context, self.task.task_id, self.task_type, self.img_repo,
self.image_id)
self.config(inject={"test": "abc"},
group='inject_metadata_properties')
with mock.patch.object(self.img_repo, 'get') as get_mock:
image = mock.MagicMock(image_id=self.image_id,
extra_properties={"test": "abc"})
get_mock.return_value = image
with mock.patch.object(self.img_repo, 'save') as save_mock:
inject_image_metadata.execute()
get_mock.assert_called_once_with(self.image_id)
save_mock.assert_called_once_with(image)
self.assertEqual({"test": "abc"}, image.extra_properties)
def test_inject_image_metadata_using_admin_user(self):
context = test_unit_utils.get_fake_context(roles='admin')
inject_image_metadata = inject_metadata._InjectMetadataProperties(
context, self.task.task_id, self.task_type, self.img_repo,
self.image_id)
self.config(inject={"test": "abc"},
group='inject_metadata_properties')
inject_image_metadata.execute()
with mock.patch.object(self.img_repo, 'get') as get_mock:
get_mock.assert_not_called()
with mock.patch.object(self.img_repo, 'save') as save_mock:
save_mock.assert_not_called()
def test_inject_image_metadata_empty(self):
context = test_unit_utils.get_fake_context(roles='member')
inject_image_metadata = inject_metadata._InjectMetadataProperties(
context, self.task.task_id, self.task_type, self.img_repo,
self.image_id)
self.config(inject={}, group='inject_metadata_properties')
inject_image_metadata.execute()
with mock.patch.object(self.img_repo, 'get') as get_mock:
get_mock.assert_not_called()
with mock.patch.object(self.img_repo, 'save') as save_mock:
save_mock.assert_not_called()
glance-16.0.1/glance/tests/unit/async/flows/test_ovf_process.py 0000666 0001750 0001750 00000016015 13267672245 024657 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 Intel Corporation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os.path
import shutil
import tarfile
import tempfile
import mock
try:
from defusedxml.cElementTree import ParseError
except ImportError:
from defusedxml.ElementTree import ParseError
from glance.async.flows import ovf_process
import glance.tests.utils as test_utils
from oslo_config import cfg
class TestOvfProcessTask(test_utils.BaseTestCase):
def setUp(self):
super(TestOvfProcessTask, self).setUp()
# The glance/tests/var dir containing sample ova packages used
# by the tests in this class
self.test_ova_dir = os.path.abspath(os.path.join(
os.path.dirname(__file__),
'../../../', 'var'))
self.tempdir = tempfile.mkdtemp()
self.config(work_dir=self.tempdir, group="task")
# These are the properties that we will extract from the ovf
# file contained in a ova package
interested_properties = (
'{\n'
' "cim_pasd": [\n'
' "InstructionSetExtensionName",\n'
' "ProcessorArchitecture"]\n'
'}\n')
self.config_file_name = os.path.join(self.tempdir, 'ovf-metadata.json')
with open(self.config_file_name, 'w') as config_file:
config_file.write(interested_properties)
self.image = mock.Mock()
self.image.container_format = 'ova'
self.image.context.is_admin = True
self.img_repo = mock.Mock()
self.img_repo.get.return_value = self.image
def tearDown(self):
if os.path.exists(self.tempdir):
shutil.rmtree(self.tempdir)
super(TestOvfProcessTask, self).tearDown()
def _copy_ova_to_tmpdir(self, ova_name):
# Copies an ova package to the tempdir from which
# it will be read by the system-under-test
shutil.copy(os.path.join(self.test_ova_dir, ova_name), self.tempdir)
return os.path.join(self.tempdir, ova_name)
@mock.patch.object(cfg.ConfigOpts, 'find_file')
def test_ovf_process_success(self, mock_find_file):
mock_find_file.return_value = self.config_file_name
ova_file_path = self._copy_ova_to_tmpdir('testserver.ova')
ova_uri = 'file://' + ova_file_path
oprocess = ovf_process._OVF_Process('task_id', 'ovf_proc',
self.img_repo)
self.assertEqual(ova_uri, oprocess.execute('test_image_id', ova_uri))
# Note that the extracted disk image is overwritten onto the input ova
# file
with open(ova_file_path, 'rb') as disk_image_file:
content = disk_image_file.read()
# b'ABCD' is the exact contents of the disk image file
# testserver-disk1.vmdk contained in the testserver.ova package used
# by this test
self.assertEqual(b'ABCD', content)
# 'DMTF:x86:VT-d' is the value in the testerver.ovf file in the
# testserver.ova package
self.image.extra_properties.update.assert_called_once_with(
{'cim_pasd_InstructionSetExtensionName': 'DMTF:x86:VT-d'})
self.assertEqual('bare', self.image.container_format)
@mock.patch.object(cfg.ConfigOpts, 'find_file')
def test_ovf_process_no_config_file(self, mock_find_file):
# Mimics a Glance deployment without the ovf-metadata.json file
mock_find_file.return_value = None
ova_file_path = self._copy_ova_to_tmpdir('testserver.ova')
ova_uri = 'file://' + ova_file_path
oprocess = ovf_process._OVF_Process('task_id', 'ovf_proc',
self.img_repo)
self.assertEqual(ova_uri, oprocess.execute('test_image_id', ova_uri))
# Note that the extracted disk image is overwritten onto the input
# ova file.
with open(ova_file_path, 'rb') as disk_image_file:
content = disk_image_file.read()
# b'ABCD' is the exact contents of the disk image file
# testserver-disk1.vmdk contained in the testserver.ova package used
# by this test
self.assertEqual(b'ABCD', content)
# No properties must be selected from the ovf file
self.image.extra_properties.update.assert_called_once_with({})
self.assertEqual('bare', self.image.container_format)
@mock.patch.object(cfg.ConfigOpts, 'find_file')
def test_ovf_process_not_admin(self, mock_find_file):
mock_find_file.return_value = self.config_file_name
ova_file_path = self._copy_ova_to_tmpdir('testserver.ova')
ova_uri = 'file://' + ova_file_path
self.image.context.is_admin = False
oprocess = ovf_process._OVF_Process('task_id', 'ovf_proc',
self.img_repo)
self.assertRaises(RuntimeError, oprocess.execute, 'test_image_id',
ova_uri)
def test_extract_ova_not_tar(self):
# testserver-not-tar.ova package is not in tar format
ova_file_path = os.path.join(self.test_ova_dir,
'testserver-not-tar.ova')
iextractor = ovf_process.OVAImageExtractor()
with open(ova_file_path, 'rb') as ova_file:
self.assertRaises(tarfile.ReadError, iextractor.extract, ova_file)
def test_extract_ova_no_disk(self):
# testserver-no-disk.ova package contains no disk image file
ova_file_path = os.path.join(self.test_ova_dir,
'testserver-no-disk.ova')
iextractor = ovf_process.OVAImageExtractor()
with open(ova_file_path, 'rb') as ova_file:
self.assertRaises(KeyError, iextractor.extract, ova_file)
def test_extract_ova_no_ovf(self):
# testserver-no-ovf.ova package contains no ovf file
ova_file_path = os.path.join(self.test_ova_dir,
'testserver-no-ovf.ova')
iextractor = ovf_process.OVAImageExtractor()
with open(ova_file_path, 'rb') as ova_file:
self.assertRaises(RuntimeError, iextractor.extract, ova_file)
def test_extract_ova_bad_ovf(self):
# testserver-bad-ovf.ova package has an ovf file that contains
# invalid xml
ova_file_path = os.path.join(self.test_ova_dir,
'testserver-bad-ovf.ova')
iextractor = ovf_process.OVAImageExtractor()
with open(ova_file_path, 'rb') as ova_file:
self.assertRaises(ParseError, iextractor._parse_OVF, ova_file)
glance-16.0.1/glance/tests/unit/async/flows/test_introspect.py 0000666 0001750 0001750 00000010773 13267672245 024526 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import glance_store
from oslo_concurrency import processutils
from oslo_config import cfg
from glance.async.flows import introspect
from glance.async import utils as async_utils
from glance import domain
import glance.tests.utils as test_utils
CONF = cfg.CONF
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class TestImportTask(test_utils.BaseTestCase):
def setUp(self):
super(TestImportTask, self).setUp()
self.task_factory = domain.TaskFactory()
task_input = {
"import_from": "http://cloud.foo/image.qcow2",
"import_from_format": "qcow2",
"image_properties": mock.sentinel.image_properties
}
task_ttl = CONF.task.task_time_to_live
self.task_type = 'import'
self.task = self.task_factory.new_task(self.task_type, TENANT1,
task_time_to_live=task_ttl,
task_input=task_input)
self.context = mock.Mock()
self.img_repo = mock.Mock()
self.task_repo = mock.Mock()
self.img_factory = mock.Mock()
glance_store.register_opts(CONF)
self.config(default_store='file',
stores=['file', 'http'],
filesystem_store_datadir=self.test_dir,
group="glance_store")
glance_store.create_stores(CONF)
def test_introspect_success(self):
image_create = introspect._Introspect(self.task.task_id,
self.task_type,
self.img_repo)
self.task_repo.get.return_value = self.task
image_id = mock.sentinel.image_id
image = mock.MagicMock(image_id=image_id)
self.img_repo.get.return_value = image
with mock.patch.object(processutils, 'execute') as exc_mock:
result = json.dumps({
"virtual-size": 10737418240,
"filename": "/tmp/image.qcow2",
"cluster-size": 65536,
"format": "qcow2",
"actual-size": 373030912,
"format-specific": {
"type": "qcow2",
"data": {
"compat": "0.10"
}
},
"dirty-flag": False
})
exc_mock.return_value = (result, None)
image_create.execute(image, '/test/path.qcow2')
self.assertEqual(10737418240, image.virtual_size)
# NOTE(hemanthm): Assert that process limits are being applied on
# "qemu-img info" calls. See bug #1449062 for more details.
kw_args = exc_mock.call_args[1]
self.assertIn('prlimit', kw_args)
self.assertEqual(async_utils.QEMU_IMG_PROC_LIMITS,
kw_args.get('prlimit'))
def test_introspect_no_image(self):
image_create = introspect._Introspect(self.task.task_id,
self.task_type,
self.img_repo)
self.task_repo.get.return_value = self.task
image_id = mock.sentinel.image_id
image = mock.MagicMock(image_id=image_id, virtual_size=None)
self.img_repo.get.return_value = image
# NOTE(flaper87): Don't mock, test the error.
with mock.patch.object(processutils, 'execute') as exc_mock:
exc_mock.return_value = (None, "some error")
# NOTE(flaper87): Pls, read the `OptionalTask._catch_all`
# docs to know why this is commented.
# self.assertRaises(RuntimeError,
# image_create.execute,
# image, '/test/path.qcow2')
image_create.execute(image, '/test/path.qcow2')
self.assertIsNone(image.virtual_size)
glance-16.0.1/glance/tests/unit/async/flows/test_import.py 0000666 0001750 0001750 00000042701 13267672245 023642 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import os
import glance_store
from oslo_concurrency import processutils as putils
from oslo_config import cfg
import six
from six.moves import urllib
from taskflow import task
from taskflow.types import failure
import glance.async.flows.base_import as import_flow
from glance.async import taskflow_executor
from glance.async import utils as async_utils
from glance.common.scripts.image_import import main as image_import
from glance.common.scripts import utils as script_utils
from glance.common import utils
from glance import domain
from glance import gateway
import glance.tests.utils as test_utils
CONF = cfg.CONF
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class _ErrorTask(task.Task):
def execute(self):
raise RuntimeError()
class TestImportTask(test_utils.BaseTestCase):
def setUp(self):
super(TestImportTask, self).setUp()
glance_store.register_opts(CONF)
self.config(default_store='file',
stores=['file', 'http'],
filesystem_store_datadir=self.test_dir,
group="glance_store")
glance_store.create_stores(CONF)
self.work_dir = os.path.join(self.test_dir, 'work_dir')
utils.safe_mkdirs(self.work_dir)
self.config(work_dir=self.work_dir, group='task')
self.context = mock.MagicMock()
self.img_repo = mock.MagicMock()
self.task_repo = mock.MagicMock()
self.gateway = gateway.Gateway()
self.task_factory = domain.TaskFactory()
self.img_factory = self.gateway.get_image_factory(self.context)
self.image = self.img_factory.new_image(image_id=UUID1,
disk_format='qcow2',
container_format='bare')
task_input = {
"import_from": "http://cloud.foo/image.qcow2",
"import_from_format": "qcow2",
"image_properties": {'disk_format': 'qcow2',
'container_format': 'bare'}
}
task_ttl = CONF.task.task_time_to_live
self.task_type = 'import'
self.task = self.task_factory.new_task(self.task_type, TENANT1,
task_time_to_live=task_ttl,
task_input=task_input)
def _assert_qemu_process_limits(self, exec_mock):
# NOTE(hemanthm): Assert that process limits are being applied
# on "qemu-img info" calls. See bug #1449062 for more details.
kw_args = exec_mock.call_args[1]
self.assertIn('prlimit', kw_args)
self.assertEqual(async_utils.QEMU_IMG_PROC_LIMITS,
kw_args.get('prlimit'))
def test_import_flow(self):
self.config(engine_mode='serial',
group='taskflow_executor')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = self.image
img_factory.new_image.side_effect = create_image
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
dmock.return_value = six.BytesIO(b"TEST_IMAGE")
with mock.patch.object(putils, 'trycmd') as tmock:
tmock.return_value = (json.dumps({
'format': 'qcow2',
}), None)
executor.begin_processing(self.task.task_id)
image_path = os.path.join(self.test_dir, self.image.image_id)
tmp_image_path = os.path.join(self.work_dir,
"%s.tasks_import" % image_path)
self.assertFalse(os.path.exists(tmp_image_path))
self.assertTrue(os.path.exists(image_path))
self.assertEqual(1, len(list(self.image.locations)))
self.assertEqual("file://%s/%s" % (self.test_dir,
self.image.image_id),
self.image.locations[0]['url'])
self._assert_qemu_process_limits(tmock)
def test_import_flow_missing_work_dir(self):
self.config(engine_mode='serial', group='taskflow_executor')
self.config(work_dir=None, group='task')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = self.image
img_factory.new_image.side_effect = create_image
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
dmock.return_value = six.BytesIO(b"TEST_IMAGE")
with mock.patch.object(import_flow._ImportToFS, 'execute') as emk:
executor.begin_processing(self.task.task_id)
self.assertFalse(emk.called)
image_path = os.path.join(self.test_dir, self.image.image_id)
tmp_image_path = os.path.join(self.work_dir,
"%s.tasks_import" % image_path)
self.assertFalse(os.path.exists(tmp_image_path))
self.assertTrue(os.path.exists(image_path))
def test_import_flow_revert_import_to_fs(self):
self.config(engine_mode='serial', group='taskflow_executor')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = self.image
img_factory.new_image.side_effect = create_image
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
dmock.side_effect = RuntimeError
with mock.patch.object(import_flow._ImportToFS, 'revert') as rmock:
self.assertRaises(RuntimeError,
executor.begin_processing, self.task.task_id)
self.assertTrue(rmock.called)
self.assertIsInstance(rmock.call_args[1]['result'],
failure.Failure)
image_path = os.path.join(self.test_dir, self.image.image_id)
tmp_image_path = os.path.join(self.work_dir,
"%s.tasks_import" % image_path)
self.assertFalse(os.path.exists(tmp_image_path))
# Note(sabari): The image should not have been uploaded to
# the store as the flow failed before ImportToStore Task.
self.assertFalse(os.path.exists(image_path))
def test_import_flow_backed_file_import_to_fs(self):
self.config(engine_mode='serial', group='taskflow_executor')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = self.image
img_factory.new_image.side_effect = create_image
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
dmock.return_value = six.BytesIO(b"TEST_IMAGE")
with mock.patch.object(putils, 'trycmd') as tmock:
tmock.return_value = (json.dumps({
'backing-filename': '/etc/password'
}), None)
with mock.patch.object(import_flow._ImportToFS,
'revert') as rmock:
self.assertRaises(RuntimeError,
executor.begin_processing,
self.task.task_id)
self.assertTrue(rmock.called)
self.assertIsInstance(rmock.call_args[1]['result'],
failure.Failure)
self._assert_qemu_process_limits(tmock)
image_path = os.path.join(self.test_dir,
self.image.image_id)
fname = "%s.tasks_import" % image_path
tmp_image_path = os.path.join(self.work_dir, fname)
self.assertFalse(os.path.exists(tmp_image_path))
# Note(sabari): The image should not have been uploaded to
# the store as the flow failed before ImportToStore Task.
self.assertFalse(os.path.exists(image_path))
def test_import_flow_revert(self):
self.config(engine_mode='serial',
group='taskflow_executor')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = self.image
img_factory.new_image.side_effect = create_image
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
dmock.return_value = six.BytesIO(b"TEST_IMAGE")
with mock.patch.object(putils, 'trycmd') as tmock:
tmock.return_value = (json.dumps({
'format': 'qcow2',
}), None)
with mock.patch.object(import_flow,
"_get_import_flows") as imock:
imock.return_value = (x for x in [_ErrorTask()])
self.assertRaises(RuntimeError,
executor.begin_processing,
self.task.task_id)
self._assert_qemu_process_limits(tmock)
image_path = os.path.join(self.test_dir,
self.image.image_id)
tmp_image_path = os.path.join(self.work_dir,
("%s.tasks_import" %
image_path))
self.assertFalse(os.path.exists(tmp_image_path))
# NOTE(flaper87): Eventually, we want this to be assertTrue
# The current issue is there's no way to tell taskflow to
# continue on failures. That is, revert the subflow but
# keep executing the parent flow. Under
# discussion/development.
self.assertFalse(os.path.exists(image_path))
def test_import_flow_no_import_flows(self):
self.config(engine_mode='serial',
group='taskflow_executor')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = self.image
img_factory.new_image.side_effect = create_image
with mock.patch.object(urllib.request, 'urlopen') as umock:
content = b"TEST_IMAGE"
umock.return_value = six.BytesIO(content)
with mock.patch.object(import_flow, "_get_import_flows") as imock:
imock.return_value = (x for x in [])
executor.begin_processing(self.task.task_id)
image_path = os.path.join(self.test_dir, self.image.image_id)
tmp_image_path = os.path.join(self.work_dir,
"%s.tasks_import" % image_path)
self.assertFalse(os.path.exists(tmp_image_path))
self.assertTrue(os.path.exists(image_path))
self.assertEqual(1, umock.call_count)
with open(image_path, 'rb') as ifile:
self.assertEqual(content, ifile.read())
def test_create_image(self):
image_create = import_flow._CreateImage(self.task.task_id,
self.task_type,
self.task_repo,
self.img_repo,
self.img_factory)
self.task_repo.get.return_value = self.task
with mock.patch.object(image_import, 'create_image') as ci_mock:
ci_mock.return_value = mock.Mock()
image_create.execute()
ci_mock.assert_called_once_with(self.img_repo,
self.img_factory,
{'container_format': 'bare',
'disk_format': 'qcow2'},
self.task.task_id)
def test_save_image(self):
save_image = import_flow._SaveImage(self.task.task_id,
self.task_type,
self.img_repo)
with mock.patch.object(self.img_repo, 'get') as get_mock:
image_id = mock.sentinel.image_id
image = mock.MagicMock(image_id=image_id, status='saving')
get_mock.return_value = image
with mock.patch.object(self.img_repo, 'save') as save_mock:
save_image.execute(image.image_id)
get_mock.assert_called_once_with(image_id)
save_mock.assert_called_once_with(image)
self.assertEqual('active', image.status)
def test_import_to_fs(self):
import_fs = import_flow._ImportToFS(self.task.task_id,
self.task_type,
self.task_repo,
'http://example.com/image.qcow2')
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
content = b"test"
dmock.return_value = [content]
with mock.patch.object(putils, 'trycmd') as tmock:
tmock.return_value = (json.dumps({
'format': 'qcow2',
}), None)
image_id = UUID1
path = import_fs.execute(image_id)
reader, size = glance_store.get_from_backend(path)
self.assertEqual(4, size)
self.assertEqual(content, b"".join(reader))
image_path = os.path.join(self.work_dir, image_id)
tmp_image_path = os.path.join(self.work_dir, image_path)
self.assertTrue(os.path.exists(tmp_image_path))
self._assert_qemu_process_limits(tmock)
def test_delete_from_fs(self):
delete_fs = import_flow._DeleteFromFS(self.task.task_id,
self.task_type)
data = [b"test"]
store = glance_store.get_store_from_scheme('file')
path = glance_store.store_add_to_backend(mock.sentinel.image_id, data,
mock.sentinel.image_size,
store, context=None)[0]
path_wo_scheme = path.split("file://")[1]
self.assertTrue(os.path.exists(path_wo_scheme))
delete_fs.execute(path)
self.assertFalse(os.path.exists(path_wo_scheme))
def test_complete_task(self):
complete_task = import_flow._CompleteTask(self.task.task_id,
self.task_type,
self.task_repo)
image_id = mock.sentinel.image_id
image = mock.MagicMock(image_id=image_id)
self.task_repo.get.return_value = self.task
with mock.patch.object(self.task, 'succeed') as succeed:
complete_task.execute(image.image_id)
succeed.assert_called_once_with({'image_id': image_id})
glance-16.0.1/glance/tests/unit/async/flows/test_convert.py 0000666 0001750 0001750 00000017564 13267672245 024021 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import os
import glance_store
from oslo_concurrency import processutils
from oslo_config import cfg
import six
from glance.async.flows import convert
from glance.async import taskflow_executor
from glance.common.scripts import utils as script_utils
from glance.common import utils
from glance import domain
from glance import gateway
import glance.tests.utils as test_utils
CONF = cfg.CONF
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class TestImportTask(test_utils.BaseTestCase):
def setUp(self):
super(TestImportTask, self).setUp()
self.work_dir = os.path.join(self.test_dir, 'work_dir')
utils.safe_mkdirs(self.work_dir)
self.config(work_dir=self.work_dir, group='task')
self.context = mock.MagicMock()
self.img_repo = mock.MagicMock()
self.task_repo = mock.MagicMock()
self.gateway = gateway.Gateway()
self.task_factory = domain.TaskFactory()
self.img_factory = self.gateway.get_image_factory(self.context)
self.image = self.img_factory.new_image(image_id=UUID1,
disk_format='raw',
container_format='bare')
task_input = {
"import_from": "http://cloud.foo/image.raw",
"import_from_format": "raw",
"image_properties": {'disk_format': 'qcow2',
'container_format': 'bare'}
}
task_ttl = CONF.task.task_time_to_live
self.task_type = 'import'
self.task = self.task_factory.new_task(self.task_type, TENANT1,
task_time_to_live=task_ttl,
task_input=task_input)
glance_store.register_opts(CONF)
self.config(default_store='file',
stores=['file', 'http'],
filesystem_store_datadir=self.test_dir,
group="glance_store")
self.config(conversion_format='qcow2',
group='taskflow_executor')
glance_store.create_stores(CONF)
def test_convert_success(self):
image_convert = convert._Convert(self.task.task_id,
self.task_type,
self.img_repo)
self.task_repo.get.return_value = self.task
image_id = mock.sentinel.image_id
image = mock.MagicMock(image_id=image_id, virtual_size=None)
self.img_repo.get.return_value = image
with mock.patch.object(processutils, 'execute') as exc_mock:
exc_mock.return_value = ("", None)
with mock.patch.object(os, 'rename') as rm_mock:
rm_mock.return_value = None
image_convert.execute(image, 'file:///test/path.raw')
# NOTE(hemanthm): Asserting that the source format is passed
# to qemu-utis to avoid inferring the image format. This
# shields us from an attack vector described at
# https://bugs.launchpad.net/glance/+bug/1449062/comments/72
self.assertIn('-f', exc_mock.call_args[0])
def test_convert_revert_success(self):
image_convert = convert._Convert(self.task.task_id,
self.task_type,
self.img_repo)
self.task_repo.get.return_value = self.task
image_id = mock.sentinel.image_id
image = mock.MagicMock(image_id=image_id, virtual_size=None)
self.img_repo.get.return_value = image
with mock.patch.object(processutils, 'execute') as exc_mock:
exc_mock.return_value = ("", None)
with mock.patch.object(os, 'remove') as rmtree_mock:
rmtree_mock.return_value = None
image_convert.revert(image, 'file:///tmp/test')
def test_import_flow_with_convert_and_introspect(self):
self.config(engine_mode='serial',
group='taskflow_executor')
image = self.img_factory.new_image(image_id=UUID1,
disk_format='raw',
container_format='bare')
img_factory = mock.MagicMock()
executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.img_repo,
img_factory)
self.task_repo.get.return_value = self.task
def create_image(*args, **kwargs):
kwargs['image_id'] = UUID1
return self.img_factory.new_image(*args, **kwargs)
self.img_repo.get.return_value = image
img_factory.new_image.side_effect = create_image
image_path = os.path.join(self.work_dir, image.image_id)
def fake_execute(*args, **kwargs):
if 'info' in args:
# NOTE(flaper87): Make sure the file actually
# exists. Extra check to verify previous tasks did
# what they were supposed to do.
assert os.path.exists(args[3].split("file://")[-1])
return (json.dumps({
"virtual-size": 10737418240,
"filename": "/tmp/image.qcow2",
"cluster-size": 65536,
"format": "qcow2",
"actual-size": 373030912,
"format-specific": {
"type": "qcow2",
"data": {
"compat": "0.10"
}
},
"dirty-flag": False
}), None)
open("%s.converted" % image_path, 'a').close()
return ("", None)
with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
dmock.return_value = six.BytesIO(b"TEST_IMAGE")
with mock.patch.object(processutils, 'execute') as exc_mock:
exc_mock.side_effect = fake_execute
executor.begin_processing(self.task.task_id)
# NOTE(flaper87): DeleteFromFS should've deleted this
# file. Make sure it doesn't exist.
self.assertFalse(os.path.exists(image_path))
# NOTE(flaper87): Workdir should be empty after all
# the tasks have been executed.
self.assertEqual([], os.listdir(self.work_dir))
self.assertEqual('qcow2', image.disk_format)
self.assertEqual(10737418240, image.virtual_size)
# NOTE(hemanthm): Asserting that the source format is passed
# to qemu-utis to avoid inferring the image format when
# converting. This shields us from an attack vector described
# at https://bugs.launchpad.net/glance/+bug/1449062/comments/72
#
# A total of three calls will be made to 'execute': 'info',
# 'convert' and 'info' towards introspection, conversion and
# OVF packaging respectively. We care about the 'convert' call
# here, hence we fetch the 2nd set of args from the args list.
convert_call_args, _ = exc_mock.call_args_list[1]
self.assertIn('-f', convert_call_args)
glance-16.0.1/glance/tests/unit/async/test_taskflow_executor.py 0000666 0001750 0001750 00000007260 13267672245 024747 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import glance_store
from oslo_config import cfg
from taskflow import engines
from glance.async import taskflow_executor
from glance.common.scripts.image_import import main as image_import
from glance import domain
import glance.tests.utils as test_utils
CONF = cfg.CONF
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class TestTaskExecutor(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskExecutor, self).setUp()
glance_store.register_opts(CONF)
self.config(default_store='file',
stores=['file', 'http'],
filesystem_store_datadir=self.test_dir,
group="glance_store")
glance_store.create_stores(CONF)
self.config(engine_mode='serial',
group='taskflow_executor')
self.context = mock.Mock()
self.task_repo = mock.Mock()
self.image_repo = mock.Mock()
self.image_factory = mock.Mock()
task_input = {
"import_from": "http://cloud.foo/image.qcow2",
"import_from_format": "qcow2",
"image_properties": {'disk_format': 'qcow2',
'container_format': 'bare'}
}
task_ttl = CONF.task.task_time_to_live
self.task_type = 'import'
self.task_factory = domain.TaskFactory()
self.task = self.task_factory.new_task(self.task_type, TENANT1,
task_time_to_live=task_ttl,
task_input=task_input)
self.executor = taskflow_executor.TaskExecutor(
self.context,
self.task_repo,
self.image_repo,
self.image_factory)
def test_begin_processing(self):
with mock.patch.object(engines, 'load') as load_mock:
engine = mock.Mock()
load_mock.return_value = engine
self.task_repo.get.return_value = self.task
self.executor.begin_processing(self.task.task_id)
# assert the call
self.assertEqual(1, load_mock.call_count)
self.assertEqual(1, engine.run.call_count)
def test_task_fail(self):
with mock.patch.object(engines, 'load') as load_mock:
engine = mock.Mock()
load_mock.return_value = engine
engine.run.side_effect = RuntimeError
self.task_repo.get.return_value = self.task
self.assertRaises(RuntimeError, self.executor.begin_processing,
self.task.task_id)
self.assertEqual('failure', self.task.status)
self.task_repo.save.assert_called_with(self.task)
def test_task_fail_upload(self):
with mock.patch.object(image_import, 'set_image_data') as import_mock:
import_mock.side_effect = IOError
self.task_repo.get.return_value = self.task
self.executor.begin_processing(self.task.task_id)
self.assertEqual('failure', self.task.status)
self.task_repo.save.assert_called_with(self.task)
self.assertEqual(1, import_mock.call_count)
glance-16.0.1/glance/tests/unit/test_policy.py 0000666 0001750 0001750 00000060635 13267672245 021366 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import os.path
import mock
import oslo_config.cfg
import glance.api.policy
from glance.common import exception
import glance.context
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
from glance.tests import utils as test_utils
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
class IterableMock(mock.Mock, collections.Iterable):
def __iter__(self):
while False:
yield None
class ImageRepoStub(object):
def get(self, *args, **kwargs):
return 'image_from_get'
def save(self, *args, **kwargs):
return 'image_from_save'
def add(self, *args, **kwargs):
return 'image_from_add'
def list(self, *args, **kwargs):
return ['image_from_list_0', 'image_from_list_1']
class ImageStub(object):
def __init__(self, image_id=None, visibility='private',
container_format='bear', disk_format='raw',
status='active', extra_properties=None):
if extra_properties is None:
extra_properties = {}
self.image_id = image_id
self.visibility = visibility
self.container_format = container_format
self.disk_format = disk_format
self.status = status
self.extra_properties = extra_properties
self.checksum = 'c2e5db72bd7fd153f53ede5da5a06de3'
self.created_at = '2013-09-28T15:27:36Z'
self.updated_at = '2013-09-28T15:27:37Z'
self.locations = []
self.min_disk = 0
self.min_ram = 0
self.name = 'image_name'
self.owner = 'tenant1'
self.protected = False
self.size = 0
self.virtual_size = 0
self.tags = []
def delete(self):
self.status = 'deleted'
class ImageFactoryStub(object):
def new_image(self, image_id=None, name=None, visibility='private',
min_disk=0, min_ram=0, protected=False, owner=None,
disk_format=None, container_format=None,
extra_properties=None, tags=None, **other_args):
self.visibility = visibility
return 'new_image'
class MemberRepoStub(object):
image = None
def add(self, image_member):
image_member.output = 'member_repo_add'
def get(self, *args, **kwargs):
return 'member_repo_get'
def save(self, image_member, from_state=None):
image_member.output = 'member_repo_save'
def list(self, *args, **kwargs):
return 'member_repo_list'
def remove(self, image_member):
image_member.output = 'member_repo_remove'
class ImageMembershipStub(object):
def __init__(self, output=None):
self.output = output
class TaskRepoStub(object):
def get(self, *args, **kwargs):
return 'task_from_get'
def add(self, *args, **kwargs):
return 'task_from_add'
def list(self, *args, **kwargs):
return ['task_from_list_0', 'task_from_list_1']
class TaskStub(object):
def __init__(self, task_id):
self.task_id = task_id
self.status = 'pending'
def run(self, executor):
self.status = 'processing'
class TaskFactoryStub(object):
def new_task(self, *args):
return 'new_task'
class TestPolicyEnforcer(base.IsolatedUnitTest):
def test_policy_file_default_rules_default_location(self):
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
enforcer.enforce(context, 'get_image', {})
def test_policy_file_custom_rules_default_location(self):
rules = {"get_image": '!'}
self.set_policy_rules(rules)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertRaises(exception.Forbidden,
enforcer.enforce, context, 'get_image', {})
def test_policy_file_custom_location(self):
self.config(policy_file=os.path.join(self.test_dir, 'gobble.gobble'),
group='oslo_policy')
rules = {"get_image": '!'}
self.set_policy_rules(rules)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertRaises(exception.Forbidden,
enforcer.enforce, context, 'get_image', {})
def test_policy_file_check(self):
self.config(policy_file=os.path.join(self.test_dir, 'gobble.gobble'),
group='oslo_policy')
rules = {"get_image": '!'}
self.set_policy_rules(rules)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertEqual(False, enforcer.check(context, 'get_image', {}))
def test_policy_file_get_image_default_everybody(self):
rules = {"default": ''}
self.set_policy_rules(rules)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertEqual(True, enforcer.check(context, 'get_image', {}))
def test_policy_file_get_image_default_nobody(self):
rules = {"default": '!'}
self.set_policy_rules(rules)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertRaises(exception.Forbidden,
enforcer.enforce, context, 'get_image', {})
class TestPolicyEnforcerNoFile(base.IsolatedUnitTest):
def test_policy_file_specified_but_not_found(self):
"""Missing defined policy file should result in a default ruleset"""
self.config(policy_file='gobble.gobble', group='oslo_policy')
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertRaises(exception.Forbidden,
enforcer.enforce, context, 'manage_image_cache', {})
admin_context = glance.context.RequestContext(roles=['admin'])
enforcer.enforce(admin_context, 'manage_image_cache', {})
def test_policy_file_default_not_found(self):
"""Missing default policy file should result in a default ruleset"""
def fake_find_file(self, name):
return None
self.stubs.Set(oslo_config.cfg.ConfigOpts, 'find_file',
fake_find_file)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[])
self.assertRaises(exception.Forbidden,
enforcer.enforce, context, 'manage_image_cache', {})
admin_context = glance.context.RequestContext(roles=['admin'])
enforcer.enforce(admin_context, 'manage_image_cache', {})
class TestImagePolicy(test_utils.BaseTestCase):
def setUp(self):
self.image_stub = ImageStub(UUID1)
self.image_repo_stub = ImageRepoStub()
self.image_factory_stub = ImageFactoryStub()
self.policy = mock.Mock()
self.policy.enforce = mock.Mock()
super(TestImagePolicy, self).setUp()
def test_publicize_image_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
self.assertRaises(exception.Forbidden,
setattr, image, 'visibility', 'public')
self.assertEqual('private', image.visibility)
self.policy.enforce.assert_called_once_with({}, "publicize_image",
image.target)
def test_publicize_image_allowed(self):
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
image.visibility = 'public'
self.assertEqual('public', image.visibility)
self.policy.enforce.assert_called_once_with({}, "publicize_image",
image.target)
def test_communitize_image_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
self.assertRaises(exception.Forbidden,
setattr, image, 'visibility', 'community')
self.assertEqual('private', image.visibility)
self.policy.enforce.assert_called_once_with({}, "communitize_image",
image.target)
def test_communitize_image_allowed(self):
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
image.visibility = 'community'
self.assertEqual('community', image.visibility)
self.policy.enforce.assert_called_once_with({}, "communitize_image",
image.target)
def test_delete_image_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
self.assertRaises(exception.Forbidden, image.delete)
self.assertEqual('active', image.status)
self.policy.enforce.assert_called_once_with({}, "delete_image",
image.target)
def test_delete_image_allowed(self):
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
args = dict(image.target)
image.delete()
self.assertEqual('deleted', image.status)
self.policy.enforce.assert_called_once_with({}, "delete_image", args)
def test_get_image_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image_target = IterableMock()
with mock.patch.object(glance.api.policy, 'ImageTarget') as target:
target.return_value = image_target
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
self.assertRaises(exception.Forbidden, image_repo.get, UUID1)
self.policy.enforce.assert_called_once_with({}, "get_image",
dict(image_target))
def test_get_image_allowed(self):
image_target = IterableMock()
with mock.patch.object(glance.api.policy, 'ImageTarget') as target:
target.return_value = image_target
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
output = image_repo.get(UUID1)
self.assertIsInstance(output, glance.api.policy.ImageProxy)
self.assertEqual('image_from_get', output.image)
self.policy.enforce.assert_called_once_with({}, "get_image",
dict(image_target))
def test_get_images_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
self.assertRaises(exception.Forbidden, image_repo.list)
self.policy.enforce.assert_called_once_with({}, "get_images", {})
def test_get_images_allowed(self):
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
images = image_repo.list()
for i, image in enumerate(images):
self.assertIsInstance(image, glance.api.policy.ImageProxy)
self.assertEqual('image_from_list_%d' % i, image.image)
self.policy.enforce.assert_called_once_with({}, "get_images", {})
def test_modify_image_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
self.assertRaises(exception.Forbidden, image_repo.save, image)
self.policy.enforce.assert_called_once_with({}, "modify_image",
image.target)
def test_modify_image_allowed(self):
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
image_repo.save(image)
self.policy.enforce.assert_called_once_with({}, "modify_image",
image.target)
def test_add_image_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
self.assertRaises(exception.Forbidden, image_repo.add, image)
self.policy.enforce.assert_called_once_with({}, "add_image",
image.target)
def test_add_image_allowed(self):
image_repo = glance.api.policy.ImageRepoProxy(self.image_repo_stub,
{}, self.policy)
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
image_repo.add(image)
self.policy.enforce.assert_called_once_with({}, "add_image",
image.target)
def test_new_image_visibility_public_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image_factory = glance.api.policy.ImageFactoryProxy(
self.image_factory_stub, {}, self.policy)
self.assertRaises(exception.Forbidden, image_factory.new_image,
visibility='public')
self.policy.enforce.assert_called_once_with({}, "publicize_image", {})
def test_new_image_visibility_public_allowed(self):
image_factory = glance.api.policy.ImageFactoryProxy(
self.image_factory_stub, {}, self.policy)
image_factory.new_image(visibility='public')
self.policy.enforce.assert_called_once_with({}, "publicize_image", {})
def test_new_image_visibility_community_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
image_factory = glance.api.policy.ImageFactoryProxy(
self.image_factory_stub, {}, self.policy)
self.assertRaises(exception.Forbidden, image_factory.new_image,
visibility='community')
self.policy.enforce.assert_called_once_with({},
"communitize_image",
{})
def test_new_image_visibility_community_allowed(self):
image_factory = glance.api.policy.ImageFactoryProxy(
self.image_factory_stub, {}, self.policy)
image_factory.new_image(visibility='community')
self.policy.enforce.assert_called_once_with({},
"communitize_image",
{})
def test_image_get_data_policy_enforced_with_target(self):
extra_properties = {
'test_key': 'test_4321'
}
image_stub = ImageStub(UUID1, extra_properties=extra_properties)
with mock.patch('glance.api.policy.ImageTarget'):
image = glance.api.policy.ImageProxy(image_stub, {}, self.policy)
target = image.target
self.policy.enforce.side_effect = exception.Forbidden
self.assertRaises(exception.Forbidden, image.get_data)
self.policy.enforce.assert_called_once_with({}, "download_image",
target)
class TestMemberPolicy(test_utils.BaseTestCase):
def setUp(self):
self.policy = mock.Mock()
self.policy.enforce = mock.Mock()
self.image_stub = ImageStub(UUID1)
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
self.member_repo = glance.api.policy.ImageMemberRepoProxy(
MemberRepoStub(), image, {}, self.policy)
self.target = self.member_repo.target
super(TestMemberPolicy, self).setUp()
def test_add_member_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
self.assertRaises(exception.Forbidden, self.member_repo.add, '')
self.policy.enforce.assert_called_once_with({}, "add_member",
self.target)
def test_add_member_allowed(self):
image_member = ImageMembershipStub()
self.member_repo.add(image_member)
self.assertEqual('member_repo_add', image_member.output)
self.policy.enforce.assert_called_once_with({}, "add_member",
self.target)
def test_get_member_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
self.assertRaises(exception.Forbidden, self.member_repo.get, '')
self.policy.enforce.assert_called_once_with({}, "get_member",
self.target)
def test_get_member_allowed(self):
output = self.member_repo.get('')
self.assertEqual('member_repo_get', output)
self.policy.enforce.assert_called_once_with({}, "get_member",
self.target)
def test_modify_member_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
self.assertRaises(exception.Forbidden, self.member_repo.save, '')
self.policy.enforce.assert_called_once_with({}, "modify_member",
self.target)
def test_modify_member_allowed(self):
image_member = ImageMembershipStub()
self.member_repo.save(image_member)
self.assertEqual('member_repo_save', image_member.output)
self.policy.enforce.assert_called_once_with({}, "modify_member",
self.target)
def test_get_members_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
self.assertRaises(exception.Forbidden, self.member_repo.list, '')
self.policy.enforce.assert_called_once_with({}, "get_members",
self.target)
def test_get_members_allowed(self):
output = self.member_repo.list('')
self.assertEqual('member_repo_list', output)
self.policy.enforce.assert_called_once_with({}, "get_members",
self.target)
def test_delete_member_not_allowed(self):
self.policy.enforce.side_effect = exception.Forbidden
self.assertRaises(exception.Forbidden, self.member_repo.remove, '')
self.policy.enforce.assert_called_once_with({}, "delete_member",
self.target)
def test_delete_member_allowed(self):
image_member = ImageMembershipStub()
self.member_repo.remove(image_member)
self.assertEqual('member_repo_remove', image_member.output)
self.policy.enforce.assert_called_once_with({}, "delete_member",
self.target)
class TestTaskPolicy(test_utils.BaseTestCase):
def setUp(self):
self.task_stub = TaskStub(UUID1)
self.task_repo_stub = TaskRepoStub()
self.task_factory_stub = TaskFactoryStub()
self.policy = unit_test_utils.FakePolicyEnforcer()
super(TestTaskPolicy, self).setUp()
def test_get_task_not_allowed(self):
rules = {"get_task": False}
self.policy.set_rules(rules)
task_repo = glance.api.policy.TaskRepoProxy(
self.task_repo_stub,
{},
self.policy
)
self.assertRaises(exception.Forbidden,
task_repo.get,
UUID1)
def test_get_task_allowed(self):
rules = {"get_task": True}
self.policy.set_rules(rules)
task_repo = glance.api.policy.TaskRepoProxy(
self.task_repo_stub,
{},
self.policy
)
task = task_repo.get(UUID1)
self.assertIsInstance(task, glance.api.policy.TaskProxy)
self.assertEqual('task_from_get', task.task)
def test_get_tasks_not_allowed(self):
rules = {"get_tasks": False}
self.policy.set_rules(rules)
task_repo = glance.api.policy.TaskStubRepoProxy(
self.task_repo_stub,
{},
self.policy
)
self.assertRaises(exception.Forbidden, task_repo.list)
def test_get_tasks_allowed(self):
rules = {"get_task": True}
self.policy.set_rules(rules)
task_repo = glance.api.policy.TaskStubRepoProxy(
self.task_repo_stub,
{},
self.policy
)
tasks = task_repo.list()
for i, task in enumerate(tasks):
self.assertIsInstance(task, glance.api.policy.TaskStubProxy)
self.assertEqual('task_from_list_%d' % i, task.task_stub)
def test_add_task_not_allowed(self):
rules = {"add_task": False}
self.policy.set_rules(rules)
task_repo = glance.api.policy.TaskRepoProxy(
self.task_repo_stub,
{},
self.policy
)
task = glance.api.policy.TaskProxy(self.task_stub, {}, self.policy)
self.assertRaises(exception.Forbidden, task_repo.add, task)
def test_add_task_allowed(self):
rules = {"add_task": True}
self.policy.set_rules(rules)
task_repo = glance.api.policy.TaskRepoProxy(
self.task_repo_stub,
{},
self.policy
)
task = glance.api.policy.TaskProxy(self.task_stub, {}, self.policy)
task_repo.add(task)
class TestContextPolicyEnforcer(base.IsolatedUnitTest):
def _do_test_policy_influence_context_admin(self,
policy_admin_role,
context_role,
context_is_admin,
admin_expected):
self.config(policy_file=os.path.join(self.test_dir, 'gobble.gobble'),
group='oslo_policy')
rules = {'context_is_admin': 'role:%s' % policy_admin_role}
self.set_policy_rules(rules)
enforcer = glance.api.policy.Enforcer()
context = glance.context.RequestContext(roles=[context_role],
is_admin=context_is_admin,
policy_enforcer=enforcer)
self.assertEqual(admin_expected, context.is_admin)
def test_context_admin_policy_admin(self):
self._do_test_policy_influence_context_admin('test_admin',
'test_admin',
True,
True)
def test_context_nonadmin_policy_admin(self):
self._do_test_policy_influence_context_admin('test_admin',
'test_admin',
False,
True)
def test_context_admin_policy_nonadmin(self):
self._do_test_policy_influence_context_admin('test_admin',
'demo',
True,
True)
def test_context_nonadmin_policy_nonadmin(self):
self._do_test_policy_influence_context_admin('test_admin',
'demo',
False,
False)
glance-16.0.1/glance/tests/unit/test_image_cache_client.py 0000666 0001750 0001750 00000012165 13267672245 023625 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import mock
from glance.common import exception
from glance.image_cache import client
from glance.tests import utils
class CacheClientTestCase(utils.BaseTestCase):
def setUp(self):
super(CacheClientTestCase, self).setUp()
self.client = client.CacheClient('test_host')
self.client.do_request = mock.Mock()
def test_delete_cached_image(self):
self.client.do_request.return_value = utils.FakeHTTPResponse()
self.assertTrue(self.client.delete_cached_image('test_id'))
self.client.do_request.assert_called_with("DELETE",
"/cached_images/test_id")
def test_get_cached_images(self):
expected_data = b'{"cached_images": "some_images"}'
self.client.do_request.return_value = utils.FakeHTTPResponse(
data=expected_data)
self.assertEqual("some_images", self.client.get_cached_images())
self.client.do_request.assert_called_with("GET", "/cached_images")
def test_get_queued_images(self):
expected_data = b'{"queued_images": "some_images"}'
self.client.do_request.return_value = utils.FakeHTTPResponse(
data=expected_data)
self.assertEqual("some_images", self.client.get_queued_images())
self.client.do_request.assert_called_with("GET", "/queued_images")
def test_delete_all_cached_images(self):
expected_data = b'{"num_deleted": 4}'
self.client.do_request.return_value = utils.FakeHTTPResponse(
data=expected_data)
self.assertEqual(4, self.client.delete_all_cached_images())
self.client.do_request.assert_called_with("DELETE", "/cached_images")
def test_queue_image_for_caching(self):
self.client.do_request.return_value = utils.FakeHTTPResponse()
self.assertTrue(self.client.queue_image_for_caching('test_id'))
self.client.do_request.assert_called_with("PUT",
"/queued_images/test_id")
def test_delete_queued_image(self):
self.client.do_request.return_value = utils.FakeHTTPResponse()
self.assertTrue(self.client.delete_queued_image('test_id'))
self.client.do_request.assert_called_with("DELETE",
"/queued_images/test_id")
def test_delete_all_queued_images(self):
expected_data = b'{"num_deleted": 4}'
self.client.do_request.return_value = utils.FakeHTTPResponse(
data=expected_data)
self.assertEqual(4, self.client.delete_all_queued_images())
self.client.do_request.assert_called_with("DELETE", "/queued_images")
class GetClientTestCase(utils.BaseTestCase):
def setUp(self):
super(GetClientTestCase, self).setUp()
self.host = 'test_host'
self.env = os.environ.copy()
os.environ.clear()
def tearDown(self):
os.environ = self.env
super(GetClientTestCase, self).tearDown()
def test_get_client_host_only(self):
expected_creds = {
'username': None,
'password': None,
'tenant': None,
'auth_url': None,
'strategy': 'noauth',
'region': None
}
self.assertEqual(expected_creds, client.get_client(self.host).creds)
def test_get_client_all_creds(self):
expected_creds = {
'username': 'name',
'password': 'pass',
'tenant': 'ten',
'auth_url': 'url',
'strategy': 'keystone',
'region': 'reg'
}
creds = client.get_client(
self.host,
username='name',
password='pass',
tenant='ten',
auth_url='url',
auth_strategy='strategy',
region='reg'
).creds
self.assertEqual(expected_creds, creds)
def test_get_client_using_provided_host(self):
cli = client.get_client(self.host)
cli._do_request = mock.MagicMock()
cli.configure_from_url = mock.MagicMock()
cli.auth_plugin.management_url = mock.MagicMock()
cli.do_request("GET", "/queued_images")
self.assertFalse(cli.configure_from_url.called)
self.assertFalse(client.get_client(self.host).configure_via_auth)
def test_get_client_client_configuration_error(self):
self.assertRaises(exception.ClientConfigurationError,
client.get_client, self.host, username='name',
password='pass', tenant='ten',
auth_strategy='keystone', region='reg')
glance-16.0.1/glance/tests/unit/test_schema.py 0000666 0001750 0001750 00000013461 13267672245 021322 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.common import exception
import glance.schema
from glance.tests import utils as test_utils
class TestBasicSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestBasicSchema, self).setUp()
properties = {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
}
self.schema = glance.schema.Schema('basic', properties)
def test_validate_passes(self):
obj = {'ham': 'no', 'eggs': 'scrambled'}
self.schema.validate(obj) # No exception raised
def test_validate_fails_on_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
self.assertRaises(exception.InvalidObject, self.schema.validate, obj)
def test_validate_fails_on_bad_type(self):
obj = {'eggs': 2}
self.assertRaises(exception.InvalidObject, self.schema.validate, obj)
def test_filter_strips_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
filtered = self.schema.filter(obj)
expected = {'ham': 'virginia', 'eggs': 'scrambled'}
self.assertEqual(expected, filtered)
def test_merge_properties(self):
self.schema.merge_properties({'bacon': {'type': 'string'}})
expected = set(['ham', 'eggs', 'bacon'])
actual = set(self.schema.raw()['properties'].keys())
self.assertEqual(expected, actual)
def test_merge_conflicting_properties(self):
conflicts = {'eggs': {'type': 'integer'}}
self.assertRaises(exception.SchemaLoadError,
self.schema.merge_properties, conflicts)
def test_merge_conflicting_but_identical_properties(self):
conflicts = {'ham': {'type': 'string'}}
self.schema.merge_properties(conflicts) # no exception raised
expected = set(['ham', 'eggs'])
actual = set(self.schema.raw()['properties'].keys())
self.assertEqual(expected, actual)
def test_raw_json_schema(self):
expected = {
'name': 'basic',
'properties': {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
},
'additionalProperties': False,
}
self.assertEqual(expected, self.schema.raw())
class TestBasicSchemaLinks(test_utils.BaseTestCase):
def setUp(self):
super(TestBasicSchemaLinks, self).setUp()
properties = {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
}
links = [
{'rel': 'up', 'href': '/menu'},
]
self.schema = glance.schema.Schema('basic', properties, links)
def test_raw_json_schema(self):
expected = {
'name': 'basic',
'properties': {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
},
'links': [
{'rel': 'up', 'href': '/menu'},
],
'additionalProperties': False,
}
self.assertEqual(expected, self.schema.raw())
class TestPermissiveSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestPermissiveSchema, self).setUp()
properties = {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
}
self.schema = glance.schema.PermissiveSchema('permissive', properties)
def test_validate_with_additional_properties_allowed(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
self.schema.validate(obj) # No exception raised
def test_validate_rejects_non_string_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'grits': 1000}
self.assertRaises(exception.InvalidObject, self.schema.validate, obj)
def test_filter_passes_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
filtered = self.schema.filter(obj)
self.assertEqual(obj, filtered)
def test_raw_json_schema(self):
expected = {
'name': 'permissive',
'properties': {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
},
'additionalProperties': {'type': 'string'},
}
self.assertEqual(expected, self.schema.raw())
class TestCollectionSchema(test_utils.BaseTestCase):
def test_raw_json_schema(self):
item_properties = {'cheese': {'type': 'string'}}
item_schema = glance.schema.Schema('mouse', item_properties)
collection_schema = glance.schema.CollectionSchema('mice', item_schema)
expected = {
'name': 'mice',
'properties': {
'mice': {
'type': 'array',
'items': item_schema.raw(),
},
'first': {'type': 'string'},
'next': {'type': 'string'},
'schema': {'type': 'string'},
},
'links': [
{'rel': 'first', 'href': '{first}'},
{'rel': 'next', 'href': '{next}'},
{'rel': 'describedby', 'href': '{schema}'},
],
}
self.assertEqual(expected, collection_schema.raw())
glance-16.0.1/glance/tests/unit/test_versions.py 0000666 0001750 0001750 00000024033 13267672245 021727 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
from six.moves import http_client as http
import webob
from glance.api.middleware import version_negotiation
from glance.api import versions
from glance.common.wsgi import Request as WsgiRequest
from glance.tests.unit import base
class VersionsTest(base.IsolatedUnitTest):
"""Test the version information returned from the API service."""
def _get_versions_list(self, url):
versions = [
{
'id': 'v2.6',
'status': 'CURRENT',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v2.5',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v2.4',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v2.3',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v2.2',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v2.1',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v2.0',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': '%s/v2/' % url}],
},
{
'id': 'v1.1',
'status': 'DEPRECATED',
'links': [{'rel': 'self',
'href': '%s/v1/' % url}],
},
{
'id': 'v1.0',
'status': 'DEPRECATED',
'links': [{'rel': 'self',
'href': '%s/v1/' % url}],
},
]
return versions
def test_get_version_list(self):
req = webob.Request.blank('/', base_url='http://127.0.0.1:9292/')
req.accept = 'application/json'
self.config(bind_host='127.0.0.1', bind_port=9292)
res = versions.Controller().index(req)
self.assertEqual(http.MULTIPLE_CHOICES, res.status_int)
self.assertEqual('application/json', res.content_type)
results = jsonutils.loads(res.body)['versions']
expected = self._get_versions_list('http://127.0.0.1:9292')
self.assertEqual(expected, results)
def test_get_version_list_public_endpoint(self):
req = webob.Request.blank('/', base_url='http://127.0.0.1:9292/')
req.accept = 'application/json'
self.config(bind_host='127.0.0.1', bind_port=9292,
public_endpoint='https://example.com:9292')
res = versions.Controller().index(req)
self.assertEqual(http.MULTIPLE_CHOICES, res.status_int)
self.assertEqual('application/json', res.content_type)
results = jsonutils.loads(res.body)['versions']
expected = self._get_versions_list('https://example.com:9292')
self.assertEqual(expected, results)
def test_get_version_list_secure_proxy_ssl_header(self):
self.config(secure_proxy_ssl_header='HTTP_X_FORWARDED_PROTO')
url = 'http://localhost:9292'
environ = webob.request.environ_from_url(url)
req = WsgiRequest(environ)
res = versions.Controller().index(req)
self.assertEqual(http.MULTIPLE_CHOICES, res.status_int)
self.assertEqual('application/json', res.content_type)
results = jsonutils.loads(res.body)['versions']
expected = self._get_versions_list(url)
self.assertEqual(expected, results)
def test_get_version_list_secure_proxy_ssl_header_https(self):
self.config(secure_proxy_ssl_header='HTTP_X_FORWARDED_PROTO')
url = 'http://localhost:9292'
ssl_url = 'https://localhost:9292'
environ = webob.request.environ_from_url(url)
environ['HTTP_X_FORWARDED_PROTO'] = "https"
req = WsgiRequest(environ)
res = versions.Controller().index(req)
self.assertEqual(http.MULTIPLE_CHOICES, res.status_int)
self.assertEqual('application/json', res.content_type)
results = jsonutils.loads(res.body)['versions']
expected = self._get_versions_list(ssl_url)
self.assertEqual(expected, results)
class VersionNegotiationTest(base.IsolatedUnitTest):
def setUp(self):
super(VersionNegotiationTest, self).setUp()
self.middleware = version_negotiation.VersionNegotiationFilter(None)
def test_request_url_v1(self):
request = webob.Request.blank('/v1/images')
self.middleware.process_request(request)
self.assertEqual('/v1/images', request.path_info)
def test_request_url_v1_0(self):
request = webob.Request.blank('/v1.0/images')
self.middleware.process_request(request)
self.assertEqual('/v1/images', request.path_info)
def test_request_url_v1_1(self):
request = webob.Request.blank('/v1.1/images')
self.middleware.process_request(request)
self.assertEqual('/v1/images', request.path_info)
def test_request_accept_v1(self):
request = webob.Request.blank('/images')
request.headers = {'accept': 'application/vnd.openstack.images-v1'}
self.middleware.process_request(request)
self.assertEqual('/v1/images', request.path_info)
def test_request_url_v2(self):
request = webob.Request.blank('/v2/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_0(self):
request = webob.Request.blank('/v2.0/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_1(self):
request = webob.Request.blank('/v2.1/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_2(self):
request = webob.Request.blank('/v2.2/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_3(self):
request = webob.Request.blank('/v2.3/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_4(self):
request = webob.Request.blank('/v2.4/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_5(self):
request = webob.Request.blank('/v2.5/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_6(self):
request = webob.Request.blank('/v2.6/images')
self.middleware.process_request(request)
self.assertEqual('/v2/images', request.path_info)
def test_request_url_v2_7_unsupported(self):
request = webob.Request.blank('/v2.7/images')
resp = self.middleware.process_request(request)
self.assertIsInstance(resp, versions.Controller)
def test_request_url_v2_7_unsupported_EXPERIMENTAL(self):
request = webob.Request.blank('/v2.7/images')
self.config(enable_image_import=True)
resp = self.middleware.process_request(request)
self.assertIsInstance(resp, versions.Controller)
class VersionsAndNegotiationTest(VersionNegotiationTest, VersionsTest):
"""
Test that versions mentioned in the versions response are correctly
negotiated.
"""
def _get_list_of_version_ids(self, status):
request = webob.Request.blank('/')
request.accept = 'application/json'
response = versions.Controller().index(request)
v_list = jsonutils.loads(response.body)['versions']
return [v['id'] for v in v_list if v['status'] == status]
def _assert_version_is_negotiated(self, version_id):
request = webob.Request.blank("/%s/images" % version_id)
self.middleware.process_request(request)
major = version_id.split('.', 1)[0]
expected = "/%s/images" % major
self.assertEqual(expected, request.path_info)
def test_current_is_negotiated(self):
# NOTE(rosmaita): Bug 1609571: the versions response was correct, but
# the negotiation had not been updated for the CURRENT version.
to_check = self._get_list_of_version_ids('CURRENT')
self.assertTrue(to_check)
for version_id in to_check:
self._assert_version_is_negotiated(version_id)
def test_supported_is_negotiated(self):
to_check = self._get_list_of_version_ids('SUPPORTED')
for version_id in to_check:
self._assert_version_is_negotiated(version_id)
def test_deprecated_is_negotiated(self):
to_check = self._get_list_of_version_ids('DEPRECATED')
for version_id in to_check:
self._assert_version_is_negotiated(version_id)
def test_experimental_is_negotiated(self):
to_check = self._get_list_of_version_ids('EXPERIMENTAL')
for version_id in to_check:
self._assert_version_is_negotiated(version_id)
glance-16.0.1/glance/tests/unit/test_db.py 0000666 0001750 0001750 00000076061 13267672254 020454 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
import mock
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_utils import encodeutils
from oslo_utils import timeutils
from glance.common import crypt
from glance.common import exception
import glance.context
import glance.db
from glance.db.sqlalchemy import api
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
CONF = cfg.CONF
CONF.import_opt('metadata_encryption_key', 'glance.common.config')
@mock.patch('oslo_utils.importutils.import_module')
class TestDbUtilities(test_utils.BaseTestCase):
def setUp(self):
super(TestDbUtilities, self).setUp()
self.config(data_api='silly pants')
self.api = mock.Mock()
def test_get_api_calls_configure_if_present(self, import_module):
import_module.return_value = self.api
self.assertEqual(glance.db.get_api(), self.api)
import_module.assert_called_once_with('silly pants')
self.api.configure.assert_called_once_with()
def test_get_api_skips_configure_if_missing(self, import_module):
import_module.return_value = self.api
del self.api.configure
self.assertEqual(glance.db.get_api(), self.api)
import_module.assert_called_once_with('silly pants')
self.assertFalse(hasattr(self.api, 'configure'))
def test_get_api_calls_for_v1_api(self, import_module):
api = glance.db.get_api(v1_mode=True)
self.assertNotEqual(api, self.api)
import_module.assert_called_once_with('glance.db.sqlalchemy.api')
api.configure.assert_called_once_with()
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
UUID4 = '6bbe7cc2-eae7-4c0f-b50d-a7160b0c6a86'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
UUID1_LOCATION = 'file:///path/to/image'
UUID1_LOCATION_METADATA = {'key': 'value'}
UUID3_LOCATION = 'http://somehost.com/place'
CHECKSUM = '93264c3edf5972c9f1cb309543d38a5c'
CHCKSUM1 = '43264c3edf4972c9f1cb309543d38a55'
def _db_fixture(id, **kwargs):
obj = {
'id': id,
'name': None,
'is_public': False,
'properties': {},
'checksum': None,
'owner': None,
'status': 'queued',
'tags': [],
'size': None,
'locations': [],
'protected': False,
'disk_format': None,
'container_format': None,
'deleted': False,
'min_ram': None,
'min_disk': None,
}
if 'visibility' in kwargs:
obj.pop('is_public')
obj.update(kwargs)
return obj
def _db_image_member_fixture(image_id, member_id, **kwargs):
obj = {
'image_id': image_id,
'member': member_id,
}
obj.update(kwargs)
return obj
def _db_task_fixture(task_id, type, status, **kwargs):
obj = {
'id': task_id,
'type': type,
'status': status,
'input': None,
'result': None,
'owner': None,
'message': None,
'deleted': False,
'expires_at': timeutils.utcnow() + datetime.timedelta(days=365)
}
obj.update(kwargs)
return obj
class TestImageRepo(test_utils.BaseTestCase):
def setUp(self):
super(TestImageRepo, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.context = glance.context.RequestContext(
user=USER1, tenant=TENANT1)
self.image_repo = glance.db.ImageRepo(self.context, self.db)
self.image_factory = glance.domain.ImageFactory()
self._create_images()
self._create_image_members()
def _create_images(self):
self.images = [
_db_fixture(UUID1, owner=TENANT1, checksum=CHECKSUM,
name='1', size=256,
is_public=True, status='active',
locations=[{'url': UUID1_LOCATION,
'metadata': UUID1_LOCATION_METADATA,
'status': 'active'}]),
_db_fixture(UUID2, owner=TENANT1, checksum=CHCKSUM1,
name='2', size=512, is_public=False),
_db_fixture(UUID3, owner=TENANT3, checksum=CHCKSUM1,
name='3', size=1024, is_public=True,
locations=[{'url': UUID3_LOCATION,
'metadata': {},
'status': 'active'}]),
_db_fixture(UUID4, owner=TENANT4, name='4', size=2048),
]
[self.db.image_create(None, image) for image in self.images]
self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
def _create_image_members(self):
self.image_members = [
_db_image_member_fixture(UUID2, TENANT2),
_db_image_member_fixture(UUID2, TENANT3, status='accepted'),
]
[self.db.image_member_create(None, image_member)
for image_member in self.image_members]
def test_get(self):
image = self.image_repo.get(UUID1)
self.assertEqual(UUID1, image.image_id)
self.assertEqual('1', image.name)
self.assertEqual(set(['ping', 'pong']), image.tags)
self.assertEqual('public', image.visibility)
self.assertEqual('active', image.status)
self.assertEqual(256, image.size)
self.assertEqual(TENANT1, image.owner)
def test_location_value(self):
image = self.image_repo.get(UUID3)
self.assertEqual(UUID3_LOCATION, image.locations[0]['url'])
def test_location_data_value(self):
image = self.image_repo.get(UUID1)
self.assertEqual(UUID1_LOCATION, image.locations[0]['url'])
self.assertEqual(UUID1_LOCATION_METADATA,
image.locations[0]['metadata'])
def test_location_data_exists(self):
image = self.image_repo.get(UUID2)
self.assertEqual([], image.locations)
def test_get_not_found(self):
fake_uuid = str(uuid.uuid4())
exc = self.assertRaises(exception.ImageNotFound, self.image_repo.get,
fake_uuid)
self.assertIn(fake_uuid, encodeutils.exception_to_unicode(exc))
def test_get_forbidden(self):
self.assertRaises(exception.NotFound, self.image_repo.get, UUID4)
def test_list(self):
images = self.image_repo.list()
image_ids = set([i.image_id for i in images])
self.assertEqual(set([UUID1, UUID2, UUID3]), image_ids)
def _do_test_list_status(self, status, expected):
self.context = glance.context.RequestContext(
user=USER1, tenant=TENANT3)
self.image_repo = glance.db.ImageRepo(self.context, self.db)
images = self.image_repo.list(member_status=status)
self.assertEqual(expected, len(images))
def test_list_status(self):
self._do_test_list_status(None, 3)
def test_list_status_pending(self):
self._do_test_list_status('pending', 2)
def test_list_status_rejected(self):
self._do_test_list_status('rejected', 2)
def test_list_status_all(self):
self._do_test_list_status('all', 3)
def test_list_with_marker(self):
full_images = self.image_repo.list()
full_ids = [i.image_id for i in full_images]
marked_images = self.image_repo.list(marker=full_ids[0])
actual_ids = [i.image_id for i in marked_images]
self.assertEqual(full_ids[1:], actual_ids)
def test_list_with_last_marker(self):
images = self.image_repo.list()
marked_images = self.image_repo.list(marker=images[-1].image_id)
self.assertEqual(0, len(marked_images))
def test_limited_list(self):
limited_images = self.image_repo.list(limit=2)
self.assertEqual(2, len(limited_images))
def test_list_with_marker_and_limit(self):
full_images = self.image_repo.list()
full_ids = [i.image_id for i in full_images]
marked_images = self.image_repo.list(marker=full_ids[0], limit=1)
actual_ids = [i.image_id for i in marked_images]
self.assertEqual(full_ids[1:2], actual_ids)
def test_list_private_images(self):
filters = {'visibility': 'private'}
images = self.image_repo.list(filters=filters)
self.assertEqual(0, len(images))
def test_list_shared_images(self):
filters = {'visibility': 'shared'}
images = self.image_repo.list(filters=filters)
image_ids = set([i.image_id for i in images])
self.assertEqual(set([UUID2]), image_ids)
def test_list_with_checksum_filter_single_image(self):
filters = {'checksum': CHECKSUM}
images = self.image_repo.list(filters=filters)
image_ids = list([i.image_id for i in images])
self.assertEqual(1, len(image_ids))
self.assertEqual([UUID1], image_ids)
def test_list_with_checksum_filter_multiple_images(self):
filters = {'checksum': CHCKSUM1}
images = self.image_repo.list(filters=filters)
image_ids = list([i.image_id for i in images])
self.assertEqual(2, len(image_ids))
self.assertIn(UUID2, image_ids)
self.assertIn(UUID3, image_ids)
def test_list_with_wrong_checksum(self):
WRONG_CHKSUM = 'd2fd42f979e1ed1aafadc7eb9354bff839c858cd'
filters = {'checksum': WRONG_CHKSUM}
images = self.image_repo.list(filters=filters)
self.assertEqual(0, len(images))
def test_list_with_tags_filter_single_tag(self):
filters = {'tags': ['ping']}
images = self.image_repo.list(filters=filters)
image_ids = list([i.image_id for i in images])
self.assertEqual(1, len(image_ids))
self.assertEqual([UUID1], image_ids)
def test_list_with_tags_filter_multiple_tags(self):
filters = {'tags': ['ping', 'pong']}
images = self.image_repo.list(filters=filters)
image_ids = list([i.image_id for i in images])
self.assertEqual(1, len(image_ids))
self.assertEqual([UUID1], image_ids)
def test_list_with_tags_filter_multiple_tags_and_nonexistent(self):
filters = {'tags': ['ping', 'fake']}
images = self.image_repo.list(filters=filters)
image_ids = list([i.image_id for i in images])
self.assertEqual(0, len(image_ids))
def test_list_with_wrong_tags(self):
filters = {'tags': ['fake']}
images = self.image_repo.list(filters=filters)
self.assertEqual(0, len(images))
def test_list_public_images(self):
filters = {'visibility': 'public'}
images = self.image_repo.list(filters=filters)
image_ids = set([i.image_id for i in images])
self.assertEqual(set([UUID1, UUID3]), image_ids)
def test_sorted_list(self):
images = self.image_repo.list(sort_key=['size'], sort_dir=['asc'])
image_ids = [i.image_id for i in images]
self.assertEqual([UUID1, UUID2, UUID3], image_ids)
def test_sorted_list_with_multiple_keys(self):
temp_id = 'd80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
image = _db_fixture(temp_id, owner=TENANT1, checksum=CHECKSUM,
name='1', size=1024,
is_public=True, status='active',
locations=[{'url': UUID1_LOCATION,
'metadata': UUID1_LOCATION_METADATA,
'status': 'active'}])
self.db.image_create(None, image)
images = self.image_repo.list(sort_key=['name', 'size'],
sort_dir=['asc'])
image_ids = [i.image_id for i in images]
self.assertEqual([UUID1, temp_id, UUID2, UUID3], image_ids)
images = self.image_repo.list(sort_key=['size', 'name'],
sort_dir=['asc'])
image_ids = [i.image_id for i in images]
self.assertEqual([UUID1, UUID2, temp_id, UUID3], image_ids)
def test_sorted_list_with_multiple_dirs(self):
temp_id = 'd80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
image = _db_fixture(temp_id, owner=TENANT1, checksum=CHECKSUM,
name='1', size=1024,
is_public=True, status='active',
locations=[{'url': UUID1_LOCATION,
'metadata': UUID1_LOCATION_METADATA,
'status': 'active'}])
self.db.image_create(None, image)
images = self.image_repo.list(sort_key=['name', 'size'],
sort_dir=['asc', 'desc'])
image_ids = [i.image_id for i in images]
self.assertEqual([temp_id, UUID1, UUID2, UUID3], image_ids)
images = self.image_repo.list(sort_key=['name', 'size'],
sort_dir=['desc', 'asc'])
image_ids = [i.image_id for i in images]
self.assertEqual([UUID3, UUID2, UUID1, temp_id], image_ids)
def test_add_image(self):
image = self.image_factory.new_image(name='added image')
self.assertEqual(image.updated_at, image.created_at)
self.image_repo.add(image)
retreived_image = self.image_repo.get(image.image_id)
self.assertEqual('added image', retreived_image.name)
self.assertEqual(image.updated_at, retreived_image.updated_at)
def test_save_image(self):
image = self.image_repo.get(UUID1)
original_update_time = image.updated_at
image.name = 'foo'
image.tags = ['king', 'kong']
self.image_repo.save(image)
current_update_time = image.updated_at
self.assertGreater(current_update_time, original_update_time)
image = self.image_repo.get(UUID1)
self.assertEqual('foo', image.name)
self.assertEqual(set(['king', 'kong']), image.tags)
self.assertEqual(current_update_time, image.updated_at)
def test_save_image_not_found(self):
fake_uuid = str(uuid.uuid4())
image = self.image_repo.get(UUID1)
image.image_id = fake_uuid
exc = self.assertRaises(exception.ImageNotFound, self.image_repo.save,
image)
self.assertIn(fake_uuid, encodeutils.exception_to_unicode(exc))
def test_remove_image(self):
image = self.image_repo.get(UUID1)
previous_update_time = image.updated_at
self.image_repo.remove(image)
self.assertGreater(image.updated_at, previous_update_time)
self.assertRaises(exception.ImageNotFound, self.image_repo.get, UUID1)
def test_remove_image_not_found(self):
fake_uuid = str(uuid.uuid4())
image = self.image_repo.get(UUID1)
image.image_id = fake_uuid
exc = self.assertRaises(
exception.ImageNotFound, self.image_repo.remove, image)
self.assertIn(fake_uuid, encodeutils.exception_to_unicode(exc))
class TestEncryptedLocations(test_utils.BaseTestCase):
def setUp(self):
super(TestEncryptedLocations, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.context = glance.context.RequestContext(
user=USER1, tenant=TENANT1)
self.image_repo = glance.db.ImageRepo(self.context, self.db)
self.image_factory = glance.domain.ImageFactory()
self.crypt_key = '0123456789abcdef'
self.config(metadata_encryption_key=self.crypt_key)
self.foo_bar_location = [{'url': 'foo', 'metadata': {},
'status': 'active'},
{'url': 'bar', 'metadata': {},
'status': 'active'}]
def test_encrypt_locations_on_add(self):
image = self.image_factory.new_image(UUID1)
image.locations = self.foo_bar_location
self.image_repo.add(image)
db_data = self.db.image_get(self.context, UUID1)
self.assertNotEqual(db_data['locations'], ['foo', 'bar'])
decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l['url'])
for l in db_data['locations']]
self.assertEqual([l['url'] for l in self.foo_bar_location],
decrypted_locations)
def test_encrypt_locations_on_save(self):
image = self.image_factory.new_image(UUID1)
self.image_repo.add(image)
image.locations = self.foo_bar_location
self.image_repo.save(image)
db_data = self.db.image_get(self.context, UUID1)
self.assertNotEqual(db_data['locations'], ['foo', 'bar'])
decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l['url'])
for l in db_data['locations']]
self.assertEqual([l['url'] for l in self.foo_bar_location],
decrypted_locations)
def test_decrypt_locations_on_get(self):
url_loc = ['ping', 'pong']
orig_locations = [{'url': l, 'metadata': {}, 'status': 'active'}
for l in url_loc]
encrypted_locs = [crypt.urlsafe_encrypt(self.crypt_key, l)
for l in url_loc]
encrypted_locations = [{'url': l, 'metadata': {}, 'status': 'active'}
for l in encrypted_locs]
self.assertNotEqual(encrypted_locations, orig_locations)
db_data = _db_fixture(UUID1, owner=TENANT1,
locations=encrypted_locations)
self.db.image_create(None, db_data)
image = self.image_repo.get(UUID1)
self.assertIn('id', image.locations[0])
self.assertIn('id', image.locations[1])
image.locations[0].pop('id')
image.locations[1].pop('id')
self.assertEqual(orig_locations, image.locations)
def test_decrypt_locations_on_list(self):
url_loc = ['ping', 'pong']
orig_locations = [{'url': l, 'metadata': {}, 'status': 'active'}
for l in url_loc]
encrypted_locs = [crypt.urlsafe_encrypt(self.crypt_key, l)
for l in url_loc]
encrypted_locations = [{'url': l, 'metadata': {}, 'status': 'active'}
for l in encrypted_locs]
self.assertNotEqual(encrypted_locations, orig_locations)
db_data = _db_fixture(UUID1, owner=TENANT1,
locations=encrypted_locations)
self.db.image_create(None, db_data)
image = self.image_repo.list()[0]
self.assertIn('id', image.locations[0])
self.assertIn('id', image.locations[1])
image.locations[0].pop('id')
image.locations[1].pop('id')
self.assertEqual(orig_locations, image.locations)
class TestImageMemberRepo(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMemberRepo, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.context = glance.context.RequestContext(
user=USER1, tenant=TENANT1)
self.image_repo = glance.db.ImageRepo(self.context, self.db)
self.image_member_factory = glance.domain.ImageMemberFactory()
self._create_images()
self._create_image_members()
image = self.image_repo.get(UUID1)
self.image_member_repo = glance.db.ImageMemberRepo(self.context,
self.db, image)
def _create_images(self):
self.images = [
_db_fixture(UUID1, owner=TENANT1, name='1', size=256,
status='active'),
_db_fixture(UUID2, owner=TENANT1, name='2',
size=512, visibility='shared'),
]
[self.db.image_create(None, image) for image in self.images]
self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
def _create_image_members(self):
self.image_members = [
_db_image_member_fixture(UUID1, TENANT2),
_db_image_member_fixture(UUID1, TENANT3),
]
[self.db.image_member_create(None, image_member)
for image_member in self.image_members]
def test_list(self):
image_members = self.image_member_repo.list()
image_member_ids = set([i.member_id for i in image_members])
self.assertEqual(set([TENANT2, TENANT3]), image_member_ids)
def test_list_no_members(self):
image = self.image_repo.get(UUID2)
self.image_member_repo_uuid2 = glance.db.ImageMemberRepo(
self.context, self.db, image)
image_members = self.image_member_repo_uuid2.list()
image_member_ids = set([i.member_id for i in image_members])
self.assertEqual(set([]), image_member_ids)
def test_save_image_member(self):
image_member = self.image_member_repo.get(TENANT2)
image_member.status = 'accepted'
self.image_member_repo.save(image_member)
image_member_updated = self.image_member_repo.get(TENANT2)
self.assertEqual(image_member.id, image_member_updated.id)
self.assertEqual('accepted', image_member_updated.status)
def test_add_image_member(self):
image = self.image_repo.get(UUID1)
image_member = self.image_member_factory.new_image_member(image,
TENANT4)
self.assertIsNone(image_member.id)
self.image_member_repo.add(image_member)
retreived_image_member = self.image_member_repo.get(TENANT4)
self.assertIsNotNone(retreived_image_member.id)
self.assertEqual(image_member.image_id,
retreived_image_member.image_id)
self.assertEqual(image_member.member_id,
retreived_image_member.member_id)
self.assertEqual('pending', retreived_image_member.status)
def test_add_duplicate_image_member(self):
image = self.image_repo.get(UUID1)
image_member = self.image_member_factory.new_image_member(image,
TENANT4)
self.assertIsNone(image_member.id)
self.image_member_repo.add(image_member)
retreived_image_member = self.image_member_repo.get(TENANT4)
self.assertIsNotNone(retreived_image_member.id)
self.assertEqual(image_member.image_id,
retreived_image_member.image_id)
self.assertEqual(image_member.member_id,
retreived_image_member.member_id)
self.assertEqual('pending', retreived_image_member.status)
self.assertRaises(exception.Duplicate, self.image_member_repo.add,
image_member)
def test_get_image_member(self):
image = self.image_repo.get(UUID1)
image_member = self.image_member_factory.new_image_member(image,
TENANT4)
self.assertIsNone(image_member.id)
self.image_member_repo.add(image_member)
member = self.image_member_repo.get(image_member.member_id)
self.assertEqual(member.id, image_member.id)
self.assertEqual(member.image_id, image_member.image_id)
self.assertEqual(member.member_id, image_member.member_id)
self.assertEqual('pending', member.status)
def test_get_nonexistent_image_member(self):
fake_image_member_id = 'fake'
self.assertRaises(exception.NotFound, self.image_member_repo.get,
fake_image_member_id)
def test_remove_image_member(self):
image_member = self.image_member_repo.get(TENANT2)
self.image_member_repo.remove(image_member)
self.assertRaises(exception.NotFound, self.image_member_repo.get,
TENANT2)
def test_remove_image_member_does_not_exist(self):
fake_uuid = str(uuid.uuid4())
image = self.image_repo.get(UUID2)
fake_member = glance.domain.ImageMemberFactory().new_image_member(
image, TENANT4)
fake_member.id = fake_uuid
exc = self.assertRaises(exception.NotFound,
self.image_member_repo.remove,
fake_member)
self.assertIn(fake_uuid, encodeutils.exception_to_unicode(exc))
class TestTaskRepo(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskRepo, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.context = glance.context.RequestContext(user=USER1,
tenant=TENANT1)
self.task_repo = glance.db.TaskRepo(self.context, self.db)
self.task_factory = glance.domain.TaskFactory()
self.fake_task_input = ('{"import_from": '
'"swift://cloud.foo/account/mycontainer/path"'
',"import_from_format": "qcow2"}')
self._create_tasks()
def _create_tasks(self):
self.tasks = [
_db_task_fixture(UUID1, type='import', status='pending',
input=self.fake_task_input,
result='',
owner=TENANT1,
message='',
),
_db_task_fixture(UUID2, type='import', status='processing',
input=self.fake_task_input,
result='',
owner=TENANT1,
message='',
),
_db_task_fixture(UUID3, type='import', status='failure',
input=self.fake_task_input,
result='',
owner=TENANT1,
message='',
),
_db_task_fixture(UUID4, type='import', status='success',
input=self.fake_task_input,
result='',
owner=TENANT2,
message='',
),
]
[self.db.task_create(None, task) for task in self.tasks]
def test_get(self):
task = self.task_repo.get(UUID1)
self.assertEqual(task.task_id, UUID1)
self.assertEqual('import', task.type)
self.assertEqual('pending', task.status)
self.assertEqual(task.task_input, self.fake_task_input)
self.assertEqual('', task.result)
self.assertEqual('', task.message)
self.assertEqual(task.owner, TENANT1)
def test_get_not_found(self):
self.assertRaises(exception.NotFound,
self.task_repo.get,
str(uuid.uuid4()))
def test_get_forbidden(self):
self.assertRaises(exception.NotFound,
self.task_repo.get,
UUID4)
def test_list(self):
tasks = self.task_repo.list()
task_ids = set([i.task_id for i in tasks])
self.assertEqual(set([UUID1, UUID2, UUID3]), task_ids)
def test_list_with_type(self):
filters = {'type': 'import'}
tasks = self.task_repo.list(filters=filters)
task_ids = set([i.task_id for i in tasks])
self.assertEqual(set([UUID1, UUID2, UUID3]), task_ids)
def test_list_with_status(self):
filters = {'status': 'failure'}
tasks = self.task_repo.list(filters=filters)
task_ids = set([i.task_id for i in tasks])
self.assertEqual(set([UUID3]), task_ids)
def test_list_with_marker(self):
full_tasks = self.task_repo.list()
full_ids = [i.task_id for i in full_tasks]
marked_tasks = self.task_repo.list(marker=full_ids[0])
actual_ids = [i.task_id for i in marked_tasks]
self.assertEqual(full_ids[1:], actual_ids)
def test_list_with_last_marker(self):
tasks = self.task_repo.list()
marked_tasks = self.task_repo.list(marker=tasks[-1].task_id)
self.assertEqual(0, len(marked_tasks))
def test_limited_list(self):
limited_tasks = self.task_repo.list(limit=2)
self.assertEqual(2, len(limited_tasks))
def test_list_with_marker_and_limit(self):
full_tasks = self.task_repo.list()
full_ids = [i.task_id for i in full_tasks]
marked_tasks = self.task_repo.list(marker=full_ids[0], limit=1)
actual_ids = [i.task_id for i in marked_tasks]
self.assertEqual(full_ids[1:2], actual_ids)
def test_sorted_list(self):
tasks = self.task_repo.list(sort_key='status', sort_dir='desc')
task_ids = [i.task_id for i in tasks]
self.assertEqual([UUID2, UUID1, UUID3], task_ids)
def test_add_task(self):
task_type = 'import'
task = self.task_factory.new_task(task_type, None,
task_input=self.fake_task_input)
self.assertEqual(task.updated_at, task.created_at)
self.task_repo.add(task)
retrieved_task = self.task_repo.get(task.task_id)
self.assertEqual(task.updated_at, retrieved_task.updated_at)
self.assertEqual(self.fake_task_input, retrieved_task.task_input)
def test_save_task(self):
task = self.task_repo.get(UUID1)
original_update_time = task.updated_at
self.task_repo.save(task)
current_update_time = task.updated_at
self.assertGreater(current_update_time, original_update_time)
task = self.task_repo.get(UUID1)
self.assertEqual(current_update_time, task.updated_at)
def test_remove_task(self):
task = self.task_repo.get(UUID1)
self.task_repo.remove(task)
self.assertRaises(exception.NotFound,
self.task_repo.get,
task.task_id)
class RetryOnDeadlockTestCase(test_utils.BaseTestCase):
def test_raise_deadlock(self):
class TestException(Exception):
pass
self.attempts = 3
def _mock_get_session():
def _raise_exceptions():
self.attempts -= 1
if self.attempts <= 0:
raise TestException("Exit")
raise db_exc.DBDeadlock("Fake Exception")
return _raise_exceptions
with mock.patch.object(api, 'get_session') as sess:
sess.side_effect = _mock_get_session()
try:
api._image_update(None, {}, 'fake-id')
except TestException:
self.assertEqual(3, sess.call_count)
# Test retry on image destroy if db deadlock occurs
self.attempts = 3
with mock.patch.object(api, 'get_session') as sess:
sess.side_effect = _mock_get_session()
try:
api.image_destroy(None, 'fake-id')
except TestException:
self.assertEqual(3, sess.call_count)
glance-16.0.1/glance/tests/unit/test_quota.py 0000666 0001750 0001750 00000071637 13267672245 021224 0 ustar zuul zuul 0000000 0000000 # Copyright 2013, Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import mock
from mock import patch
from oslo_utils import encodeutils
from oslo_utils import units
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.common import exception
from glance.common import store_utils
import glance.quota
from glance.tests.unit import utils as unit_test_utils
from glance.tests import utils as test_utils
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
class FakeContext(object):
owner = 'someone'
is_admin = False
class FakeImage(object):
size = None
image_id = 'someid'
locations = [{'url': 'file:///not/a/path', 'metadata': {}}]
tags = set([])
def set_data(self, data, size=None):
self.size = 0
for d in data:
self.size += len(d)
def __init__(self, **kwargs):
self.extra_properties = kwargs.get('extra_properties', {})
class TestImageQuota(test_utils.BaseTestCase):
def setUp(self):
super(TestImageQuota, self).setUp()
def _get_image(self, location_count=1, image_size=10):
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = 'xyz'
base_image.size = image_size
image = glance.quota.ImageProxy(base_image, context, db_api, store)
locations = []
for i in range(location_count):
locations.append({'url': 'file:///g/there/it/is%d' % i,
'metadata': {}, 'status': 'active'})
image_values = {'id': 'xyz', 'owner': context.owner,
'status': 'active', 'size': image_size,
'locations': locations}
db_api.image_create(context, image_values)
return image
def test_quota_allowed(self):
quota = 10
self.config(user_storage_quota=str(quota))
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = 'id'
image = glance.quota.ImageProxy(base_image, context, db_api, store)
data = '*' * quota
base_image.set_data(data, size=None)
image.set_data(data)
self.assertEqual(quota, base_image.size)
def _test_quota_allowed_unit(self, data_length, config_quota):
self.config(user_storage_quota=config_quota)
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = 'id'
image = glance.quota.ImageProxy(base_image, context, db_api, store)
data = '*' * data_length
base_image.set_data(data, size=None)
image.set_data(data)
self.assertEqual(data_length, base_image.size)
def test_quota_allowed_unit_b(self):
self._test_quota_allowed_unit(10, '10B')
def test_quota_allowed_unit_kb(self):
self._test_quota_allowed_unit(10, '1KB')
def test_quota_allowed_unit_mb(self):
self._test_quota_allowed_unit(10, '1MB')
def test_quota_allowed_unit_gb(self):
self._test_quota_allowed_unit(10, '1GB')
def test_quota_allowed_unit_tb(self):
self._test_quota_allowed_unit(10, '1TB')
def _quota_exceeded_size(self, quota, data,
deleted=True, size=None):
self.config(user_storage_quota=quota)
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = 'id'
image = glance.quota.ImageProxy(base_image, context, db_api, store)
if deleted:
with patch.object(store_utils, 'safe_delete_from_backend'):
store_utils.safe_delete_from_backend(
context,
image.image_id,
base_image.locations[0])
self.assertRaises(exception.StorageQuotaFull,
image.set_data,
data,
size=size)
def test_quota_exceeded_no_size(self):
quota = 10
data = '*' * (quota + 1)
# NOTE(jbresnah) When the image size is None it means that it is
# not known. In this case the only time we will raise an
# exception is when there is no room left at all, thus we know
# it will not fit.
# That's why 'get_remaining_quota' is mocked with return_value = 0.
with patch.object(glance.api.common, 'get_remaining_quota',
return_value=0):
self._quota_exceeded_size(str(quota), data)
def test_quota_exceeded_with_right_size(self):
quota = 10
data = '*' * (quota + 1)
self._quota_exceeded_size(str(quota), data, size=len(data),
deleted=False)
def test_quota_exceeded_with_right_size_b(self):
quota = 10
data = '*' * (quota + 1)
self._quota_exceeded_size('10B', data, size=len(data),
deleted=False)
def test_quota_exceeded_with_right_size_kb(self):
quota = units.Ki
data = '*' * (quota + 1)
self._quota_exceeded_size('1KB', data, size=len(data),
deleted=False)
def test_quota_exceeded_with_lie_size(self):
quota = 10
data = '*' * (quota + 1)
self._quota_exceeded_size(str(quota), data, deleted=False,
size=quota - 1)
def test_append_location(self):
new_location = {'url': 'file:///a/path', 'metadata': {},
'status': 'active'}
image = self._get_image()
pre_add_locations = image.locations[:]
image.locations.append(new_location)
pre_add_locations.append(new_location)
self.assertEqual(image.locations, pre_add_locations)
def test_insert_location(self):
new_location = {'url': 'file:///a/path', 'metadata': {},
'status': 'active'}
image = self._get_image()
pre_add_locations = image.locations[:]
image.locations.insert(0, new_location)
pre_add_locations.insert(0, new_location)
self.assertEqual(image.locations, pre_add_locations)
def test_extend_location(self):
new_location = {'url': 'file:///a/path', 'metadata': {},
'status': 'active'}
image = self._get_image()
pre_add_locations = image.locations[:]
image.locations.extend([new_location])
pre_add_locations.extend([new_location])
self.assertEqual(image.locations, pre_add_locations)
def test_iadd_location(self):
new_location = {'url': 'file:///a/path', 'metadata': {},
'status': 'active'}
image = self._get_image()
pre_add_locations = image.locations[:]
image.locations += [new_location]
pre_add_locations += [new_location]
self.assertEqual(image.locations, pre_add_locations)
def test_set_location(self):
new_location = {'url': 'file:///a/path', 'metadata': {},
'status': 'active'}
image = self._get_image()
image.locations = [new_location]
self.assertEqual(image.locations, [new_location])
def _make_image_with_quota(self, image_size=10, location_count=2):
quota = image_size * location_count
self.config(user_storage_quota=str(quota))
return self._get_image(image_size=image_size,
location_count=location_count)
def test_exceed_append_location(self):
image = self._make_image_with_quota()
self.assertRaises(exception.StorageQuotaFull,
image.locations.append,
{'url': 'file:///a/path', 'metadata': {},
'status': 'active'})
def test_exceed_insert_location(self):
image = self._make_image_with_quota()
self.assertRaises(exception.StorageQuotaFull,
image.locations.insert,
0,
{'url': 'file:///a/path', 'metadata': {},
'status': 'active'})
def test_exceed_extend_location(self):
image = self._make_image_with_quota()
self.assertRaises(exception.StorageQuotaFull,
image.locations.extend,
[{'url': 'file:///a/path', 'metadata': {},
'status': 'active'}])
def test_set_location_under(self):
image = self._make_image_with_quota(location_count=1)
image.locations = [{'url': 'file:///a/path', 'metadata': {},
'status': 'active'}]
def test_set_location_exceed(self):
image = self._make_image_with_quota(location_count=1)
try:
image.locations = [{'url': 'file:///a/path', 'metadata': {},
'status': 'active'},
{'url': 'file:///a/path2', 'metadata': {},
'status': 'active'}]
self.fail('Should have raised the quota exception')
except exception.StorageQuotaFull:
pass
def test_iadd_location_exceed(self):
image = self._make_image_with_quota(location_count=1)
try:
image.locations += [{'url': 'file:///a/path', 'metadata': {},
'status': 'active'}]
self.fail('Should have raised the quota exception')
except exception.StorageQuotaFull:
pass
def test_append_location_for_queued_image(self):
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = str(uuid.uuid4())
image = glance.quota.ImageProxy(base_image, context, db_api, store)
self.assertIsNone(image.size)
self.stubs.Set(store_api, 'get_size_from_backend',
unit_test_utils.fake_get_size_from_backend)
image.locations.append({'url': 'file:///fake.img.tar.gz',
'metadata': {}})
self.assertIn({'url': 'file:///fake.img.tar.gz', 'metadata': {}},
image.locations)
def test_insert_location_for_queued_image(self):
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = str(uuid.uuid4())
image = glance.quota.ImageProxy(base_image, context, db_api, store)
self.assertIsNone(image.size)
self.stubs.Set(store_api, 'get_size_from_backend',
unit_test_utils.fake_get_size_from_backend)
image.locations.insert(0,
{'url': 'file:///fake.img.tar.gz',
'metadata': {}})
self.assertIn({'url': 'file:///fake.img.tar.gz', 'metadata': {}},
image.locations)
def test_set_location_for_queued_image(self):
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = str(uuid.uuid4())
image = glance.quota.ImageProxy(base_image, context, db_api, store)
self.assertIsNone(image.size)
self.stubs.Set(store_api, 'get_size_from_backend',
unit_test_utils.fake_get_size_from_backend)
image.locations = [{'url': 'file:///fake.img.tar.gz', 'metadata': {}}]
self.assertEqual([{'url': 'file:///fake.img.tar.gz', 'metadata': {}}],
image.locations)
def test_iadd_location_for_queued_image(self):
context = FakeContext()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
base_image = FakeImage()
base_image.image_id = str(uuid.uuid4())
image = glance.quota.ImageProxy(base_image, context, db_api, store)
self.assertIsNone(image.size)
self.stubs.Set(store_api, 'get_size_from_backend',
unit_test_utils.fake_get_size_from_backend)
image.locations += [{'url': 'file:///fake.img.tar.gz', 'metadata': {}}]
self.assertIn({'url': 'file:///fake.img.tar.gz', 'metadata': {}},
image.locations)
class TestImagePropertyQuotas(test_utils.BaseTestCase):
def setUp(self):
super(TestImagePropertyQuotas, self).setUp()
self.base_image = FakeImage()
self.image = glance.quota.ImageProxy(self.base_image,
mock.Mock(),
mock.Mock(),
mock.Mock())
self.image_repo_mock = mock.Mock()
self.image_repo_mock.add.return_value = self.base_image
self.image_repo_mock.save.return_value = self.base_image
self.image_repo_proxy = glance.quota.ImageRepoProxy(
self.image_repo_mock,
mock.Mock(),
mock.Mock(),
mock.Mock())
def test_save_image_with_image_property(self):
self.config(image_property_quota=1)
self.image.extra_properties = {'foo': 'bar'}
self.image_repo_proxy.save(self.image)
self.image_repo_mock.save.assert_called_once_with(self.base_image,
from_state=None)
def test_save_image_too_many_image_properties(self):
self.config(image_property_quota=1)
self.image.extra_properties = {'foo': 'bar', 'foo2': 'bar2'}
exc = self.assertRaises(exception.ImagePropertyLimitExceeded,
self.image_repo_proxy.save, self.image)
self.assertIn("Attempted: 2, Maximum: 1",
encodeutils.exception_to_unicode(exc))
def test_save_image_unlimited_image_properties(self):
self.config(image_property_quota=-1)
self.image.extra_properties = {'foo': 'bar'}
self.image_repo_proxy.save(self.image)
self.image_repo_mock.save.assert_called_once_with(self.base_image,
from_state=None)
def test_add_image_with_image_property(self):
self.config(image_property_quota=1)
self.image.extra_properties = {'foo': 'bar'}
self.image_repo_proxy.add(self.image)
self.image_repo_mock.add.assert_called_once_with(self.base_image)
def test_add_image_too_many_image_properties(self):
self.config(image_property_quota=1)
self.image.extra_properties = {'foo': 'bar', 'foo2': 'bar2'}
exc = self.assertRaises(exception.ImagePropertyLimitExceeded,
self.image_repo_proxy.add, self.image)
self.assertIn("Attempted: 2, Maximum: 1",
encodeutils.exception_to_unicode(exc))
def test_add_image_unlimited_image_properties(self):
self.config(image_property_quota=-1)
self.image.extra_properties = {'foo': 'bar'}
self.image_repo_proxy.add(self.image)
self.image_repo_mock.add.assert_called_once_with(self.base_image)
def _quota_exceed_setup(self):
self.config(image_property_quota=2)
self.base_image.extra_properties = {'foo': 'bar', 'spam': 'ham'}
self.image = glance.quota.ImageProxy(self.base_image,
mock.Mock(),
mock.Mock(),
mock.Mock())
def test_modify_image_properties_when_quota_exceeded(self):
self._quota_exceed_setup()
self.config(image_property_quota=1)
self.image.extra_properties = {'foo': 'frob', 'spam': 'eggs'}
self.image_repo_proxy.save(self.image)
self.image_repo_mock.save.assert_called_once_with(self.base_image,
from_state=None)
self.assertEqual('frob', self.base_image.extra_properties['foo'])
self.assertEqual('eggs', self.base_image.extra_properties['spam'])
def test_delete_image_properties_when_quota_exceeded(self):
self._quota_exceed_setup()
self.config(image_property_quota=1)
del self.image.extra_properties['foo']
self.image_repo_proxy.save(self.image)
self.image_repo_mock.save.assert_called_once_with(self.base_image,
from_state=None)
self.assertNotIn('foo', self.base_image.extra_properties)
self.assertEqual('ham', self.base_image.extra_properties['spam'])
def test_invalid_quota_config_parameter(self):
self.config(user_storage_quota='foo')
location = {"url": "file:///fake.img.tar.gz", "metadata": {}}
self.assertRaises(exception.InvalidOptionValue,
self.image.locations.append, location)
def test_exceed_quota_during_patch_operation(self):
self._quota_exceed_setup()
self.image.extra_properties['frob'] = 'baz'
self.image.extra_properties['lorem'] = 'ipsum'
self.assertEqual('bar', self.base_image.extra_properties['foo'])
self.assertEqual('ham', self.base_image.extra_properties['spam'])
self.assertEqual('baz', self.base_image.extra_properties['frob'])
self.assertEqual('ipsum', self.base_image.extra_properties['lorem'])
del self.image.extra_properties['frob']
del self.image.extra_properties['lorem']
self.image_repo_proxy.save(self.image)
call_args = mock.call(self.base_image, from_state=None)
self.assertEqual(call_args, self.image_repo_mock.save.call_args)
self.assertEqual('bar', self.base_image.extra_properties['foo'])
self.assertEqual('ham', self.base_image.extra_properties['spam'])
self.assertNotIn('frob', self.base_image.extra_properties)
self.assertNotIn('lorem', self.base_image.extra_properties)
def test_quota_exceeded_after_delete_image_properties(self):
self.config(image_property_quota=3)
self.base_image.extra_properties = {'foo': 'bar',
'spam': 'ham',
'frob': 'baz'}
self.image = glance.quota.ImageProxy(self.base_image,
mock.Mock(),
mock.Mock(),
mock.Mock())
self.config(image_property_quota=1)
del self.image.extra_properties['foo']
self.image_repo_proxy.save(self.image)
self.image_repo_mock.save.assert_called_once_with(self.base_image,
from_state=None)
self.assertNotIn('foo', self.base_image.extra_properties)
self.assertEqual('ham', self.base_image.extra_properties['spam'])
self.assertEqual('baz', self.base_image.extra_properties['frob'])
class TestImageTagQuotas(test_utils.BaseTestCase):
def setUp(self):
super(TestImageTagQuotas, self).setUp()
self.base_image = mock.Mock()
self.base_image.tags = set([])
self.base_image.extra_properties = {}
self.image = glance.quota.ImageProxy(self.base_image,
mock.Mock(),
mock.Mock(),
mock.Mock())
self.image_repo_mock = mock.Mock()
self.image_repo_proxy = glance.quota.ImageRepoProxy(
self.image_repo_mock,
mock.Mock(),
mock.Mock(),
mock.Mock())
def test_replace_image_tag(self):
self.config(image_tag_quota=1)
self.image.tags = ['foo']
self.assertEqual(1, len(self.image.tags))
def test_replace_too_many_image_tags(self):
self.config(image_tag_quota=0)
exc = self.assertRaises(exception.ImageTagLimitExceeded,
setattr, self.image, 'tags', ['foo', 'bar'])
self.assertIn('Attempted: 2, Maximum: 0',
encodeutils.exception_to_unicode(exc))
self.assertEqual(0, len(self.image.tags))
def test_replace_unlimited_image_tags(self):
self.config(image_tag_quota=-1)
self.image.tags = ['foo']
self.assertEqual(1, len(self.image.tags))
def test_add_image_tag(self):
self.config(image_tag_quota=1)
self.image.tags.add('foo')
self.assertEqual(1, len(self.image.tags))
def test_add_too_many_image_tags(self):
self.config(image_tag_quota=1)
self.image.tags.add('foo')
exc = self.assertRaises(exception.ImageTagLimitExceeded,
self.image.tags.add, 'bar')
self.assertIn('Attempted: 2, Maximum: 1',
encodeutils.exception_to_unicode(exc))
def test_add_unlimited_image_tags(self):
self.config(image_tag_quota=-1)
self.image.tags.add('foo')
self.assertEqual(1, len(self.image.tags))
def test_remove_image_tag_while_over_quota(self):
self.config(image_tag_quota=1)
self.image.tags.add('foo')
self.assertEqual(1, len(self.image.tags))
self.config(image_tag_quota=0)
self.image.tags.remove('foo')
self.assertEqual(0, len(self.image.tags))
class TestQuotaImageTagsProxy(test_utils.BaseTestCase):
def setUp(self):
super(TestQuotaImageTagsProxy, self).setUp()
def test_add(self):
proxy = glance.quota.QuotaImageTagsProxy(set([]))
proxy.add('foo')
self.assertIn('foo', proxy)
def test_add_too_many_tags(self):
self.config(image_tag_quota=0)
proxy = glance.quota.QuotaImageTagsProxy(set([]))
exc = self.assertRaises(exception.ImageTagLimitExceeded,
proxy.add, 'bar')
self.assertIn('Attempted: 1, Maximum: 0',
encodeutils.exception_to_unicode(exc))
def test_equals(self):
proxy = glance.quota.QuotaImageTagsProxy(set([]))
self.assertEqual(set([]), proxy)
def test_not_equals(self):
proxy = glance.quota.QuotaImageTagsProxy(set([]))
self.assertNotEqual('foo', proxy)
def test_contains(self):
proxy = glance.quota.QuotaImageTagsProxy(set(['foo']))
self.assertIn('foo', proxy)
def test_len(self):
proxy = glance.quota.QuotaImageTagsProxy(set(['foo',
'bar',
'baz',
'niz']))
self.assertEqual(4, len(proxy))
def test_iter(self):
items = set(['foo', 'bar', 'baz', 'niz'])
proxy = glance.quota.QuotaImageTagsProxy(items.copy())
self.assertEqual(4, len(items))
for item in proxy:
items.remove(item)
self.assertEqual(0, len(items))
class TestImageMemberQuotas(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMemberQuotas, self).setUp()
db_api = unit_test_utils.FakeDB()
store_api = unit_test_utils.FakeStoreAPI()
store = unit_test_utils.FakeStoreUtils(store_api)
context = FakeContext()
self.image = mock.Mock()
self.base_image_member_factory = mock.Mock()
self.image_member_factory = glance.quota.ImageMemberFactoryProxy(
self.base_image_member_factory, context,
db_api, store)
def test_new_image_member(self):
self.config(image_member_quota=1)
self.image_member_factory.new_image_member(self.image,
'fake_id')
nim = self.base_image_member_factory.new_image_member
nim.assert_called_once_with(self.image, 'fake_id')
def test_new_image_member_unlimited_members(self):
self.config(image_member_quota=-1)
self.image_member_factory.new_image_member(self.image,
'fake_id')
nim = self.base_image_member_factory.new_image_member
nim.assert_called_once_with(self.image, 'fake_id')
def test_new_image_member_too_many_members(self):
self.config(image_member_quota=0)
self.assertRaises(exception.ImageMemberLimitExceeded,
self.image_member_factory.new_image_member,
self.image, 'fake_id')
class TestImageLocationQuotas(test_utils.BaseTestCase):
def setUp(self):
super(TestImageLocationQuotas, self).setUp()
self.base_image = mock.Mock()
self.base_image.locations = []
self.base_image.size = 1
self.base_image.extra_properties = {}
self.image = glance.quota.ImageProxy(self.base_image,
mock.Mock(),
mock.Mock(),
mock.Mock())
self.image_repo_mock = mock.Mock()
self.image_repo_proxy = glance.quota.ImageRepoProxy(
self.image_repo_mock,
mock.Mock(),
mock.Mock(),
mock.Mock())
def test_replace_image_location(self):
self.config(image_location_quota=1)
self.image.locations = [{"url": "file:///fake.img.tar.gz",
"metadata": {}
}]
self.assertEqual(1, len(self.image.locations))
def test_replace_too_many_image_locations(self):
self.config(image_location_quota=1)
self.image.locations = [{"url": "file:///fake.img.tar.gz",
"metadata": {}}
]
locations = [
{"url": "file:///fake1.img.tar.gz", "metadata": {}},
{"url": "file:///fake2.img.tar.gz", "metadata": {}},
{"url": "file:///fake3.img.tar.gz", "metadata": {}}
]
exc = self.assertRaises(exception.ImageLocationLimitExceeded,
setattr, self.image, 'locations', locations)
self.assertIn('Attempted: 3, Maximum: 1',
encodeutils.exception_to_unicode(exc))
self.assertEqual(1, len(self.image.locations))
def test_replace_unlimited_image_locations(self):
self.config(image_location_quota=-1)
self.image.locations = [{"url": "file:///fake.img.tar.gz",
"metadata": {}}
]
self.assertEqual(1, len(self.image.locations))
def test_add_image_location(self):
self.config(image_location_quota=1)
location = {"url": "file:///fake.img.tar.gz", "metadata": {}}
self.image.locations.append(location)
self.assertEqual(1, len(self.image.locations))
def test_add_too_many_image_locations(self):
self.config(image_location_quota=1)
location1 = {"url": "file:///fake1.img.tar.gz", "metadata": {}}
self.image.locations.append(location1)
location2 = {"url": "file:///fake2.img.tar.gz", "metadata": {}}
exc = self.assertRaises(exception.ImageLocationLimitExceeded,
self.image.locations.append, location2)
self.assertIn('Attempted: 2, Maximum: 1',
encodeutils.exception_to_unicode(exc))
def test_add_unlimited_image_locations(self):
self.config(image_location_quota=-1)
location1 = {"url": "file:///fake1.img.tar.gz", "metadata": {}}
self.image.locations.append(location1)
self.assertEqual(1, len(self.image.locations))
def test_remove_image_location_while_over_quota(self):
self.config(image_location_quota=1)
location1 = {"url": "file:///fake1.img.tar.gz", "metadata": {}}
self.image.locations.append(location1)
self.assertEqual(1, len(self.image.locations))
self.config(image_location_quota=0)
self.image.locations.remove(location1)
self.assertEqual(0, len(self.image.locations))
glance-16.0.1/glance/tests/unit/v2/ 0000775 0001750 0001750 00000000000 13267672475 016776 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/v2/test_image_tags_resource.py 0000666 0001750 0001750 00000010152 13267672245 024412 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from six.moves import http_client as http
import webob
import glance.api.v2.image_tags
from glance.common import exception
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
import glance.tests.unit.v2.test_image_data_resource as image_data_tests
import glance.tests.utils as test_utils
class TestImageTagsController(base.IsolatedUnitTest):
def setUp(self):
super(TestImageTagsController, self).setUp()
self.db = unit_test_utils.FakeDB()
self.controller = glance.api.v2.image_tags.Controller(self.db)
def test_create_tag(self):
request = unit_test_utils.get_fake_request()
self.controller.update(request, unit_test_utils.UUID1, 'dink')
context = request.context
tags = self.db.image_tag_get_all(context, unit_test_utils.UUID1)
self.assertEqual(1, len([tag for tag in tags if tag == 'dink']))
def test_create_too_many_tags(self):
self.config(image_tag_quota=0)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
request, unit_test_utils.UUID1, 'dink')
def test_create_duplicate_tag_ignored(self):
request = unit_test_utils.get_fake_request()
self.controller.update(request, unit_test_utils.UUID1, 'dink')
self.controller.update(request, unit_test_utils.UUID1, 'dink')
context = request.context
tags = self.db.image_tag_get_all(context, unit_test_utils.UUID1)
self.assertEqual(1, len([tag for tag in tags if tag == 'dink']))
def test_update_tag_of_non_existing_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
request, "abcd", "dink")
def test_delete_tag_forbidden(self):
def fake_get(self):
raise exception.Forbidden()
image_repo = image_data_tests.FakeImageRepo()
image_repo.get = fake_get
def get_fake_repo(self):
return image_repo
self.controller.gateway.get_repo = get_fake_repo
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, unit_test_utils.UUID1, "ping")
def test_delete_tag(self):
request = unit_test_utils.get_fake_request()
self.controller.delete(request, unit_test_utils.UUID1, 'ping')
def test_delete_tag_not_found(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
request, unit_test_utils.UUID1, 'what')
def test_delete_tag_of_non_existing_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
request, "abcd", "dink")
class TestImagesSerializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializer, self).setUp()
self.serializer = glance.api.v2.image_tags.ResponseSerializer()
def test_create_tag(self):
response = webob.Response()
self.serializer.update(response, None)
self.assertEqual(http.NO_CONTENT, response.status_int)
def test_delete_tag(self):
response = webob.Response()
self.serializer.delete(response, None)
self.assertEqual(http.NO_CONTENT, response.status_int)
glance-16.0.1/glance/tests/unit/v2/test_images_resource.py 0000666 0001750 0001750 00000550004 13267672254 023564 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import eventlet
import uuid
import glance_store as store
import mock
from oslo_serialization import jsonutils
import six
from six.moves import http_client as http
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
import testtools
import webob
import glance.api.v2.image_actions
import glance.api.v2.images
from glance.common import exception
from glance import domain
import glance.schema
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
ISOTIME = '2012-05-16T15:27:36Z'
BASE_URI = unit_test_utils.BASE_URI
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
UUID4 = '6bbe7cc2-eae7-4c0f-b50d-a7160b0c6a86'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
CHKSUM = '93264c3edf5972c9f1cb309543d38a5c'
CHKSUM1 = '43254c3edf6972c9f1cb309543d38a8c'
def _db_fixture(id, **kwargs):
obj = {
'id': id,
'name': None,
'visibility': 'shared',
'properties': {},
'checksum': None,
'owner': None,
'status': 'queued',
'tags': [],
'size': None,
'virtual_size': None,
'locations': [],
'protected': False,
'disk_format': None,
'container_format': None,
'deleted': False,
'min_ram': None,
'min_disk': None,
}
obj.update(kwargs)
return obj
def _domain_fixture(id, **kwargs):
properties = {
'image_id': id,
'name': None,
'visibility': 'private',
'checksum': None,
'owner': None,
'status': 'queued',
'size': None,
'virtual_size': None,
'locations': [],
'protected': False,
'disk_format': None,
'container_format': None,
'min_ram': None,
'min_disk': None,
'tags': [],
}
properties.update(kwargs)
return glance.domain.Image(**properties)
def _db_image_member_fixture(image_id, member_id, **kwargs):
obj = {
'image_id': image_id,
'member': member_id,
}
obj.update(kwargs)
return obj
class TestImagesController(base.IsolatedUnitTest):
def setUp(self):
super(TestImagesController, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.policy = unit_test_utils.FakePolicyEnforcer()
self.notifier = unit_test_utils.FakeNotifier()
self.store = unit_test_utils.FakeStoreAPI()
for i in range(1, 4):
self.store.data['%s/fake_location_%i' % (BASE_URI, i)] = ('Z', 1)
self.store_utils = unit_test_utils.FakeStoreUtils(self.store)
self._create_images()
self._create_image_members()
self.controller = glance.api.v2.images.ImagesController(self.db,
self.policy,
self.notifier,
self.store)
self.action_controller = (glance.api.v2.image_actions.
ImageActionsController(self.db,
self.policy,
self.notifier,
self.store))
self.controller.gateway.store_utils = self.store_utils
store.create_stores()
def _create_images(self):
self.images = [
_db_fixture(UUID1, owner=TENANT1, checksum=CHKSUM,
name='1', size=256, virtual_size=1024,
visibility='public',
locations=[{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}, 'status': 'active'}],
disk_format='raw',
container_format='bare',
status='active'),
_db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
name='2', size=512, virtual_size=2048,
visibility='public',
disk_format='raw',
container_format='bare',
status='active',
tags=['redhat', '64bit', 'power'],
properties={'hypervisor_type': 'kvm', 'foo': 'bar',
'bar': 'foo'}),
_db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
name='3', size=512, virtual_size=2048,
visibility='public', tags=['windows', '64bit', 'x86']),
_db_fixture(UUID4, owner=TENANT4, name='4',
size=1024, virtual_size=3072),
]
[self.db.image_create(None, image) for image in self.images]
self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
def _create_image_members(self):
self.image_members = [
_db_image_member_fixture(UUID4, TENANT2),
_db_image_member_fixture(UUID4, TENANT3,
status='accepted'),
]
[self.db.image_member_create(None, image_member)
for image_member in self.image_members]
def test_index(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request)
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID3])
self.assertEqual(expected, actual)
def test_index_member_status_accepted(self):
self.config(limit_param_default=5, api_limit_max=5)
request = unit_test_utils.get_fake_request(tenant=TENANT2)
output = self.controller.index(request)
self.assertEqual(3, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID1, UUID2, UUID3])
# can see only the public image
self.assertEqual(expected, actual)
request = unit_test_utils.get_fake_request(tenant=TENANT3)
output = self.controller.index(request)
self.assertEqual(4, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID1, UUID2, UUID3, UUID4])
self.assertEqual(expected, actual)
def test_index_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.controller.index(request)
self.assertEqual(4, len(output['images']))
def test_index_admin_deleted_images_hidden(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.controller.delete(request, UUID1)
output = self.controller.index(request)
self.assertEqual(3, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2, UUID3, UUID4])
self.assertEqual(expected, actual)
def test_index_return_parameters(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, marker=UUID3, limit=1,
sort_key=['created_at'],
sort_dir=['desc'])
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2])
self.assertEqual(actual, expected)
self.assertEqual(UUID2, output['next_marker'])
def test_index_next_marker(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, marker=UUID3, limit=2)
self.assertEqual(2, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2, UUID1])
self.assertEqual(expected, actual)
self.assertEqual(UUID1, output['next_marker'])
def test_index_no_next_marker(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, marker=UUID1, limit=2)
self.assertEqual(0, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([])
self.assertEqual(expected, actual)
self.assertNotIn('next_marker', output)
def test_index_with_id_filter(self):
request = unit_test_utils.get_fake_request('/images?id=%s' % UUID1)
output = self.controller.index(request, filters={'id': UUID1})
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID1])
self.assertEqual(expected, actual)
def test_index_with_checksum_filter_single_image(self):
req = unit_test_utils.get_fake_request('/images?checksum=%s' % CHKSUM)
output = self.controller.index(req, filters={'checksum': CHKSUM})
self.assertEqual(1, len(output['images']))
actual = list([image.image_id for image in output['images']])
expected = [UUID1]
self.assertEqual(expected, actual)
def test_index_with_checksum_filter_multiple_images(self):
req = unit_test_utils.get_fake_request('/images?checksum=%s' % CHKSUM1)
output = self.controller.index(req, filters={'checksum': CHKSUM1})
self.assertEqual(2, len(output['images']))
actual = list([image.image_id for image in output['images']])
expected = [UUID3, UUID2]
self.assertEqual(expected, actual)
def test_index_with_non_existent_checksum(self):
req = unit_test_utils.get_fake_request('/images?checksum=236231827')
output = self.controller.index(req, filters={'checksum': '236231827'})
self.assertEqual(0, len(output['images']))
def test_index_size_max_filter(self):
request = unit_test_utils.get_fake_request('/images?size_max=512')
output = self.controller.index(request, filters={'size_max': 512})
self.assertEqual(3, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID1, UUID2, UUID3])
self.assertEqual(expected, actual)
def test_index_size_min_filter(self):
request = unit_test_utils.get_fake_request('/images?size_min=512')
output = self.controller.index(request, filters={'size_min': 512})
self.assertEqual(2, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2, UUID3])
self.assertEqual(expected, actual)
def test_index_size_range_filter(self):
path = '/images?size_min=512&size_max=512'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'size_min': 512,
'size_max': 512})
self.assertEqual(2, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2, UUID3])
self.assertEqual(expected, actual)
def test_index_virtual_size_max_filter(self):
ref = '/images?virtual_size_max=2048'
request = unit_test_utils.get_fake_request(ref)
output = self.controller.index(request,
filters={'virtual_size_max': 2048})
self.assertEqual(3, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID1, UUID2, UUID3])
self.assertEqual(expected, actual)
def test_index_virtual_size_min_filter(self):
ref = '/images?virtual_size_min=2048'
request = unit_test_utils.get_fake_request(ref)
output = self.controller.index(request,
filters={'virtual_size_min': 2048})
self.assertEqual(2, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2, UUID3])
self.assertEqual(expected, actual)
def test_index_virtual_size_range_filter(self):
path = '/images?virtual_size_min=512&virtual_size_max=2048'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'virtual_size_min': 2048,
'virtual_size_max': 2048})
self.assertEqual(2, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID2, UUID3])
self.assertEqual(expected, actual)
def test_index_with_invalid_max_range_filter_value(self):
request = unit_test_utils.get_fake_request('/images?size_max=blah')
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index,
request,
filters={'size_max': 'blah'})
def test_index_with_filters_return_many(self):
path = '/images?status=queued'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, filters={'status': 'queued'})
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID3])
self.assertEqual(expected, actual)
def test_index_with_nonexistent_name_filter(self):
request = unit_test_utils.get_fake_request('/images?name=%s' % 'blah')
images = self.controller.index(request,
filters={'name': 'blah'})['images']
self.assertEqual(0, len(images))
def test_index_with_non_default_is_public_filter(self):
private_uuid = str(uuid.uuid4())
new_image = _db_fixture(private_uuid,
visibility='private',
owner=TENANT3)
self.db.image_create(None, new_image)
path = '/images?visibility=private'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request,
filters={'visibility': 'private'})
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([private_uuid])
self.assertEqual(expected, actual)
path = '/images?visibility=shared'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request,
filters={'visibility': 'shared'})
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID4])
self.assertEqual(expected, actual)
def test_index_with_many_filters(self):
url = '/images?status=queued&name=3'
request = unit_test_utils.get_fake_request(url)
output = self.controller.index(request,
filters={
'status': 'queued',
'name': '3',
})
self.assertEqual(1, len(output['images']))
actual = set([image.image_id for image in output['images']])
expected = set([UUID3])
self.assertEqual(expected, actual)
def test_index_with_marker(self):
self.config(limit_param_default=1, api_limit_max=3)
path = '/images'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, marker=UUID3)
actual = set([image.image_id for image in output['images']])
self.assertEqual(1, len(actual))
self.assertIn(UUID2, actual)
def test_index_with_limit(self):
path = '/images'
limit = 2
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, limit=limit)
actual = set([image.image_id for image in output['images']])
self.assertEqual(limit, len(actual))
self.assertIn(UUID3, actual)
self.assertIn(UUID2, actual)
def test_index_greater_than_limit_max(self):
self.config(limit_param_default=1, api_limit_max=3)
path = '/images'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, limit=4)
actual = set([image.image_id for image in output['images']])
self.assertEqual(3, len(actual))
self.assertNotIn(output['next_marker'], output)
def test_index_default_limit(self):
self.config(limit_param_default=1, api_limit_max=3)
path = '/images'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request)
actual = set([image.image_id for image in output['images']])
self.assertEqual(1, len(actual))
def test_index_with_sort_dir(self):
path = '/images'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, sort_dir=['asc'], limit=3)
actual = [image.image_id for image in output['images']]
self.assertEqual(3, len(actual))
self.assertEqual(UUID1, actual[0])
self.assertEqual(UUID2, actual[1])
self.assertEqual(UUID3, actual[2])
def test_index_with_sort_key(self):
path = '/images'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, sort_key=['created_at'],
limit=3)
actual = [image.image_id for image in output['images']]
self.assertEqual(3, len(actual))
self.assertEqual(UUID3, actual[0])
self.assertEqual(UUID2, actual[1])
self.assertEqual(UUID1, actual[2])
def test_index_with_multiple_sort_keys(self):
path = '/images'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
sort_key=['created_at', 'name'],
limit=3)
actual = [image.image_id for image in output['images']]
self.assertEqual(3, len(actual))
self.assertEqual(UUID3, actual[0])
self.assertEqual(UUID2, actual[1])
self.assertEqual(UUID1, actual[2])
def test_index_with_marker_not_found(self):
fake_uuid = str(uuid.uuid4())
path = '/images'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, request, marker=fake_uuid)
def test_index_invalid_sort_key(self):
path = '/images'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, request, sort_key=['foo'])
def test_index_zero_images(self):
self.db.reset()
request = unit_test_utils.get_fake_request()
output = self.controller.index(request)
self.assertEqual([], output['images'])
def test_index_with_tags(self):
path = '/images?tag=64bit'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request, filters={'tags': ['64bit']})
actual = [image.tags for image in output['images']]
self.assertEqual(2, len(actual))
self.assertIn('64bit', actual[0])
self.assertIn('64bit', actual[1])
def test_index_with_multi_tags(self):
path = '/images?tag=power&tag=64bit'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'tags': ['power', '64bit']})
actual = [image.tags for image in output['images']]
self.assertEqual(1, len(actual))
self.assertIn('64bit', actual[0])
self.assertIn('power', actual[0])
def test_index_with_multi_tags_and_nonexistent(self):
path = '/images?tag=power&tag=fake'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'tags': ['power', 'fake']})
actual = [image.tags for image in output['images']]
self.assertEqual(0, len(actual))
def test_index_with_tags_and_properties(self):
path = '/images?tag=64bit&hypervisor_type=kvm'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'tags': ['64bit'],
'hypervisor_type': 'kvm'})
tags = [image.tags for image in output['images']]
properties = [image.extra_properties for image in output['images']]
self.assertEqual(len(tags), len(properties))
self.assertIn('64bit', tags[0])
self.assertEqual('kvm', properties[0]['hypervisor_type'])
def test_index_with_multiple_properties(self):
path = '/images?foo=bar&hypervisor_type=kvm'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'foo': 'bar',
'hypervisor_type': 'kvm'})
properties = [image.extra_properties for image in output['images']]
self.assertEqual('kvm', properties[0]['hypervisor_type'])
self.assertEqual('bar', properties[0]['foo'])
def test_index_with_core_and_extra_property(self):
path = '/images?disk_format=raw&foo=bar'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'foo': 'bar',
'disk_format': 'raw'})
properties = [image.extra_properties for image in output['images']]
self.assertEqual(1, len(output['images']))
self.assertEqual('raw', output['images'][0].disk_format)
self.assertEqual('bar', properties[0]['foo'])
def test_index_with_nonexistent_properties(self):
path = '/images?abc=xyz&pudding=banana'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'abc': 'xyz',
'pudding': 'banana'})
self.assertEqual(0, len(output['images']))
def test_index_with_non_existent_tags(self):
path = '/images?tag=fake'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request,
filters={'tags': ['fake']})
actual = [image.tags for image in output['images']]
self.assertEqual(0, len(actual))
def test_show(self):
request = unit_test_utils.get_fake_request()
output = self.controller.show(request, image_id=UUID2)
self.assertEqual(UUID2, output.image_id)
self.assertEqual('2', output.name)
def test_show_deleted_properties(self):
"""Ensure that the api filters out deleted image properties."""
# get the image properties into the odd state
image = {
'id': str(uuid.uuid4()),
'status': 'active',
'properties': {'poo': 'bear'},
}
self.db.image_create(None, image)
self.db.image_update(None, image['id'],
{'properties': {'yin': 'yang'}},
purge_props=True)
request = unit_test_utils.get_fake_request()
output = self.controller.show(request, image['id'])
self.assertEqual('yang', output.extra_properties['yin'])
def test_show_non_existent(self):
request = unit_test_utils.get_fake_request()
image_id = str(uuid.uuid4())
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, request, image_id)
def test_show_deleted_image_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.controller.delete(request, UUID1)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, request, UUID1)
def test_show_not_allowed(self):
request = unit_test_utils.get_fake_request()
self.assertEqual(TENANT1, request.context.tenant)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, request, UUID4)
def test_image_import_raises_conflict(self):
request = unit_test_utils.get_fake_request()
# NOTE(abhishekk): Due to
# https://bugs.launchpad.net/glance/+bug/1712463 taskflow is not
# executing. Once it is fixed instead of mocking spawn_n method
# we should mock execute method of _ImportToStore task.
with mock.patch.object(eventlet.GreenPool, 'spawn_n',
side_effect=exception.Conflict):
self.assertRaises(webob.exc.HTTPConflict,
self.controller.import_image, request, UUID4,
{'method': {'name': 'glance-direct'}})
def test_image_import_raises_conflict_for_invalid_status_change(self):
request = unit_test_utils.get_fake_request()
# NOTE(abhishekk): Due to
# https://bugs.launchpad.net/glance/+bug/1712463 taskflow is not
# executing. Once it is fixed instead of mocking spawn_n method
# we should mock execute method of _ImportToStore task.
with mock.patch.object(
eventlet.GreenPool, 'spawn_n',
side_effect=exception.InvalidImageStatusTransition):
self.assertRaises(webob.exc.HTTPConflict,
self.controller.import_image, request, UUID4,
{'method': {'name': 'glance-direct'}})
def test_image_import_raises_bad_request(self):
request = unit_test_utils.get_fake_request()
# NOTE(abhishekk): Due to
# https://bugs.launchpad.net/glance/+bug/1712463 taskflow is not
# executing. Once it is fixed instead of mocking spawn_n method
# we should mock execute method of _ImportToStore task.
with mock.patch.object(eventlet.GreenPool, 'spawn_n',
side_effect=ValueError):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.import_image, request, UUID4,
{'method': {'name': 'glance-direct'}})
def test_create(self):
request = unit_test_utils.get_fake_request()
image = {'name': 'image-1'}
output = self.controller.create(request, image=image,
extra_properties={},
tags=[])
self.assertEqual('image-1', output.name)
self.assertEqual({}, output.extra_properties)
self.assertEqual(set([]), output.tags)
self.assertEqual('shared', output.visibility)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.create', output_log['event_type'])
self.assertEqual('image-1', output_log['payload']['name'])
def test_create_disabled_notification(self):
self.config(disabled_notifications=["image.create"])
request = unit_test_utils.get_fake_request()
image = {'name': 'image-1'}
output = self.controller.create(request, image=image,
extra_properties={},
tags=[])
self.assertEqual('image-1', output.name)
self.assertEqual({}, output.extra_properties)
self.assertEqual(set([]), output.tags)
self.assertEqual('shared', output.visibility)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_create_with_properties(self):
request = unit_test_utils.get_fake_request()
image_properties = {'foo': 'bar'}
image = {'name': 'image-1'}
output = self.controller.create(request, image=image,
extra_properties=image_properties,
tags=[])
self.assertEqual('image-1', output.name)
self.assertEqual(image_properties, output.extra_properties)
self.assertEqual(set([]), output.tags)
self.assertEqual('shared', output.visibility)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.create', output_log['event_type'])
self.assertEqual('image-1', output_log['payload']['name'])
def test_create_with_too_many_properties(self):
self.config(image_property_quota=1)
request = unit_test_utils.get_fake_request()
image_properties = {'foo': 'bar', 'foo2': 'bar'}
image = {'name': 'image-1'}
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, request,
image=image,
extra_properties=image_properties,
tags=[])
def test_create_with_bad_min_disk_size(self):
request = unit_test_utils.get_fake_request()
image = {'min_disk': -42, 'name': 'image-1'}
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, request,
image=image,
extra_properties={},
tags=[])
def test_create_with_bad_min_ram_size(self):
request = unit_test_utils.get_fake_request()
image = {'min_ram': -42, 'name': 'image-1'}
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, request,
image=image,
extra_properties={},
tags=[])
def test_create_public_image_as_admin(self):
request = unit_test_utils.get_fake_request()
image = {'name': 'image-1', 'visibility': 'public'}
output = self.controller.create(request, image=image,
extra_properties={}, tags=[])
self.assertEqual('public', output.visibility)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.create', output_log['event_type'])
self.assertEqual(output.image_id, output_log['payload']['id'])
def test_create_dup_id(self):
request = unit_test_utils.get_fake_request()
image = {'image_id': UUID4}
self.assertRaises(webob.exc.HTTPConflict,
self.controller.create,
request,
image=image,
extra_properties={},
tags=[])
def test_create_duplicate_tags(self):
request = unit_test_utils.get_fake_request()
tags = ['ping', 'ping']
output = self.controller.create(request, image={},
extra_properties={}, tags=tags)
self.assertEqual(set(['ping']), output.tags)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.create', output_log['event_type'])
self.assertEqual(output.image_id, output_log['payload']['id'])
def test_create_with_too_many_tags(self):
self.config(image_tag_quota=1)
request = unit_test_utils.get_fake_request()
tags = ['ping', 'pong']
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create,
request, image={}, extra_properties={},
tags=tags)
def test_create_with_owner_non_admin(self):
request = unit_test_utils.get_fake_request()
request.context.is_admin = False
image = {'owner': '12345'}
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.create,
request, image=image, extra_properties={},
tags=[])
request = unit_test_utils.get_fake_request()
request.context.is_admin = False
image = {'owner': TENANT1}
output = self.controller.create(request, image=image,
extra_properties={}, tags=[])
self.assertEqual(TENANT1, output.owner)
def test_create_with_owner_admin(self):
request = unit_test_utils.get_fake_request()
request.context.is_admin = True
image = {'owner': '12345'}
output = self.controller.create(request, image=image,
extra_properties={}, tags=[])
self.assertEqual('12345', output.owner)
def test_create_with_duplicate_location(self):
request = unit_test_utils.get_fake_request()
location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
image = {'name': 'image-1', 'locations': [location, location]}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
request, image=image, extra_properties={},
tags=[])
def test_create_unexpected_property(self):
request = unit_test_utils.get_fake_request()
image_properties = {'unexpected': 'unexpected'}
image = {'name': 'image-1'}
with mock.patch.object(domain.ImageFactory, 'new_image',
side_effect=TypeError):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, request, image=image,
extra_properties=image_properties, tags=[])
def test_create_reserved_property(self):
request = unit_test_utils.get_fake_request()
image_properties = {'reserved': 'reserved'}
image = {'name': 'image-1'}
with mock.patch.object(domain.ImageFactory, 'new_image',
side_effect=exception.ReservedProperty(
property='reserved')):
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.create, request, image=image,
extra_properties=image_properties, tags=[])
def test_create_readonly_property(self):
request = unit_test_utils.get_fake_request()
image_properties = {'readonly': 'readonly'}
image = {'name': 'image-1'}
with mock.patch.object(domain.ImageFactory, 'new_image',
side_effect=exception.ReadonlyProperty(
property='readonly')):
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.create, request, image=image,
extra_properties=image_properties, tags=[])
def test_update_no_changes(self):
request = unit_test_utils.get_fake_request()
output = self.controller.update(request, UUID1, changes=[])
self.assertEqual(UUID1, output.image_id)
self.assertEqual(output.created_at, output.updated_at)
self.assertEqual(2, len(output.tags))
self.assertIn('ping', output.tags)
self.assertIn('pong', output.tags)
output_logs = self.notifier.get_logs()
# NOTE(markwash): don't send a notification if nothing is updated
self.assertEqual(0, len(output_logs))
def test_update_with_bad_min_disk(self):
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['min_disk'], 'value': -42}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes=changes)
def test_update_with_bad_min_ram(self):
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['min_ram'], 'value': -42}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes=changes)
def test_update_image_doesnt_exist(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
request, str(uuid.uuid4()), changes=[])
def test_update_deleted_image_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.controller.delete(request, UUID1)
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
request, UUID1, changes=[])
def test_update_with_too_many_properties(self):
self.config(show_multiple_locations=True)
self.config(user_storage_quota='1')
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
request, UUID1, changes=changes)
def test_update_replace_base_attribute(self):
self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
request = unit_test_utils.get_fake_request()
request.context.is_admin = True
changes = [{'op': 'replace', 'path': ['name'], 'value': 'fedora'},
{'op': 'replace', 'path': ['owner'], 'value': TENANT3}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual('fedora', output.name)
self.assertEqual(TENANT3, output.owner)
self.assertEqual({'foo': 'bar'}, output.extra_properties)
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_replace_onwer_non_admin(self):
request = unit_test_utils.get_fake_request()
request.context.is_admin = False
changes = [{'op': 'replace', 'path': ['owner'], 'value': TENANT3}]
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.update, request, UUID1, changes)
def test_update_replace_tags(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'replace', 'path': ['tags'], 'value': ['king', 'kong']},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(2, len(output.tags))
self.assertIn('king', output.tags)
self.assertIn('kong', output.tags)
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_replace_property(self):
request = unit_test_utils.get_fake_request()
properties = {'foo': 'bar', 'snitch': 'golden'}
self.db.image_update(None, UUID1, {'properties': properties})
output = self.controller.show(request, UUID1)
self.assertEqual('bar', output.extra_properties['foo'])
self.assertEqual('golden', output.extra_properties['snitch'])
changes = [
{'op': 'replace', 'path': ['foo'], 'value': 'baz'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual('baz', output.extra_properties['foo'])
self.assertEqual('golden', output.extra_properties['snitch'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_add_too_many_properties(self):
self.config(image_property_quota=1)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['foo'], 'value': 'baz'},
{'op': 'add', 'path': ['snitch'], 'value': 'golden'},
]
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update, request,
UUID1, changes)
def test_update_add_and_remove_too_many_properties(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['foo'], 'value': 'baz'},
{'op': 'add', 'path': ['snitch'], 'value': 'golden'},
]
self.controller.update(request, UUID1, changes)
self.config(image_property_quota=1)
# We must remove two properties to avoid being
# over the limit of 1 property
changes = [
{'op': 'remove', 'path': ['foo']},
{'op': 'add', 'path': ['fizz'], 'value': 'buzz'},
]
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update, request,
UUID1, changes)
def test_update_add_unlimited_properties(self):
self.config(image_property_quota=-1)
request = unit_test_utils.get_fake_request()
output = self.controller.show(request, UUID1)
changes = [{'op': 'add',
'path': ['foo'],
'value': 'bar'}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_format_properties(self):
statuses_for_immutability = ['active', 'saving', 'killed']
request = unit_test_utils.get_fake_request(is_admin=True)
for status in statuses_for_immutability:
image = {
'id': str(uuid.uuid4()),
'status': status,
'disk_format': 'ari',
'container_format': 'ari',
}
self.db.image_create(None, image)
changes = [
{'op': 'replace', 'path': ['disk_format'], 'value': 'ami'},
]
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.update,
request, image['id'], changes)
changes = [
{'op': 'replace',
'path': ['container_format'],
'value': 'ami'},
]
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.update,
request, image['id'], changes)
self.db.image_update(None, image['id'], {'status': 'queued'})
changes = [
{'op': 'replace', 'path': ['disk_format'], 'value': 'raw'},
{'op': 'replace', 'path': ['container_format'], 'value': 'bare'},
]
resp = self.controller.update(request, image['id'], changes)
self.assertEqual('raw', resp.disk_format)
self.assertEqual('bare', resp.container_format)
def test_update_remove_property_while_over_limit(self):
"""Ensure that image properties can be removed.
Image properties should be able to be removed as long as the image has
fewer than the limited number of image properties after the
transaction.
"""
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['foo'], 'value': 'baz'},
{'op': 'add', 'path': ['snitch'], 'value': 'golden'},
{'op': 'add', 'path': ['fizz'], 'value': 'buzz'},
]
self.controller.update(request, UUID1, changes)
self.config(image_property_quota=1)
# We must remove two properties to avoid being
# over the limit of 1 property
changes = [
{'op': 'remove', 'path': ['foo']},
{'op': 'remove', 'path': ['snitch']},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(1, len(output.extra_properties))
self.assertEqual('buzz', output.extra_properties['fizz'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_add_and_remove_property_under_limit(self):
"""Ensure that image properties can be removed.
Image properties should be able to be added and removed simultaneously
as long as the image has fewer than the limited number of image
properties after the transaction.
"""
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['foo'], 'value': 'baz'},
{'op': 'add', 'path': ['snitch'], 'value': 'golden'},
]
self.controller.update(request, UUID1, changes)
self.config(image_property_quota=1)
# We must remove two properties to avoid being
# over the limit of 1 property
changes = [
{'op': 'remove', 'path': ['foo']},
{'op': 'remove', 'path': ['snitch']},
{'op': 'add', 'path': ['fizz'], 'value': 'buzz'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(1, len(output.extra_properties))
self.assertEqual('buzz', output.extra_properties['fizz'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_replace_missing_property(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'replace', 'path': 'foo', 'value': 'baz'},
]
self.assertRaises(webob.exc.HTTPConflict,
self.controller.update, request, UUID1, changes)
def test_prop_protection_with_create_and_permitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
created_image = self.controller.create(request, image=image,
extra_properties={},
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'add', 'path': ['x_owner_foo'], 'value': 'bar'},
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('bar', output.extra_properties['x_owner_foo'])
def test_prop_protection_with_update_and_permitted_policy(self):
self.set_property_protections(use_policies=True)
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
request = unit_test_utils.get_fake_request(roles=['spl_role'])
image = {'name': 'image-1'}
extra_props = {'spl_creator_policy': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
self.assertEqual('bar',
created_image.extra_properties['spl_creator_policy'])
another_request = unit_test_utils.get_fake_request(roles=['spl_role'])
changes = [
{'op': 'replace', 'path': ['spl_creator_policy'], 'value': 'par'},
]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
another_request, created_image.image_id, changes)
another_request = unit_test_utils.get_fake_request(roles=['admin'])
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('par',
output.extra_properties['spl_creator_policy'])
def test_prop_protection_with_create_with_patch_and_policy(self):
self.set_property_protections(use_policies=True)
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
request = unit_test_utils.get_fake_request(roles=['spl_role', 'admin'])
image = {'name': 'image-1'}
extra_props = {'spl_default_policy': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
changes = [
{'op': 'add', 'path': ['spl_creator_policy'], 'value': 'bar'},
]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
another_request, created_image.image_id, changes)
another_request = unit_test_utils.get_fake_request(roles=['spl_role'])
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('bar',
output.extra_properties['spl_creator_policy'])
def test_prop_protection_with_create_and_unpermitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
created_image = self.controller.create(request, image=image,
extra_properties={},
tags=[])
roles = ['fake_member']
another_request = unit_test_utils.get_fake_request(roles=roles)
changes = [
{'op': 'add', 'path': ['x_owner_foo'], 'value': 'bar'},
]
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.update, another_request,
created_image.image_id, changes)
def test_prop_protection_with_show_and_permitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_owner_foo': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
output = self.controller.show(another_request, created_image.image_id)
self.assertEqual('bar', output.extra_properties['x_owner_foo'])
def test_prop_protection_with_show_and_unpermitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['member'])
image = {'name': 'image-1'}
extra_props = {'x_owner_foo': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
output = self.controller.show(another_request, created_image.image_id)
self.assertRaises(KeyError, output.extra_properties.__getitem__,
'x_owner_foo')
def test_prop_protection_with_update_and_permitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_owner_foo': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'replace', 'path': ['x_owner_foo'], 'value': 'baz'},
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('baz', output.extra_properties['x_owner_foo'])
def test_prop_protection_with_update_and_unpermitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_owner_foo': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
changes = [
{'op': 'replace', 'path': ['x_owner_foo'], 'value': 'baz'},
]
self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
another_request, created_image.image_id, changes)
def test_prop_protection_with_delete_and_permitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_owner_foo': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'remove', 'path': ['x_owner_foo']}
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertRaises(KeyError, output.extra_properties.__getitem__,
'x_owner_foo')
def test_prop_protection_with_delete_and_unpermitted_role(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_owner_foo': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
changes = [
{'op': 'remove', 'path': ['x_owner_foo']}
]
self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
another_request, created_image.image_id, changes)
def test_create_protected_prop_case_insensitive(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
created_image = self.controller.create(request, image=image,
extra_properties={},
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'add', 'path': ['x_case_insensitive'], 'value': '1'},
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('1', output.extra_properties['x_case_insensitive'])
def test_read_protected_prop_case_insensitive(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_case_insensitive': '1'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
output = self.controller.show(another_request, created_image.image_id)
self.assertEqual('1', output.extra_properties['x_case_insensitive'])
def test_update_protected_prop_case_insensitive(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_case_insensitive': '1'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'replace', 'path': ['x_case_insensitive'], 'value': '2'},
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('2', output.extra_properties['x_case_insensitive'])
def test_delete_protected_prop_case_insensitive(self):
enforcer = glance.api.policy.Enforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
enforcer,
self.notifier,
self.store)
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_case_insensitive': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'remove', 'path': ['x_case_insensitive']}
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertRaises(KeyError, output.extra_properties.__getitem__,
'x_case_insensitive')
def test_create_non_protected_prop(self):
"""Property marked with special char @ creatable by an unknown role"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_all_permitted_1': '1'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
self.assertEqual('1',
created_image.extra_properties['x_all_permitted_1'])
another_request = unit_test_utils.get_fake_request(roles=['joe_soap'])
extra_props = {'x_all_permitted_2': '2'}
created_image = self.controller.create(another_request, image=image,
extra_properties=extra_props,
tags=[])
self.assertEqual('2',
created_image.extra_properties['x_all_permitted_2'])
def test_read_non_protected_prop(self):
"""Property marked with special char @ readable by an unknown role"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_all_permitted': '1'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['joe_soap'])
output = self.controller.show(another_request, created_image.image_id)
self.assertEqual('1', output.extra_properties['x_all_permitted'])
def test_update_non_protected_prop(self):
"""Property marked with special char @ updatable by an unknown role"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_all_permitted': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['joe_soap'])
changes = [
{'op': 'replace', 'path': ['x_all_permitted'], 'value': 'baz'},
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertEqual('baz', output.extra_properties['x_all_permitted'])
def test_delete_non_protected_prop(self):
"""Property marked with special char @ deletable by an unknown role"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_all_permitted': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['member'])
changes = [
{'op': 'remove', 'path': ['x_all_permitted']}
]
output = self.controller.update(another_request,
created_image.image_id, changes)
self.assertRaises(KeyError, output.extra_properties.__getitem__,
'x_all_permitted')
def test_create_locked_down_protected_prop(self):
"""Property marked with special char ! creatable by no one"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
created_image = self.controller.create(request, image=image,
extra_properties={},
tags=[])
roles = ['fake_member']
another_request = unit_test_utils.get_fake_request(roles=roles)
changes = [
{'op': 'add', 'path': ['x_none_permitted'], 'value': 'bar'},
]
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.update, another_request,
created_image.image_id, changes)
def test_read_locked_down_protected_prop(self):
"""Property marked with special char ! readable by no one"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['member'])
image = {'name': 'image-1'}
extra_props = {'x_none_read': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
output = self.controller.show(another_request, created_image.image_id)
self.assertRaises(KeyError, output.extra_properties.__getitem__,
'x_none_read')
def test_update_locked_down_protected_prop(self):
"""Property marked with special char ! updatable by no one"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_none_update': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
changes = [
{'op': 'replace', 'path': ['x_none_update'], 'value': 'baz'},
]
self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
another_request, created_image.image_id, changes)
def test_delete_locked_down_protected_prop(self):
"""Property marked with special char ! deletable by no one"""
self.set_property_protections()
request = unit_test_utils.get_fake_request(roles=['admin'])
image = {'name': 'image-1'}
extra_props = {'x_none_delete': 'bar'}
created_image = self.controller.create(request, image=image,
extra_properties=extra_props,
tags=[])
another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
changes = [
{'op': 'remove', 'path': ['x_none_delete']}
]
self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
another_request, created_image.image_id, changes)
def test_update_replace_locations_non_empty(self):
self.config(show_multiple_locations=True)
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['locations'],
'value': [new_location]}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
def test_update_replace_locations_metadata_update(self):
self.config(show_multiple_locations=True)
location = {'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {'a': 1}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['locations'],
'value': [location]}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual({'a': 1}, output.locations[0]['metadata'])
def test_locations_actions_with_locations_invisible(self):
self.config(show_multiple_locations=False)
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['locations'],
'value': [new_location]}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_replace_locations_invalid(self):
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_add_property(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['foo'], 'value': 'baz'},
{'op': 'add', 'path': ['snitch'], 'value': 'golden'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual('baz', output.extra_properties['foo'])
self.assertEqual('golden', output.extra_properties['snitch'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_add_base_property_json_schema_version_4(self):
request = unit_test_utils.get_fake_request()
changes = [{
'json_schema_version': 4, 'op': 'add',
'path': ['name'], 'value': 'fedora'
}]
self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
request, UUID1, changes)
def test_update_add_extra_property_json_schema_version_4(self):
self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
request = unit_test_utils.get_fake_request()
changes = [{
'json_schema_version': 4, 'op': 'add',
'path': ['foo'], 'value': 'baz'
}]
self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
request, UUID1, changes)
def test_update_add_base_property_json_schema_version_10(self):
request = unit_test_utils.get_fake_request()
changes = [{
'json_schema_version': 10, 'op': 'add',
'path': ['name'], 'value': 'fedora'
}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual('fedora', output.name)
def test_update_add_extra_property_json_schema_version_10(self):
self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
request = unit_test_utils.get_fake_request()
changes = [{
'json_schema_version': 10, 'op': 'add',
'path': ['foo'], 'value': 'baz'
}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual({'foo': 'baz'}, output.extra_properties)
def test_update_add_property_already_present_json_schema_version_4(self):
request = unit_test_utils.get_fake_request()
properties = {'foo': 'bar'}
self.db.image_update(None, UUID1, {'properties': properties})
output = self.controller.show(request, UUID1)
self.assertEqual('bar', output.extra_properties['foo'])
changes = [
{'json_schema_version': 4, 'op': 'add',
'path': ['foo'], 'value': 'baz'},
]
self.assertRaises(webob.exc.HTTPConflict,
self.controller.update, request, UUID1, changes)
def test_update_add_property_already_present_json_schema_version_10(self):
request = unit_test_utils.get_fake_request()
properties = {'foo': 'bar'}
self.db.image_update(None, UUID1, {'properties': properties})
output = self.controller.show(request, UUID1)
self.assertEqual('bar', output.extra_properties['foo'])
changes = [
{'json_schema_version': 10, 'op': 'add',
'path': ['foo'], 'value': 'baz'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual({'foo': 'baz'}, output.extra_properties)
def test_update_add_locations(self):
self.config(show_multiple_locations=True)
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(2, len(output.locations))
self.assertEqual(new_location, output.locations[1])
def test_replace_location_possible_on_queued(self):
self.skipTest('This test is intermittently failing at the gate. '
'See bug #1649300')
self.config(show_multiple_locations=True)
self.images = [
_db_fixture('1', owner=TENANT1, checksum=CHKSUM,
name='1',
is_public=True,
disk_format='raw',
container_format='bare',
status='queued'),
]
self.db.image_create(None, self.images[0])
request = unit_test_utils.get_fake_request()
new_location = {'url': '%s/fake_location_1' % BASE_URI, 'metadata': {}}
changes = [{'op': 'replace', 'path': ['locations'],
'value': [new_location]}]
output = self.controller.update(request, '1', changes)
self.assertEqual('1', output.image_id)
self.assertEqual(1, len(output.locations))
self.assertEqual(new_location, output.locations[0])
def test_add_location_possible_on_queued(self):
self.skipTest('This test is intermittently failing at the gate. '
'See bug #1649300')
self.config(show_multiple_locations=True)
self.images = [
_db_fixture('1', owner=TENANT1, checksum=CHKSUM,
name='1',
is_public=True,
disk_format='raw',
container_format='bare',
status='queued'),
]
self.db.image_create(None, self.images[0])
request = unit_test_utils.get_fake_request()
new_location = {'url': '%s/fake_location_1' % BASE_URI, 'metadata': {}}
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
output = self.controller.update(request, '1', changes)
self.assertEqual('1', output.image_id)
self.assertEqual(1, len(output.locations))
self.assertEqual(new_location, output.locations[0])
def _test_update_locations_status(self, image_status, update):
self.config(show_multiple_locations=True)
self.images = [
_db_fixture('1', owner=TENANT1, checksum=CHKSUM,
name='1',
disk_format='raw',
container_format='bare',
status=image_status),
]
request = unit_test_utils.get_fake_request()
if image_status == 'deactivated':
self.db.image_create(request.context, self.images[0])
else:
self.db.image_create(None, self.images[0])
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
changes = [{'op': update, 'path': ['locations', '-'],
'value': new_location}]
self.assertRaises(webob.exc.HTTPConflict,
self.controller.update, request, '1', changes)
def test_location_add_not_permitted_status_saving(self):
self._test_update_locations_status('saving', 'add')
def test_location_add_not_permitted_status_deactivated(self):
self._test_update_locations_status('deactivated', 'add')
def test_location_add_not_permitted_status_deleted(self):
self._test_update_locations_status('deleted', 'add')
def test_location_add_not_permitted_status_pending_delete(self):
self._test_update_locations_status('pending_delete', 'add')
def test_location_add_not_permitted_status_killed(self):
self._test_update_locations_status('killed', 'add')
def test_location_remove_not_permitted_status_saving(self):
self._test_update_locations_status('saving', 'remove')
def test_location_remove_not_permitted_status_deactivated(self):
self._test_update_locations_status('deactivated', 'remove')
def test_location_remove_not_permitted_status_deleted(self):
self._test_update_locations_status('deleted', 'remove')
def test_location_remove_not_permitted_status_pending_delete(self):
self._test_update_locations_status('pending_delete', 'remove')
def test_location_remove_not_permitted_status_killed(self):
self._test_update_locations_status('killed', 'remove')
def test_location_remove_not_permitted_status_queued(self):
self._test_update_locations_status('queued', 'remove')
def test_location_replace_not_permitted_status_saving(self):
self._test_update_locations_status('saving', 'replace')
def test_location_replace_not_permitted_status_deactivated(self):
self._test_update_locations_status('deactivated', 'replace')
def test_location_replace_not_permitted_status_deleted(self):
self._test_update_locations_status('deleted', 'replace')
def test_location_replace_not_permitted_status_pending_delete(self):
self._test_update_locations_status('pending_delete', 'replace')
def test_location_replace_not_permitted_status_killed(self):
self._test_update_locations_status('killed', 'replace')
def test_update_add_locations_insertion(self):
self.config(show_multiple_locations=True)
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '0'],
'value': new_location}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(2, len(output.locations))
self.assertEqual(new_location, output.locations[0])
def test_update_add_locations_list(self):
self.config(show_multiple_locations=True)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': {'url': 'foo', 'metadata': {}}}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
def test_update_add_locations_invalid(self):
self.config(show_multiple_locations=True)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': {'url': 'unknow://foo', 'metadata': {}}}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
changes = [{'op': 'add', 'path': ['locations', None],
'value': {'url': 'unknow://foo', 'metadata': {}}}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
def test_update_add_duplicate_locations(self):
self.config(show_multiple_locations=True)
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(2, len(output.locations))
self.assertEqual(new_location, output.locations[1])
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
def test_update_add_too_many_locations(self):
self.config(show_multiple_locations=True)
self.config(image_location_quota=1)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {}}},
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_2' % BASE_URI,
'metadata': {}}},
]
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update, request,
UUID1, changes)
def test_update_add_and_remove_too_many_locations(self):
self.config(show_multiple_locations=True)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {}}},
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_2' % BASE_URI,
'metadata': {}}},
]
self.controller.update(request, UUID1, changes)
self.config(image_location_quota=1)
# We must remove two properties to avoid being
# over the limit of 1 property
changes = [
{'op': 'remove', 'path': ['locations', '0']},
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_3' % BASE_URI,
'metadata': {}}},
]
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update, request,
UUID1, changes)
def test_update_add_unlimited_locations(self):
self.config(show_multiple_locations=True)
self.config(image_location_quota=-1)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {}}},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_remove_location_while_over_limit(self):
"""Ensure that image locations can be removed.
Image locations should be able to be removed as long as the image has
fewer than the limited number of image locations after the
transaction.
"""
self.config(show_multiple_locations=True)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {}}},
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_2' % BASE_URI,
'metadata': {}}},
]
self.controller.update(request, UUID1, changes)
self.config(image_location_quota=1)
self.config(show_multiple_locations=True)
# We must remove two locations to avoid being over
# the limit of 1 location
changes = [
{'op': 'remove', 'path': ['locations', '0']},
{'op': 'remove', 'path': ['locations', '0']},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(1, len(output.locations))
self.assertIn('fake_location_2', output.locations[0]['url'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_add_and_remove_location_under_limit(self):
"""Ensure that image locations can be removed.
Image locations should be able to be added and removed simultaneously
as long as the image has fewer than the limited number of image
locations after the transaction.
"""
self.stubs.Set(store, 'get_size_from_backend',
unit_test_utils.fake_get_size_from_backend)
self.config(show_multiple_locations=True)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {}}},
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_2' % BASE_URI,
'metadata': {}}},
]
self.controller.update(request, UUID1, changes)
self.config(image_location_quota=2)
# We must remove two properties to avoid being
# over the limit of 1 property
changes = [
{'op': 'remove', 'path': ['locations', '0']},
{'op': 'remove', 'path': ['locations', '0']},
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location_3' % BASE_URI,
'metadata': {}}},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(2, len(output.locations))
self.assertIn('fake_location_3', output.locations[1]['url'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_remove_base_property(self):
self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
request = unit_test_utils.get_fake_request()
changes = [{'op': 'remove', 'path': ['name']}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_remove_property(self):
request = unit_test_utils.get_fake_request()
properties = {'foo': 'bar', 'snitch': 'golden'}
self.db.image_update(None, UUID1, {'properties': properties})
output = self.controller.show(request, UUID1)
self.assertEqual('bar', output.extra_properties['foo'])
self.assertEqual('golden', output.extra_properties['snitch'])
changes = [
{'op': 'remove', 'path': ['snitch']},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual({'foo': 'bar'}, output.extra_properties)
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_remove_missing_property(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'remove', 'path': ['foo']},
]
self.assertRaises(webob.exc.HTTPConflict,
self.controller.update, request, UUID1, changes)
def test_update_remove_location(self):
self.config(show_multiple_locations=True)
self.stubs.Set(store, 'get_size_from_backend',
unit_test_utils.fake_get_size_from_backend)
request = unit_test_utils.get_fake_request()
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
self.controller.update(request, UUID1, changes)
changes = [{'op': 'remove', 'path': ['locations', '0']}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(1, len(output.locations))
self.assertEqual('active', output.status)
def test_update_remove_location_invalid_pos(self):
self.config(show_multiple_locations=True)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location' % BASE_URI,
'metadata': {}}}]
self.controller.update(request, UUID1, changes)
changes = [{'op': 'remove', 'path': ['locations', None]}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
changes = [{'op': 'remove', 'path': ['locations', '-1']}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
changes = [{'op': 'remove', 'path': ['locations', '99']}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
changes = [{'op': 'remove', 'path': ['locations', 'x']}]
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID1, changes)
def test_update_remove_location_store_exception(self):
self.config(show_multiple_locations=True)
def fake_delete_image_location_from_backend(self, *args, **kwargs):
raise Exception('fake_backend_exception')
self.stubs.Set(self.store_utils, 'delete_image_location_from_backend',
fake_delete_image_location_from_backend)
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'add', 'path': ['locations', '-'],
'value': {'url': '%s/fake_location' % BASE_URI,
'metadata': {}}}]
self.controller.update(request, UUID1, changes)
changes = [{'op': 'remove', 'path': ['locations', '0']}]
self.assertRaises(webob.exc.HTTPInternalServerError,
self.controller.update, request, UUID1, changes)
def test_update_multiple_changes(self):
request = unit_test_utils.get_fake_request()
properties = {'foo': 'bar', 'snitch': 'golden'}
self.db.image_update(None, UUID1, {'properties': properties})
changes = [
{'op': 'replace', 'path': ['min_ram'], 'value': 128},
{'op': 'replace', 'path': ['foo'], 'value': 'baz'},
{'op': 'remove', 'path': ['snitch']},
{'op': 'add', 'path': ['kb'], 'value': 'dvorak'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(UUID1, output.image_id)
self.assertEqual(128, output.min_ram)
self.addDetail('extra_properties',
testtools.content.json_content(
jsonutils.dumps(output.extra_properties)))
self.assertEqual(2, len(output.extra_properties))
self.assertEqual('baz', output.extra_properties['foo'])
self.assertEqual('dvorak', output.extra_properties['kb'])
self.assertNotEqual(output.created_at, output.updated_at)
def test_update_invalid_operation(self):
request = unit_test_utils.get_fake_request()
change = {'op': 'test', 'path': 'options', 'value': 'puts'}
try:
self.controller.update(request, UUID1, [change])
except AttributeError:
pass # AttributeError is the desired behavior
else:
self.fail('Failed to raise AssertionError on %s' % change)
def test_update_duplicate_tags(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'replace', 'path': ['tags'], 'value': ['ping', 'ping']},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual(1, len(output.tags))
self.assertIn('ping', output.tags)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.update', output_log['event_type'])
self.assertEqual(UUID1, output_log['payload']['id'])
def test_update_disabled_notification(self):
self.config(disabled_notifications=["image.update"])
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'replace', 'path': ['name'], 'value': 'Ping Pong'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual('Ping Pong', output.name)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_delete(self):
request = unit_test_utils.get_fake_request()
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
try:
self.controller.delete(request, UUID1)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual("image.delete", output_log['event_type'])
except Exception as e:
self.fail("Delete raised exception: %s" % e)
deleted_img = self.db.image_get(request.context, UUID1,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delete_with_tags(self):
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'replace', 'path': ['tags'],
'value': ['many', 'cool', 'new', 'tags']},
]
self.controller.update(request, UUID1, changes)
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
self.controller.delete(request, UUID1)
output_logs = self.notifier.get_logs()
# Get `delete` event from logs
output_delete_logs = [output_log for output_log in output_logs
if output_log['event_type'] == 'image.delete']
self.assertEqual(1, len(output_delete_logs))
output_log = output_delete_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
deleted_img = self.db.image_get(request.context, UUID1,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delete_disabled_notification(self):
self.config(disabled_notifications=["image.delete"])
request = unit_test_utils.get_fake_request()
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
try:
self.controller.delete(request, UUID1)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
except Exception as e:
self.fail("Delete raised exception: %s" % e)
deleted_img = self.db.image_get(request.context, UUID1,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delete_queued_updates_status(self):
"""Ensure status of queued image is updated (LP bug #1048851)"""
request = unit_test_utils.get_fake_request(is_admin=True)
image = self.db.image_create(request.context, {'status': 'queued'})
image_id = image['id']
self.controller.delete(request, image_id)
image = self.db.image_get(request.context, image_id,
force_show_deleted=True)
self.assertTrue(image['deleted'])
self.assertEqual('deleted', image['status'])
def test_delete_queued_updates_status_delayed_delete(self):
"""Ensure status of queued image is updated (LP bug #1048851).
Must be set to 'deleted' when delayed_delete isenabled.
"""
self.config(delayed_delete=True)
request = unit_test_utils.get_fake_request(is_admin=True)
image = self.db.image_create(request.context, {'status': 'queued'})
image_id = image['id']
self.controller.delete(request, image_id)
image = self.db.image_get(request.context, image_id,
force_show_deleted=True)
self.assertTrue(image['deleted'])
self.assertEqual('deleted', image['status'])
def test_delete_not_in_store(self):
request = unit_test_utils.get_fake_request()
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
for k in self.store.data:
if UUID1 in k:
del self.store.data[k]
break
self.controller.delete(request, UUID1)
deleted_img = self.db.image_get(request.context, UUID1,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delayed_delete(self):
self.config(delayed_delete=True)
request = unit_test_utils.get_fake_request()
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
self.controller.delete(request, UUID1)
deleted_img = self.db.image_get(request.context, UUID1,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('pending_delete', deleted_img['status'])
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delete_non_existent(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
request, str(uuid.uuid4()))
def test_delete_already_deleted_image_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.controller.delete(request, UUID1)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, request, UUID1)
def test_delete_not_allowed(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
request, UUID4)
def test_delete_in_use(self):
def fake_safe_delete_from_backend(self, *args, **kwargs):
raise store.exceptions.InUseByStore()
self.stubs.Set(self.store_utils, 'safe_delete_from_backend',
fake_safe_delete_from_backend)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPConflict, self.controller.delete,
request, UUID1)
def test_delete_has_snapshot(self):
def fake_safe_delete_from_backend(self, *args, **kwargs):
raise store.exceptions.HasSnapshot()
self.stubs.Set(self.store_utils, 'safe_delete_from_backend',
fake_safe_delete_from_backend)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPConflict, self.controller.delete,
request, UUID1)
def test_delete_to_unallowed_status(self):
# from deactivated to pending-delete
self.config(delayed_delete=True)
request = unit_test_utils.get_fake_request(is_admin=True)
self.action_controller.deactivate(request, UUID1)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete,
request, UUID1)
def test_delete_uploading_status_image(self):
"""Ensure status of uploading image is updated (LP bug #1733289)"""
self.config(enable_image_import=True)
request = unit_test_utils.get_fake_request(is_admin=True)
image = self.db.image_create(request.context, {'status': 'uploading'})
image_id = image['id']
with mock.patch.object(self.store,
'delete_from_backend') as mock_store:
self.controller.delete(request, image_id)
# Ensure delete_from_backend is called
self.assertEqual(1, mock_store.call_count)
image = self.db.image_get(request.context, image_id,
force_show_deleted=True)
self.assertTrue(image['deleted'])
self.assertEqual('deleted', image['status'])
def test_index_with_invalid_marker(self):
fake_uuid = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, request, marker=fake_uuid)
def test_invalid_locations_op_pos(self):
pos = self.controller._get_locations_op_pos(None, 2, True)
self.assertIsNone(pos)
pos = self.controller._get_locations_op_pos('1', None, True)
self.assertIsNone(pos)
def test_image_import(self):
request = unit_test_utils.get_fake_request()
output = self.controller.import_image(request, UUID4,
{'method': {'name':
'glance-direct'}})
self.assertEqual(UUID4, output)
def test_image_import_not_allowed(self):
request = unit_test_utils.get_fake_request()
# NOTE(abhishekk): For coverage purpose setting tenant to
# None. It is not expected to do in normal scenarios.
request.context.tenant = None
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.import_image,
request, UUID4, {'method': {'name':
'glance-direct'}})
class TestImagesControllerPolicies(base.IsolatedUnitTest):
def setUp(self):
super(TestImagesControllerPolicies, self).setUp()
self.db = unit_test_utils.FakeDB()
self.policy = unit_test_utils.FakePolicyEnforcer()
self.controller = glance.api.v2.images.ImagesController(self.db,
self.policy)
store = unit_test_utils.FakeStoreAPI()
self.store_utils = unit_test_utils.FakeStoreUtils(store)
def test_index_unauthorized(self):
rules = {"get_images": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.index,
request)
def test_show_unauthorized(self):
rules = {"get_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.show,
request, image_id=UUID2)
def test_create_image_unauthorized(self):
rules = {"add_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
image = {'name': 'image-1'}
extra_properties = {}
tags = []
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, image, extra_properties, tags)
def test_create_public_image_unauthorized(self):
rules = {"publicize_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
image = {'name': 'image-1', 'visibility': 'public'}
extra_properties = {}
tags = []
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, image, extra_properties, tags)
def test_create_community_image_unauthorized(self):
rules = {"communitize_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
image = {'name': 'image-c1', 'visibility': 'community'}
extra_properties = {}
tags = []
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, image, extra_properties, tags)
def test_update_unauthorized(self):
rules = {"modify_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['name'], 'value': 'image-2'}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_publicize_image_unauthorized(self):
rules = {"publicize_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['visibility'],
'value': 'public'}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_communitize_image_unauthorized(self):
rules = {"communitize_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['visibility'],
'value': 'community'}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_depublicize_image_unauthorized(self):
rules = {"publicize_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['visibility'],
'value': 'private'}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual('private', output.visibility)
def test_update_decommunitize_image_unauthorized(self):
rules = {"communitize_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['visibility'],
'value': 'private'}]
output = self.controller.update(request, UUID1, changes)
self.assertEqual('private', output.visibility)
def test_update_get_image_location_unauthorized(self):
rules = {"get_image_location": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_set_image_location_unauthorized(self):
def fake_delete_image_location_from_backend(self, *args, **kwargs):
pass
rules = {"set_image_location": False}
self.policy.set_rules(rules)
new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_update_delete_image_location_unauthorized(self):
rules = {"delete_image_location": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID1, changes)
def test_delete_unauthorized(self):
rules = {"delete_image": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
request, UUID1)
class TestImagesDeserializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializer, self).setUp()
self.deserializer = glance.api.v2.images.RequestDeserializer()
def test_create_minimal(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({})
output = self.deserializer.create(request)
expected = {'image': {}, 'extra_properties': {}, 'tags': []}
self.assertEqual(expected, output)
def test_create_invalid_id(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'id': 'gabe'})
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_create_id_to_image_id(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'id': UUID4})
output = self.deserializer.create(request)
expected = {'image': {'image_id': UUID4},
'extra_properties': {},
'tags': []}
self.assertEqual(expected, output)
def test_create_no_body(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_create_full(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({
'id': UUID3,
'name': 'image-1',
'visibility': 'public',
'tags': ['one', 'two'],
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'foo': 'bar',
'protected': True,
})
output = self.deserializer.create(request)
properties = {
'image_id': UUID3,
'name': 'image-1',
'visibility': 'public',
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'protected': True,
}
self.maxDiff = None
expected = {'image': properties,
'extra_properties': {'foo': 'bar'},
'tags': ['one', 'two']}
self.assertEqual(expected, output)
def test_create_invalid_property_key(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({
'id': UUID3,
'name': 'image-1',
'visibility': 'public',
'tags': ['one', 'two'],
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'f' * 256: 'bar',
'protected': True,
})
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_create_readonly_attributes_forbidden(self):
bodies = [
{'direct_url': 'http://example.com'},
{'self': 'http://example.com'},
{'file': 'http://example.com'},
{'schema': 'http://example.com'},
]
for body in bodies:
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPForbidden,
self.deserializer.create, request)
def _get_fake_patch_request(self, content_type_minor_version=1):
request = unit_test_utils.get_fake_request()
template = 'application/openstack-images-v2.%d-json-patch'
request.content_type = template % content_type_minor_version
return request
def test_update_empty_body(self):
request = self._get_fake_patch_request()
request.body = jsonutils.dump_as_bytes([])
output = self.deserializer.update(request)
expected = {'changes': []}
self.assertEqual(expected, output)
def test_update_unsupported_content_type(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/json-patch'
request.body = jsonutils.dump_as_bytes([])
try:
self.deserializer.update(request)
except webob.exc.HTTPUnsupportedMediaType as e:
# desired result, but must have correct Accept-Patch header
accept_patch = ['application/openstack-images-v2.1-json-patch',
'application/openstack-images-v2.0-json-patch']
expected = ', '.join(sorted(accept_patch))
self.assertEqual(expected, e.headers['Accept-Patch'])
else:
self.fail('Did not raise HTTPUnsupportedMediaType')
def test_update_body_not_a_list(self):
bodies = [
{'op': 'add', 'path': '/someprop', 'value': 'somevalue'},
'just some string',
123,
True,
False,
None,
]
for body in bodies:
request = self._get_fake_patch_request()
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update_invalid_changes(self):
changes = [
['a', 'list', 'of', 'stuff'],
'just some string',
123,
True,
False,
None,
{'op': 'invalid', 'path': '/name', 'value': 'fedora'}
]
for change in changes:
request = self._get_fake_patch_request()
request.body = jsonutils.dump_as_bytes([change])
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update(self):
request = self._get_fake_patch_request()
body = [
{'op': 'replace', 'path': '/name', 'value': 'fedora'},
{'op': 'replace', 'path': '/tags', 'value': ['king', 'kong']},
{'op': 'replace', 'path': '/foo', 'value': 'bar'},
{'op': 'add', 'path': '/bebim', 'value': 'bap'},
{'op': 'remove', 'path': '/sparks'},
{'op': 'add', 'path': '/locations/-',
'value': {'url': 'scheme3://path3', 'metadata': {}}},
{'op': 'add', 'path': '/locations/10',
'value': {'url': 'scheme4://path4', 'metadata': {}}},
{'op': 'remove', 'path': '/locations/2'},
{'op': 'replace', 'path': '/locations', 'value': []},
{'op': 'replace', 'path': '/locations',
'value': [{'url': 'scheme5://path5', 'metadata': {}},
{'url': 'scheme6://path6', 'metadata': {}}]},
]
request.body = jsonutils.dump_as_bytes(body)
output = self.deserializer.update(request)
expected = {'changes': [
{'json_schema_version': 10, 'op': 'replace',
'path': ['name'], 'value': 'fedora'},
{'json_schema_version': 10, 'op': 'replace',
'path': ['tags'], 'value': ['king', 'kong']},
{'json_schema_version': 10, 'op': 'replace',
'path': ['foo'], 'value': 'bar'},
{'json_schema_version': 10, 'op': 'add',
'path': ['bebim'], 'value': 'bap'},
{'json_schema_version': 10, 'op': 'remove',
'path': ['sparks']},
{'json_schema_version': 10, 'op': 'add',
'path': ['locations', '-'],
'value': {'url': 'scheme3://path3', 'metadata': {}}},
{'json_schema_version': 10, 'op': 'add',
'path': ['locations', '10'],
'value': {'url': 'scheme4://path4', 'metadata': {}}},
{'json_schema_version': 10, 'op': 'remove',
'path': ['locations', '2']},
{'json_schema_version': 10, 'op': 'replace',
'path': ['locations'], 'value': []},
{'json_schema_version': 10, 'op': 'replace',
'path': ['locations'],
'value': [{'url': 'scheme5://path5', 'metadata': {}},
{'url': 'scheme6://path6', 'metadata': {}}]},
]}
self.assertEqual(expected, output)
def test_update_v2_0_compatibility(self):
request = self._get_fake_patch_request(content_type_minor_version=0)
body = [
{'replace': '/name', 'value': 'fedora'},
{'replace': '/tags', 'value': ['king', 'kong']},
{'replace': '/foo', 'value': 'bar'},
{'add': '/bebim', 'value': 'bap'},
{'remove': '/sparks'},
{'add': '/locations/-', 'value': {'url': 'scheme3://path3',
'metadata': {}}},
{'add': '/locations/10', 'value': {'url': 'scheme4://path4',
'metadata': {}}},
{'remove': '/locations/2'},
{'replace': '/locations', 'value': []},
{'replace': '/locations',
'value': [{'url': 'scheme5://path5', 'metadata': {}},
{'url': 'scheme6://path6', 'metadata': {}}]},
]
request.body = jsonutils.dump_as_bytes(body)
output = self.deserializer.update(request)
expected = {'changes': [
{'json_schema_version': 4, 'op': 'replace',
'path': ['name'], 'value': 'fedora'},
{'json_schema_version': 4, 'op': 'replace',
'path': ['tags'], 'value': ['king', 'kong']},
{'json_schema_version': 4, 'op': 'replace',
'path': ['foo'], 'value': 'bar'},
{'json_schema_version': 4, 'op': 'add',
'path': ['bebim'], 'value': 'bap'},
{'json_schema_version': 4, 'op': 'remove', 'path': ['sparks']},
{'json_schema_version': 4, 'op': 'add',
'path': ['locations', '-'],
'value': {'url': 'scheme3://path3', 'metadata': {}}},
{'json_schema_version': 4, 'op': 'add',
'path': ['locations', '10'],
'value': {'url': 'scheme4://path4', 'metadata': {}}},
{'json_schema_version': 4, 'op': 'remove',
'path': ['locations', '2']},
{'json_schema_version': 4, 'op': 'replace',
'path': ['locations'], 'value': []},
{'json_schema_version': 4, 'op': 'replace', 'path': ['locations'],
'value': [{'url': 'scheme5://path5', 'metadata': {}},
{'url': 'scheme6://path6', 'metadata': {}}]},
]}
self.assertEqual(expected, output)
def test_update_base_attributes(self):
request = self._get_fake_patch_request()
body = [
{'op': 'replace', 'path': '/name', 'value': 'fedora'},
{'op': 'replace', 'path': '/visibility', 'value': 'public'},
{'op': 'replace', 'path': '/tags', 'value': ['king', 'kong']},
{'op': 'replace', 'path': '/protected', 'value': True},
{'op': 'replace', 'path': '/container_format', 'value': 'bare'},
{'op': 'replace', 'path': '/disk_format', 'value': 'raw'},
{'op': 'replace', 'path': '/min_ram', 'value': 128},
{'op': 'replace', 'path': '/min_disk', 'value': 10},
{'op': 'replace', 'path': '/locations', 'value': []},
{'op': 'replace', 'path': '/locations',
'value': [{'url': 'scheme5://path5', 'metadata': {}},
{'url': 'scheme6://path6', 'metadata': {}}]}
]
request.body = jsonutils.dump_as_bytes(body)
output = self.deserializer.update(request)
expected = {'changes': [
{'json_schema_version': 10, 'op': 'replace',
'path': ['name'], 'value': 'fedora'},
{'json_schema_version': 10, 'op': 'replace',
'path': ['visibility'], 'value': 'public'},
{'json_schema_version': 10, 'op': 'replace',
'path': ['tags'], 'value': ['king', 'kong']},
{'json_schema_version': 10, 'op': 'replace',
'path': ['protected'], 'value': True},
{'json_schema_version': 10, 'op': 'replace',
'path': ['container_format'], 'value': 'bare'},
{'json_schema_version': 10, 'op': 'replace',
'path': ['disk_format'], 'value': 'raw'},
{'json_schema_version': 10, 'op': 'replace',
'path': ['min_ram'], 'value': 128},
{'json_schema_version': 10, 'op': 'replace',
'path': ['min_disk'], 'value': 10},
{'json_schema_version': 10, 'op': 'replace',
'path': ['locations'], 'value': []},
{'json_schema_version': 10, 'op': 'replace', 'path': ['locations'],
'value': [{'url': 'scheme5://path5', 'metadata': {}},
{'url': 'scheme6://path6', 'metadata': {}}]}
]}
self.assertEqual(expected, output)
def test_update_disallowed_attributes(self):
samples = {
'direct_url': '/a/b/c/d',
'self': '/e/f/g/h',
'file': '/e/f/g/h/file',
'schema': '/i/j/k',
}
for key, value in samples.items():
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
request.body = jsonutils.dump_as_bytes(body)
try:
self.deserializer.update(request)
except webob.exc.HTTPForbidden:
pass # desired behavior
else:
self.fail("Updating %s did not result in HTTPForbidden" % key)
def test_update_readonly_attributes(self):
samples = {
'id': '00000000-0000-0000-0000-000000000000',
'status': 'active',
'checksum': 'abcdefghijklmnopqrstuvwxyz012345',
'size': 9001,
'virtual_size': 9001,
'created_at': ISOTIME,
'updated_at': ISOTIME,
}
for key, value in samples.items():
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
request.body = jsonutils.dump_as_bytes(body)
try:
self.deserializer.update(request)
except webob.exc.HTTPForbidden:
pass # desired behavior
else:
self.fail("Updating %s did not result in HTTPForbidden" % key)
def test_update_reserved_attributes(self):
samples = {
'deleted': False,
'deleted_at': ISOTIME,
}
for key, value in samples.items():
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
request.body = jsonutils.dump_as_bytes(body)
try:
self.deserializer.update(request)
except webob.exc.HTTPForbidden:
pass # desired behavior
else:
self.fail("Updating %s did not result in HTTPForbidden" % key)
def test_update_invalid_attributes(self):
keys = [
'noslash',
'///twoslash',
'/two/ /slash',
'/ / ',
'/trailingslash/',
'/lone~tilde',
'/trailingtilde~'
]
for key in keys:
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'path': '%s' % key, 'value': 'dummy'}]
request.body = jsonutils.dump_as_bytes(body)
try:
self.deserializer.update(request)
except webob.exc.HTTPBadRequest:
pass # desired behavior
else:
self.fail("Updating %s did not result in HTTPBadRequest" % key)
def test_update_pointer_encoding(self):
samples = {
'/keywith~1slash': [u'keywith/slash'],
'/keywith~0tilde': [u'keywith~tilde'],
'/tricky~01': [u'tricky~1'],
}
for encoded, decoded in samples.items():
request = self._get_fake_patch_request()
doc = [{'op': 'replace', 'path': '%s' % encoded, 'value': 'dummy'}]
request.body = jsonutils.dump_as_bytes(doc)
output = self.deserializer.update(request)
self.assertEqual(decoded, output['changes'][0]['path'])
def test_update_deep_limited_attributes(self):
samples = {
'locations/1/2': [],
}
for key, value in samples.items():
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
request.body = jsonutils.dump_as_bytes(body)
try:
self.deserializer.update(request)
except webob.exc.HTTPBadRequest:
pass # desired behavior
else:
self.fail("Updating %s did not result in HTTPBadRequest" % key)
def test_update_v2_1_missing_operations(self):
request = self._get_fake_patch_request()
body = [{'path': '/colburn', 'value': 'arcata'}]
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update_v2_1_missing_value(self):
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'path': '/colburn'}]
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update_v2_1_missing_path(self):
request = self._get_fake_patch_request()
body = [{'op': 'replace', 'value': 'arcata'}]
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update_v2_0_multiple_operations(self):
request = self._get_fake_patch_request(content_type_minor_version=0)
body = [{'replace': '/foo', 'add': '/bar', 'value': 'snore'}]
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update_v2_0_missing_operations(self):
request = self._get_fake_patch_request(content_type_minor_version=0)
body = [{'value': 'arcata'}]
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update_v2_0_missing_value(self):
request = self._get_fake_patch_request(content_type_minor_version=0)
body = [{'replace': '/colburn'}]
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_index(self):
marker = str(uuid.uuid4())
path = '/images?limit=1&marker=%s&member_status=pending' % marker
request = unit_test_utils.get_fake_request(path)
expected = {'limit': 1,
'marker': marker,
'sort_key': ['created_at'],
'sort_dir': ['desc'],
'member_status': 'pending',
'filters': {}}
output = self.deserializer.index(request)
self.assertEqual(expected, output)
def test_index_with_filter(self):
name = 'My Little Image'
path = '/images?name=%s' % name
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(name, output['filters']['name'])
def test_index_strip_params_from_filters(self):
name = 'My Little Image'
path = '/images?name=%s' % name
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(name, output['filters']['name'])
self.assertEqual(1, len(output['filters']))
def test_index_with_many_filter(self):
name = 'My Little Image'
instance_id = str(uuid.uuid4())
path = ('/images?name=%(name)s&id=%(instance_id)s' %
{'name': name, 'instance_id': instance_id})
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(name, output['filters']['name'])
self.assertEqual(instance_id, output['filters']['id'])
def test_index_with_filter_and_limit(self):
name = 'My Little Image'
path = '/images?name=%s&limit=1' % name
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(name, output['filters']['name'])
self.assertEqual(1, output['limit'])
def test_index_non_integer_limit(self):
request = unit_test_utils.get_fake_request('/images?limit=blah')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_zero_limit(self):
request = unit_test_utils.get_fake_request('/images?limit=0')
expected = {'limit': 0,
'sort_key': ['created_at'],
'member_status': 'accepted',
'sort_dir': ['desc'],
'filters': {}}
output = self.deserializer.index(request)
self.assertEqual(expected, output)
def test_index_negative_limit(self):
request = unit_test_utils.get_fake_request('/images?limit=-1')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_fraction(self):
request = unit_test_utils.get_fake_request('/images?limit=1.1')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_invalid_status(self):
path = '/images?member_status=blah'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_marker(self):
marker = str(uuid.uuid4())
path = '/images?marker=%s' % marker
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(marker, output.get('marker'))
def test_index_marker_not_specified(self):
request = unit_test_utils.get_fake_request('/images')
output = self.deserializer.index(request)
self.assertNotIn('marker', output)
def test_index_limit_not_specified(self):
request = unit_test_utils.get_fake_request('/images')
output = self.deserializer.index(request)
self.assertNotIn('limit', output)
def test_index_sort_key_id(self):
request = unit_test_utils.get_fake_request('/images?sort_key=id')
output = self.deserializer.index(request)
expected = {
'sort_key': ['id'],
'sort_dir': ['desc'],
'member_status': 'accepted',
'filters': {}
}
self.assertEqual(expected, output)
def test_index_multiple_sort_keys(self):
request = unit_test_utils.get_fake_request('/images?'
'sort_key=name&'
'sort_key=size')
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc'],
'member_status': 'accepted',
'filters': {}
}
self.assertEqual(expected, output)
def test_index_invalid_multiple_sort_keys(self):
# blah is an invalid sort key
request = unit_test_utils.get_fake_request('/images?'
'sort_key=name&'
'sort_key=blah')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_sort_dir_asc(self):
request = unit_test_utils.get_fake_request('/images?sort_dir=asc')
output = self.deserializer.index(request)
expected = {
'sort_key': ['created_at'],
'sort_dir': ['asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_multiple_sort_dirs(self):
req_string = ('/images?sort_key=name&sort_dir=asc&'
'sort_key=id&sort_dir=desc')
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'id'],
'sort_dir': ['asc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_single_key_default_dir(self):
req_string = '/images?sort=name'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name'],
'sort_dir': ['desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_single_key_desc_dir(self):
req_string = '/images?sort=name:desc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name'],
'sort_dir': ['desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_default_dir(self):
req_string = '/images?sort=name,size'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_asc_dir(self):
req_string = '/images?sort=name:asc,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['asc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_different_dirs(self):
req_string = '/images?sort=name:desc,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_optional_dir(self):
req_string = '/images?sort=name:asc,size'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['asc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
req_string = '/images?sort=name,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
req_string = '/images?sort=name,id:asc,size'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'id', 'size'],
'sort_dir': ['desc', 'asc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
req_string = '/images?sort=name:asc,id,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'id', 'size'],
'sort_dir': ['asc', 'desc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_sort_wrong_sort_dirs_number(self):
req_string = '/images?sort_key=name&sort_dir=asc&sort_dir=desc'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_sort_dirs_fewer_than_keys(self):
req_string = ('/images?sort_key=name&sort_dir=asc&sort_key=id&'
'sort_dir=asc&sort_key=created_at')
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_sort_wrong_sort_dirs_number_without_key(self):
req_string = '/images?sort_dir=asc&sort_dir=desc'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_sort_private_key(self):
request = unit_test_utils.get_fake_request('/images?sort_key=min_ram')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_sort_key_invalid_value(self):
# blah is an invalid sort key
request = unit_test_utils.get_fake_request('/images?sort_key=blah')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_sort_dir_invalid_value(self):
# foo is an invalid sort dir
request = unit_test_utils.get_fake_request('/images?sort_dir=foo')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_new_sorting_syntax_invalid_request(self):
# 'blah' is not a supported sorting key
req_string = '/images?sort=blah'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
req_string = '/images?sort=name,blah'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
# 'foo' isn't a valid sort direction
req_string = '/images?sort=name:foo'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
# 'asc:desc' isn't a valid sort direction
req_string = '/images?sort=name:asc:desc'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_combined_sorting_syntax(self):
req_string = '/images?sort_dir=name&sort=name'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_with_tag(self):
path = '/images?tag=%s&tag=%s' % ('x86', '64bit')
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(sorted(['x86', '64bit']),
sorted(output['filters']['tags']))
def test_image_import(self):
self.config(enable_image_import=True)
# Bug 1754634: make sure that what's considered valid
# is determined by the config option
self.config(enabled_import_methods=['party-time'])
request = unit_test_utils.get_fake_request()
import_body = {
"method": {
"name": "party-time"
}
}
request.body = jsonutils.dump_as_bytes(import_body)
output = self.deserializer.import_image(request)
expected = {"body": import_body}
self.assertEqual(expected, output)
def test_import_image_disabled(self):
self.config(enable_image_import=False)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.deserializer.import_image,
request)
def test_import_image_invalid_body(self):
self.config(enable_image_import=True)
request = unit_test_utils.get_fake_request()
import_body = {
"method1": {
"name": "glance-direct"
}
}
request.body = jsonutils.dump_as_bytes(import_body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.import_image,
request)
def test_import_image_invalid_input(self):
self.config(enable_image_import=True)
request = unit_test_utils.get_fake_request()
import_body = {
"method": {
"abcd": "glance-direct"
}
}
request.body = jsonutils.dump_as_bytes(import_body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.import_image,
request)
def _get_request_for_method(self, method_name):
request = unit_test_utils.get_fake_request()
import_body = {
"method": {
"name": method_name
}
}
request.body = jsonutils.dump_as_bytes(import_body)
return request
KNOWN_IMPORT_METHODS = ['glance-direct', 'web-download']
def test_import_image_invalid_import_method(self):
self.config(enable_image_import=True)
# Bug 1754634: make sure that what's considered valid
# is determined by the config option. So put known bad
# name in config, and known good name in request
self.config(enabled_import_methods=['bad-method-name'])
for m in self.KNOWN_IMPORT_METHODS:
request = self._get_request_for_method(m)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.import_image,
request)
class TestImagesDeserializerWithExtendedSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializerWithExtendedSchema, self).setUp()
self.config(allow_additional_image_properties=False)
custom_image_properties = {
'pants': {
'type': 'string',
'enum': ['on', 'off'],
},
}
schema = glance.api.v2.images.get_schema(custom_image_properties)
self.deserializer = glance.api.v2.images.RequestDeserializer(schema)
def test_create(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({
'name': 'image-1',
'pants': 'on'
})
output = self.deserializer.create(request)
expected = {
'image': {'name': 'image-1'},
'extra_properties': {'pants': 'on'},
'tags': [],
}
self.assertEqual(expected, output)
def test_create_bad_data(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({
'name': 'image-1',
'pants': 'borked'
})
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create, request)
def test_update(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/openstack-images-v2.1-json-patch'
doc = [{'op': 'add', 'path': '/pants', 'value': 'off'}]
request.body = jsonutils.dump_as_bytes(doc)
output = self.deserializer.update(request)
expected = {'changes': [
{'json_schema_version': 10, 'op': 'add',
'path': ['pants'], 'value': 'off'},
]}
self.assertEqual(expected, output)
def test_update_bad_data(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/openstack-images-v2.1-json-patch'
doc = [{'op': 'add', 'path': '/pants', 'value': 'cutoffs'}]
request.body = jsonutils.dump_as_bytes(doc)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update,
request)
class TestImagesDeserializerWithAdditionalProperties(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializerWithAdditionalProperties, self).setUp()
self.config(allow_additional_image_properties=True)
self.deserializer = glance.api.v2.images.RequestDeserializer()
def test_create(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'foo': 'bar'})
output = self.deserializer.create(request)
expected = {'image': {},
'extra_properties': {'foo': 'bar'},
'tags': []}
self.assertEqual(expected, output)
def test_create_with_numeric_property(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'abc': 123})
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create, request)
def test_update_with_numeric_property(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/openstack-images-v2.1-json-patch'
doc = [{'op': 'add', 'path': '/foo', 'value': 123}]
request.body = jsonutils.dump_as_bytes(doc)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_create_with_list_property(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'foo': ['bar']})
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create, request)
def test_update_with_list_property(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/openstack-images-v2.1-json-patch'
doc = [{'op': 'add', 'path': '/foo', 'value': ['bar', 'baz']}]
request.body = jsonutils.dump_as_bytes(doc)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
def test_update(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/openstack-images-v2.1-json-patch'
doc = [{'op': 'add', 'path': '/foo', 'value': 'bar'}]
request.body = jsonutils.dump_as_bytes(doc)
output = self.deserializer.update(request)
change = {
'json_schema_version': 10, 'op': 'add',
'path': ['foo'], 'value': 'bar'
}
self.assertEqual({'changes': [change]}, output)
class TestImagesDeserializerNoAdditionalProperties(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializerNoAdditionalProperties, self).setUp()
self.config(allow_additional_image_properties=False)
self.deserializer = glance.api.v2.images.RequestDeserializer()
def test_create_with_additional_properties_disallowed(self):
self.config(allow_additional_image_properties=False)
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'foo': 'bar'})
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create, request)
def test_update(self):
request = unit_test_utils.get_fake_request()
request.content_type = 'application/openstack-images-v2.1-json-patch'
doc = [{'op': 'add', 'path': '/foo', 'value': 'bar'}]
request.body = jsonutils.dump_as_bytes(doc)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
class TestImagesSerializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializer, self).setUp()
self.serializer = glance.api.v2.images.ResponseSerializer()
self.fixtures = [
# NOTE(bcwaldon): This first fixture has every property defined
_domain_fixture(UUID1, name='image-1', size=1024,
virtual_size=3072, created_at=DATETIME,
updated_at=DATETIME, owner=TENANT1,
visibility='public', container_format='ami',
tags=['one', 'two'], disk_format='ami',
min_ram=128, min_disk=10,
checksum='ca425b88f047ce8ec45ee90e813ada91'),
# NOTE(bcwaldon): This second fixture depends on default behavior
# and sets most values to None
_domain_fixture(UUID2, created_at=DATETIME, updated_at=DATETIME),
]
def test_index(self):
expected = {
'images': [
{
'id': UUID1,
'name': 'image-1',
'status': 'queued',
'visibility': 'public',
'protected': False,
'tags': set(['one', 'two']),
'size': 1024,
'virtual_size': 3072,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image',
'owner': '6838eb7b-6ded-434a-882c-b344c77fe8df',
},
{
'id': UUID2,
'status': 'queued',
'visibility': 'private',
'protected': False,
'tags': set([]),
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'size': None,
'name': None,
'owner': None,
'min_ram': None,
'min_disk': None,
'checksum': None,
'disk_format': None,
'virtual_size': None,
'container_format': None,
},
],
'first': '/v2/images',
'schema': '/v2/schemas/images',
}
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
result = {'images': self.fixtures}
self.serializer.index(response, result)
actual = jsonutils.loads(response.body)
for image in actual['images']:
image['tags'] = set(image['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_index_next_marker(self):
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
result = {'images': self.fixtures, 'next_marker': UUID2}
self.serializer.index(response, result)
output = jsonutils.loads(response.body)
self.assertEqual('/v2/images?marker=%s' % UUID2, output['next'])
def test_index_carries_query_parameters(self):
url = '/v2/images?limit=10&sort_key=id&sort_dir=asc'
request = webob.Request.blank(url)
response = webob.Response(request=request)
result = {'images': self.fixtures, 'next_marker': UUID2}
self.serializer.index(response, result)
output = jsonutils.loads(response.body)
expected_url = '/v2/images?limit=10&sort_dir=asc&sort_key=id'
self.assertEqual(unit_test_utils.sort_url_by_qs_keys(expected_url),
unit_test_utils.sort_url_by_qs_keys(output['first']))
expect_next = '/v2/images?limit=10&marker=%s&sort_dir=asc&sort_key=id'
self.assertEqual(unit_test_utils.sort_url_by_qs_keys(
expect_next % UUID2),
unit_test_utils.sort_url_by_qs_keys(output['next']))
def test_index_forbidden_get_image_location(self):
"""Make sure the serializer works fine.
No mater if current user is authorized to get image location if the
show_multiple_locations is False.
"""
class ImageLocations(object):
def __len__(self):
raise exception.Forbidden()
self.config(show_multiple_locations=False)
self.config(show_image_direct_url=False)
url = '/v2/images?limit=10&sort_key=id&sort_dir=asc'
request = webob.Request.blank(url)
response = webob.Response(request=request)
result = {'images': self.fixtures}
self.assertEqual(http.OK, response.status_int)
# The image index should work though the user is forbidden
result['images'][0].locations = ImageLocations()
self.serializer.index(response, result)
self.assertEqual(http.OK, response.status_int)
def test_show_full_fixture(self):
expected = {
'id': UUID1,
'name': 'image-1',
'status': 'queued',
'visibility': 'public',
'protected': False,
'tags': set(['one', 'two']),
'size': 1024,
'virtual_size': 3072,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image',
'owner': '6838eb7b-6ded-434a-882c-b344c77fe8df',
}
response = webob.Response()
self.serializer.show(response, self.fixtures[0])
actual = jsonutils.loads(response.body)
actual['tags'] = set(actual['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_show_minimal_fixture(self):
expected = {
'id': UUID2,
'status': 'queued',
'visibility': 'private',
'protected': False,
'tags': [],
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'size': None,
'name': None,
'owner': None,
'min_ram': None,
'min_disk': None,
'checksum': None,
'disk_format': None,
'virtual_size': None,
'container_format': None,
}
response = webob.Response()
self.serializer.show(response, self.fixtures[1])
self.assertEqual(expected, jsonutils.loads(response.body))
def test_create(self):
expected = {
'id': UUID1,
'name': 'image-1',
'status': 'queued',
'visibility': 'public',
'protected': False,
'tags': ['one', 'two'],
'size': 1024,
'virtual_size': 3072,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image',
'owner': '6838eb7b-6ded-434a-882c-b344c77fe8df',
}
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
self.assertEqual(http.CREATED, response.status_int)
actual = jsonutils.loads(response.body)
actual['tags'] = sorted(actual['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
self.assertEqual('/v2/images/%s' % UUID1, response.location)
def test_create_has_import_methods_header(self):
# NOTE(rosmaita): enabled_import_methods is defined as type
# oslo.config.cfg.ListOpt, so it is stored internally as a list
# but is converted to a string for output in the HTTP header
header_name = 'OpenStack-image-import-methods'
# check multiple methods
enabled_methods = ['one', 'two', 'three']
self.config(enabled_import_methods=enabled_methods)
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
self.assertEqual(http.CREATED, response.status_int)
header_value = response.headers.get(header_name)
self.assertIsNotNone(header_value)
self.assertItemsEqual(enabled_methods, header_value.split(','))
# check single method
self.config(enabled_import_methods=['swift-party-time'])
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
self.assertEqual(http.CREATED, response.status_int)
header_value = response.headers.get(header_name)
self.assertIsNotNone(header_value)
self.assertEqual('swift-party-time', header_value)
# no header for empty config value
self.config(enabled_import_methods=[])
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
self.assertEqual(http.CREATED, response.status_int)
headers = response.headers.keys()
self.assertNotIn(header_name, headers)
# TODO(rosmaita): remove this test when the enable_image_import
# option is removed
def test_create_has_no_import_methods_header(self):
header_name = 'OpenStack-image-import-methods'
self.config(enable_image_import=False)
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
self.assertEqual(http.CREATED, response.status_int)
headers = response.headers.keys()
self.assertNotIn(header_name, headers)
def test_update(self):
expected = {
'id': UUID1,
'name': 'image-1',
'status': 'queued',
'visibility': 'public',
'protected': False,
'tags': set(['one', 'two']),
'size': 1024,
'virtual_size': 3072,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image',
'owner': '6838eb7b-6ded-434a-882c-b344c77fe8df',
}
response = webob.Response()
self.serializer.update(response, self.fixtures[0])
actual = jsonutils.loads(response.body)
actual['tags'] = set(actual['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_import_image(self):
response = webob.Response()
self.serializer.import_image(response, {})
self.assertEqual(http.ACCEPTED, response.status_int)
self.assertEqual('0', response.headers['Content-Length'])
class TestImagesSerializerWithUnicode(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerWithUnicode, self).setUp()
self.serializer = glance.api.v2.images.ResponseSerializer()
self.fixtures = [
# NOTE(bcwaldon): This first fixture has every property defined
_domain_fixture(UUID1, **{
'name': u'OpenStack\u2122-1',
'size': 1024,
'virtual_size': 3072,
'tags': [u'\u2160', u'\u2161'],
'created_at': DATETIME,
'updated_at': DATETIME,
'owner': TENANT1,
'visibility': 'public',
'container_format': 'ami',
'disk_format': 'ami',
'min_ram': 128,
'min_disk': 10,
'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
'extra_properties': {'lang': u'Fran\u00E7ais',
u'dispos\u00E9': u'f\u00E2ch\u00E9'},
}),
]
def test_index(self):
expected = {
u'images': [
{
u'id': UUID1,
u'name': u'OpenStack\u2122-1',
u'status': u'queued',
u'visibility': u'public',
u'protected': False,
u'tags': [u'\u2160', u'\u2161'],
u'size': 1024,
u'virtual_size': 3072,
u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
u'container_format': u'ami',
u'disk_format': u'ami',
u'min_ram': 128,
u'min_disk': 10,
u'created_at': six.text_type(ISOTIME),
u'updated_at': six.text_type(ISOTIME),
u'self': u'/v2/images/%s' % UUID1,
u'file': u'/v2/images/%s/file' % UUID1,
u'schema': u'/v2/schemas/image',
u'lang': u'Fran\u00E7ais',
u'dispos\u00E9': u'f\u00E2ch\u00E9',
u'owner': u'6838eb7b-6ded-434a-882c-b344c77fe8df',
},
],
u'first': u'/v2/images',
u'schema': u'/v2/schemas/images',
}
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
result = {u'images': self.fixtures}
self.serializer.index(response, result)
actual = jsonutils.loads(response.body)
actual['images'][0]['tags'] = sorted(actual['images'][0]['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_show_full_fixture(self):
expected = {
u'id': UUID1,
u'name': u'OpenStack\u2122-1',
u'status': u'queued',
u'visibility': u'public',
u'protected': False,
u'tags': set([u'\u2160', u'\u2161']),
u'size': 1024,
u'virtual_size': 3072,
u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
u'container_format': u'ami',
u'disk_format': u'ami',
u'min_ram': 128,
u'min_disk': 10,
u'created_at': six.text_type(ISOTIME),
u'updated_at': six.text_type(ISOTIME),
u'self': u'/v2/images/%s' % UUID1,
u'file': u'/v2/images/%s/file' % UUID1,
u'schema': u'/v2/schemas/image',
u'lang': u'Fran\u00E7ais',
u'dispos\u00E9': u'f\u00E2ch\u00E9',
u'owner': u'6838eb7b-6ded-434a-882c-b344c77fe8df',
}
response = webob.Response()
self.serializer.show(response, self.fixtures[0])
actual = jsonutils.loads(response.body)
actual['tags'] = set(actual['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_create(self):
expected = {
u'id': UUID1,
u'name': u'OpenStack\u2122-1',
u'status': u'queued',
u'visibility': u'public',
u'protected': False,
u'tags': [u'\u2160', u'\u2161'],
u'size': 1024,
u'virtual_size': 3072,
u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
u'container_format': u'ami',
u'disk_format': u'ami',
u'min_ram': 128,
u'min_disk': 10,
u'created_at': six.text_type(ISOTIME),
u'updated_at': six.text_type(ISOTIME),
u'self': u'/v2/images/%s' % UUID1,
u'file': u'/v2/images/%s/file' % UUID1,
u'schema': u'/v2/schemas/image',
u'lang': u'Fran\u00E7ais',
u'dispos\u00E9': u'f\u00E2ch\u00E9',
u'owner': u'6838eb7b-6ded-434a-882c-b344c77fe8df',
}
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
self.assertEqual(http.CREATED, response.status_int)
actual = jsonutils.loads(response.body)
actual['tags'] = sorted(actual['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
self.assertEqual('/v2/images/%s' % UUID1, response.location)
def test_update(self):
expected = {
u'id': UUID1,
u'name': u'OpenStack\u2122-1',
u'status': u'queued',
u'visibility': u'public',
u'protected': False,
u'tags': set([u'\u2160', u'\u2161']),
u'size': 1024,
u'virtual_size': 3072,
u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
u'container_format': u'ami',
u'disk_format': u'ami',
u'min_ram': 128,
u'min_disk': 10,
u'created_at': six.text_type(ISOTIME),
u'updated_at': six.text_type(ISOTIME),
u'self': u'/v2/images/%s' % UUID1,
u'file': u'/v2/images/%s/file' % UUID1,
u'schema': u'/v2/schemas/image',
u'lang': u'Fran\u00E7ais',
u'dispos\u00E9': u'f\u00E2ch\u00E9',
u'owner': u'6838eb7b-6ded-434a-882c-b344c77fe8df',
}
response = webob.Response()
self.serializer.update(response, self.fixtures[0])
actual = jsonutils.loads(response.body)
actual['tags'] = set(actual['tags'])
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerWithExtendedSchema, self).setUp()
self.config(allow_additional_image_properties=False)
custom_image_properties = {
'color': {
'type': 'string',
'enum': ['red', 'green'],
},
}
schema = glance.api.v2.images.get_schema(custom_image_properties)
self.serializer = glance.api.v2.images.ResponseSerializer(schema)
props = dict(color='green', mood='grouchy')
self.fixture = _domain_fixture(
UUID2, name='image-2', owner=TENANT2,
checksum='ca425b88f047ce8ec45ee90e813ada91',
created_at=DATETIME, updated_at=DATETIME, size=1024,
virtual_size=3072, extra_properties=props)
def test_show(self):
expected = {
'id': UUID2,
'name': 'image-2',
'status': 'queued',
'visibility': 'private',
'protected': False,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'tags': [],
'size': 1024,
'virtual_size': 3072,
'owner': '2c014f32-55eb-467d-8fcb-4bd706012f81',
'color': 'green',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'min_ram': None,
'min_disk': None,
'disk_format': None,
'container_format': None,
}
response = webob.Response()
self.serializer.show(response, self.fixture)
self.assertEqual(expected, jsonutils.loads(response.body))
def test_show_reports_invalid_data(self):
self.fixture.extra_properties['color'] = 'invalid'
expected = {
'id': UUID2,
'name': 'image-2',
'status': 'queued',
'visibility': 'private',
'protected': False,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'tags': [],
'size': 1024,
'virtual_size': 3072,
'owner': '2c014f32-55eb-467d-8fcb-4bd706012f81',
'color': 'invalid',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'min_ram': None,
'min_disk': None,
'disk_format': None,
'container_format': None,
}
response = webob.Response()
self.serializer.show(response, self.fixture)
self.assertEqual(expected, jsonutils.loads(response.body))
class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerWithAdditionalProperties, self).setUp()
self.config(allow_additional_image_properties=True)
self.fixture = _domain_fixture(
UUID2, name='image-2', owner=TENANT2,
checksum='ca425b88f047ce8ec45ee90e813ada91',
created_at=DATETIME, updated_at=DATETIME, size=1024,
virtual_size=3072, extra_properties={'marx': 'groucho'})
def test_show(self):
serializer = glance.api.v2.images.ResponseSerializer()
expected = {
'id': UUID2,
'name': 'image-2',
'status': 'queued',
'visibility': 'private',
'protected': False,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'marx': 'groucho',
'tags': [],
'size': 1024,
'virtual_size': 3072,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'owner': '2c014f32-55eb-467d-8fcb-4bd706012f81',
'min_ram': None,
'min_disk': None,
'disk_format': None,
'container_format': None,
}
response = webob.Response()
serializer.show(response, self.fixture)
self.assertEqual(expected, jsonutils.loads(response.body))
def test_show_invalid_additional_property(self):
"""Ensure that the serializer passes
through invalid additional properties.
It must not complains with i.e. non-string.
"""
serializer = glance.api.v2.images.ResponseSerializer()
self.fixture.extra_properties['marx'] = 123
expected = {
'id': UUID2,
'name': 'image-2',
'status': 'queued',
'visibility': 'private',
'protected': False,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'marx': 123,
'tags': [],
'size': 1024,
'virtual_size': 3072,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'owner': '2c014f32-55eb-467d-8fcb-4bd706012f81',
'min_ram': None,
'min_disk': None,
'disk_format': None,
'container_format': None,
}
response = webob.Response()
serializer.show(response, self.fixture)
self.assertEqual(expected, jsonutils.loads(response.body))
def test_show_with_additional_properties_disabled(self):
self.config(allow_additional_image_properties=False)
serializer = glance.api.v2.images.ResponseSerializer()
expected = {
'id': UUID2,
'name': 'image-2',
'status': 'queued',
'visibility': 'private',
'protected': False,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'tags': [],
'size': 1024,
'virtual_size': 3072,
'owner': '2c014f32-55eb-467d-8fcb-4bd706012f81',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
'min_ram': None,
'min_disk': None,
'disk_format': None,
'container_format': None,
}
response = webob.Response()
serializer.show(response, self.fixture)
self.assertEqual(expected, jsonutils.loads(response.body))
class TestImagesSerializerDirectUrl(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerDirectUrl, self).setUp()
self.serializer = glance.api.v2.images.ResponseSerializer()
self.active_image = _domain_fixture(
UUID1, name='image-1', visibility='public',
status='active', size=1024, virtual_size=3072,
created_at=DATETIME, updated_at=DATETIME,
locations=[{'id': '1', 'url': 'http://some/fake/location',
'metadata': {}, 'status': 'active'}])
self.queued_image = _domain_fixture(
UUID2, name='image-2', status='active',
created_at=DATETIME, updated_at=DATETIME,
checksum='ca425b88f047ce8ec45ee90e813ada91')
self.location_data_image_url = 'http://abc.com/somewhere'
self.location_data_image_meta = {'key': 98231}
self.location_data_image = _domain_fixture(
UUID2, name='image-2', status='active',
created_at=DATETIME, updated_at=DATETIME,
locations=[{'id': '2',
'url': self.location_data_image_url,
'metadata': self.location_data_image_meta,
'status': 'active'}])
def _do_index(self):
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
self.serializer.index(response,
{'images': [self.active_image,
self.queued_image]})
return jsonutils.loads(response.body)['images']
def _do_show(self, image):
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
self.serializer.show(response, image)
return jsonutils.loads(response.body)
def test_index_store_location_enabled(self):
self.config(show_image_direct_url=True)
images = self._do_index()
# NOTE(markwash): ordering sanity check
self.assertEqual(UUID1, images[0]['id'])
self.assertEqual(UUID2, images[1]['id'])
self.assertEqual('http://some/fake/location', images[0]['direct_url'])
self.assertNotIn('direct_url', images[1])
def test_index_store_multiple_location_enabled(self):
self.config(show_multiple_locations=True)
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
self.serializer.index(response,
{'images': [self.location_data_image]}),
images = jsonutils.loads(response.body)['images']
location = images[0]['locations'][0]
self.assertEqual(location['url'], self.location_data_image_url)
self.assertEqual(location['metadata'], self.location_data_image_meta)
def test_index_store_location_explicitly_disabled(self):
self.config(show_image_direct_url=False)
images = self._do_index()
self.assertNotIn('direct_url', images[0])
self.assertNotIn('direct_url', images[1])
def test_show_location_enabled(self):
self.config(show_image_direct_url=True)
image = self._do_show(self.active_image)
self.assertEqual('http://some/fake/location', image['direct_url'])
def test_show_location_enabled_but_not_set(self):
self.config(show_image_direct_url=True)
image = self._do_show(self.queued_image)
self.assertNotIn('direct_url', image)
def test_show_location_explicitly_disabled(self):
self.config(show_image_direct_url=False)
image = self._do_show(self.active_image)
self.assertNotIn('direct_url', image)
class TestImageSchemaFormatConfiguration(test_utils.BaseTestCase):
def test_default_disk_formats(self):
schema = glance.api.v2.images.get_schema()
expected = [None, 'ami', 'ari', 'aki', 'vhd', 'vhdx', 'vmdk',
'raw', 'qcow2', 'vdi', 'iso', 'ploop']
actual = schema.properties['disk_format']['enum']
self.assertEqual(expected, actual)
def test_custom_disk_formats(self):
self.config(disk_formats=['gabe'], group="image_format")
schema = glance.api.v2.images.get_schema()
expected = [None, 'gabe']
actual = schema.properties['disk_format']['enum']
self.assertEqual(expected, actual)
def test_default_container_formats(self):
schema = glance.api.v2.images.get_schema()
expected = [None, 'ami', 'ari', 'aki', 'bare', 'ovf', 'ova', 'docker']
actual = schema.properties['container_format']['enum']
self.assertEqual(expected, actual)
def test_custom_container_formats(self):
self.config(container_formats=['mark'], group="image_format")
schema = glance.api.v2.images.get_schema()
expected = [None, 'mark']
actual = schema.properties['container_format']['enum']
self.assertEqual(expected, actual)
class TestImageSchemaDeterminePropertyBasis(test_utils.BaseTestCase):
def test_custom_property_marked_as_non_base(self):
self.config(allow_additional_image_properties=False)
custom_image_properties = {
'pants': {
'type': 'string',
},
}
schema = glance.api.v2.images.get_schema(custom_image_properties)
self.assertFalse(schema.properties['pants'].get('is_base', True))
def test_base_property_marked_as_base(self):
schema = glance.api.v2.images.get_schema()
self.assertTrue(schema.properties['disk_format'].get('is_base', True))
glance-16.0.1/glance/tests/unit/v2/__init__.py 0000666 0001750 0001750 00000000000 13267672245 021072 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/v2/test_discovery_image_import.py 0000666 0001750 0001750 00000003330 13267672245 025146 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2017 RedHat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import webob.exc
import glance.api.v2.discovery
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
class TestInfoControllers(test_utils.BaseTestCase):
def setUp(self):
super(TestInfoControllers, self).setUp()
self.controller = glance.api.v2.discovery.InfoController()
def test_get_import_info_when_import_not_enabled(self):
"""When import not enabled, should return 404 just like v2.5"""
self.config(enable_image_import=False)
req = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.get_image_import,
req)
def test_get_import_info(self):
# TODO(rosmaita): change this when import methods are
# listed in the config file
import_methods = ['glance-direct', 'web-download']
self.config(enable_image_import=True)
req = unit_test_utils.get_fake_request()
output = self.controller.get_image_import(req)
self.assertIn('import-methods', output)
self.assertEqual(import_methods, output['import-methods']['value'])
glance-16.0.1/glance/tests/unit/v2/test_image_data_resource.py 0000666 0001750 0001750 00000124531 13267672245 024374 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from cursive import exception as cursive_exception
import glance_store
from glance_store._drivers import filesystem
import mock
import six
from six.moves import http_client as http
import webob
import glance.api.policy
import glance.api.v2.image_data
from glance.common import exception
from glance.common import wsgi
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
class Raise(object):
def __init__(self, exc):
self.exc = exc
def __call__(self, *args, **kwargs):
raise self.exc
class FakeImage(object):
def __init__(self, image_id=None, data=None, checksum=None, size=0,
virtual_size=0, locations=None, container_format='bear',
disk_format='rawr', status=None):
self.image_id = image_id
self.data = data
self.checksum = checksum
self.size = size
self.virtual_size = virtual_size
self.locations = locations
self.container_format = container_format
self.disk_format = disk_format
self._status = status
@property
def status(self):
return self._status
@status.setter
def status(self, value):
if isinstance(self._status, BaseException):
raise self._status
else:
self._status = value
def get_data(self, offset=0, chunk_size=None):
if chunk_size:
return self.data[offset:offset + chunk_size]
return self.data[offset:]
def set_data(self, data, size=None):
self.data = ''.join(data)
self.size = size
self.status = 'modified-by-fake'
class FakeImageRepo(object):
def __init__(self, result=None):
self.result = result
def get(self, image_id):
if isinstance(self.result, BaseException):
raise self.result
else:
return self.result
def save(self, image, from_state=None):
self.saved_image = image
class FakeGateway(object):
def __init__(self, db=None, store=None, notifier=None,
policy=None, repo=None):
self.db = db
self.store = store
self.notifier = notifier
self.policy = policy
self.repo = repo
def get_repo(self, context):
return self.repo
class TestImagesController(base.StoreClearingUnitTest):
def setUp(self):
super(TestImagesController, self).setUp()
self.config(debug=True)
self.image_repo = FakeImageRepo()
db = unit_test_utils.FakeDB()
policy = unit_test_utils.FakePolicyEnforcer()
notifier = unit_test_utils.FakeNotifier()
store = unit_test_utils.FakeStoreAPI()
self.controller = glance.api.v2.image_data.ImageDataController()
self.controller.gateway = FakeGateway(db, store, notifier, policy,
self.image_repo)
def test_download(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd',
locations=[{'url': 'http://example.com/image',
'metadata': {}, 'status': 'active'}])
self.image_repo.result = image
image = self.controller.download(request, unit_test_utils.UUID1)
self.assertEqual('abcd', image.image_id)
def test_download_deactivated(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd',
status='deactivated',
locations=[{'url': 'http://example.com/image',
'metadata': {}, 'status': 'active'}])
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPForbidden, self.controller.download,
request, str(uuid.uuid4()))
def test_download_no_location(self):
# NOTE(mclaren): NoContent will be raised by the ResponseSerializer
# That's tested below.
request = unit_test_utils.get_fake_request()
self.image_repo.result = FakeImage('abcd')
image = self.controller.download(request, unit_test_utils.UUID2)
self.assertEqual('abcd', image.image_id)
def test_download_non_existent_image(self):
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.NotFound()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.download,
request, str(uuid.uuid4()))
def test_download_forbidden(self):
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.Forbidden()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.download,
request, str(uuid.uuid4()))
def test_download_ok_when_get_image_location_forbidden(self):
class ImageLocations(object):
def __len__(self):
raise exception.Forbidden()
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
self.image_repo.result = image
image.locations = ImageLocations()
image = self.controller.download(request, unit_test_utils.UUID1)
self.assertEqual('abcd', image.image_id)
def test_upload(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
self.image_repo.result = image
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
self.assertEqual('YYYY', image.data)
self.assertEqual(4, image.size)
def test_upload_status(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
self.image_repo.result = image
insurance = {'called': False}
def read_data():
insurance['called'] = True
self.assertEqual('saving', self.image_repo.saved_image.status)
yield 'YYYY'
self.controller.upload(request, unit_test_utils.UUID2,
read_data(), None)
self.assertTrue(insurance['called'])
self.assertEqual('modified-by-fake',
self.image_repo.saved_image.status)
def test_upload_no_size(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
self.image_repo.result = image
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', None)
self.assertEqual('YYYY', image.data)
self.assertIsNone(image.size)
@mock.patch.object(glance.api.policy.Enforcer, 'enforce')
def test_upload_image_forbidden(self, mock_enforce):
request = unit_test_utils.get_fake_request()
mock_enforce.side_effect = exception.Forbidden
self.assertRaises(webob.exc.HTTPForbidden, self.controller.upload,
request, unit_test_utils.UUID2, 'YYYY', 4)
mock_enforce.assert_called_once_with(request.context,
"upload_image",
{})
def test_upload_invalid(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
image.status = ValueError()
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
request, unit_test_utils.UUID1, 'YYYY', 4)
def test_upload_with_expired_token(self):
def side_effect(image, from_state=None):
if from_state == 'saving':
raise exception.NotAuthenticated()
mocked_save = mock.Mock(side_effect=side_effect)
mocked_delete = mock.Mock()
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
image.delete = mocked_delete
self.image_repo.result = image
self.image_repo.save = mocked_save
self.assertRaises(webob.exc.HTTPUnauthorized, self.controller.upload,
request, unit_test_utils.UUID1, 'YYYY', 4)
self.assertEqual(3, mocked_save.call_count)
mocked_delete.assert_called_once_with()
def test_upload_non_existent_image_during_save_initiates_deletion(self):
def fake_save_not_found(self, from_state=None):
raise exception.ImageNotFound()
def fake_save_conflict(self, from_state=None):
raise exception.Conflict()
for fun in [fake_save_not_found, fake_save_conflict]:
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd', locations=['http://example.com/image'])
self.image_repo.result = image
self.image_repo.save = fun
image.delete = mock.Mock()
self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
request, str(uuid.uuid4()), 'ABC', 3)
self.assertTrue(image.delete.called)
def test_upload_non_existent_image_raises_image_not_found_exception(self):
def fake_save(self, from_state=None):
raise exception.ImageNotFound()
def fake_delete():
raise exception.ImageNotFound()
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd', locations=['http://example.com/image'])
self.image_repo.result = image
self.image_repo.save = fake_save
image.delete = fake_delete
self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
request, str(uuid.uuid4()), 'ABC', 3)
def test_upload_non_existent_image_raises_store_not_found_exception(self):
def fake_save(self, from_state=None):
raise glance_store.NotFound()
def fake_delete():
raise exception.ImageNotFound()
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd', locations=['http://example.com/image'])
self.image_repo.result = image
self.image_repo.save = fake_save
image.delete = fake_delete
self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
request, str(uuid.uuid4()), 'ABC', 3)
def test_upload_non_existent_image_before_save(self):
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.NotFound()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.upload,
request, str(uuid.uuid4()), 'ABC', 3)
def test_upload_data_exists(self):
request = unit_test_utils.get_fake_request()
image = FakeImage()
exc = exception.InvalidImageStatusTransition(cur_status='active',
new_status='queued')
image.set_data = Raise(exc)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPConflict, self.controller.upload,
request, unit_test_utils.UUID1, 'YYYY', 4)
def test_upload_storage_full(self):
request = unit_test_utils.get_fake_request()
image = FakeImage()
image.set_data = Raise(glance_store.StorageFull)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.upload,
request, unit_test_utils.UUID2, 'YYYYYYY', 7)
def test_upload_signature_verification_fails(self):
request = unit_test_utils.get_fake_request()
image = FakeImage()
image.set_data = Raise(cursive_exception.SignatureVerificationError)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
request, unit_test_utils.UUID1, 'YYYY', 4)
self.assertEqual('killed', self.image_repo.saved_image.status)
def test_image_size_limit_exceeded(self):
request = unit_test_utils.get_fake_request()
image = FakeImage()
image.set_data = Raise(exception.ImageSizeLimitExceeded)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.upload,
request, unit_test_utils.UUID1, 'YYYYYYY', 7)
def test_upload_storage_quota_full(self):
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.StorageQuotaFull("message")
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.upload,
request, unit_test_utils.UUID1, 'YYYYYYY', 7)
def test_upload_storage_forbidden(self):
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER2)
image = FakeImage()
image.set_data = Raise(exception.Forbidden)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPForbidden, self.controller.upload,
request, unit_test_utils.UUID2, 'YY', 2)
def test_upload_storage_internal_error(self):
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.ServerError()
self.assertRaises(exception.ServerError,
self.controller.upload,
request, unit_test_utils.UUID1, 'ABC', 3)
def test_upload_storage_write_denied(self):
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER3)
image = FakeImage()
image.set_data = Raise(glance_store.StorageWriteDenied)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPServiceUnavailable,
self.controller.upload,
request, unit_test_utils.UUID2, 'YY', 2)
def test_upload_storage_store_disabled(self):
"""Test that uploading an image file raises StoreDisabled exception"""
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER3)
image = FakeImage()
image.set_data = Raise(glance_store.StoreAddDisabled)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPGone,
self.controller.upload,
request, unit_test_utils.UUID2, 'YY', 2)
@mock.patch("glance.common.trust_auth.TokenRefresher")
def test_upload_with_trusts(self, mock_refresher):
"""Test that uploading with registry correctly uses trusts"""
# initialize trust environment
self.config(data_api='glance.db.registry.api')
refresher = mock.MagicMock()
mock_refresher.return_value = refresher
refresher.refresh_token.return_value = "fake_token"
# request an image upload
request = unit_test_utils.get_fake_request()
request.environ['keystone.token_auth'] = mock.MagicMock()
request.environ['keystone.token_info'] = {
'token': {
'roles': [{'name': 'FakeRole', 'id': 'FakeID'}]
}
}
image = FakeImage('abcd')
self.image_repo.result = image
mock_fake_save = mock.Mock()
mock_fake_save.side_effect = [None, exception.NotAuthenticated, None]
temp_save = FakeImageRepo.save
# mocking save to raise NotAuthenticated on the second call
FakeImageRepo.save = mock_fake_save
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
# check image data
self.assertEqual('YYYY', image.data)
self.assertEqual(4, image.size)
FakeImageRepo.save = temp_save
# check that token has been correctly acquired and deleted
mock_refresher.assert_called_once_with(
request.environ['keystone.token_auth'],
request.context.tenant, ['FakeRole'])
refresher.refresh_token.assert_called_once_with()
refresher.release_resources.assert_called_once_with()
self.assertEqual("fake_token", request.context.auth_token)
@mock.patch("glance.common.trust_auth.TokenRefresher")
def test_upload_with_trusts_fails(self, mock_refresher):
"""Test upload with registry if trust was not successfully created"""
# initialize trust environment
self.config(data_api='glance.db.registry.api')
mock_refresher().side_effect = Exception()
# request an image upload
request = unit_test_utils.get_fake_request()
image = FakeImage('abcd')
self.image_repo.result = image
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
# check image data
self.assertEqual('YYYY', image.data)
self.assertEqual(4, image.size)
# check that the token has not been updated
self.assertEqual(0, mock_refresher().refresh_token.call_count)
def _test_upload_download_prepare_notification(self):
request = unit_test_utils.get_fake_request()
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
output = self.controller.download(request, unit_test_utils.UUID2)
output_log = self.notifier.get_logs()
prepare_payload = output['meta'].copy()
prepare_payload['checksum'] = None
prepare_payload['size'] = None
prepare_payload['virtual_size'] = None
prepare_payload['location'] = None
prepare_payload['status'] = 'queued'
del prepare_payload['updated_at']
prepare_log = {
'notification_type': "INFO",
'event_type': "image.prepare",
'payload': prepare_payload,
}
self.assertEqual(3, len(output_log))
prepare_updated_at = output_log[0]['payload']['updated_at']
del output_log[0]['payload']['updated_at']
self.assertLessEqual(prepare_updated_at, output['meta']['updated_at'])
self.assertEqual(prepare_log, output_log[0])
def _test_upload_download_upload_notification(self):
request = unit_test_utils.get_fake_request()
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
output = self.controller.download(request, unit_test_utils.UUID2)
output_log = self.notifier.get_logs()
upload_payload = output['meta'].copy()
upload_log = {
'notification_type': "INFO",
'event_type': "image.upload",
'payload': upload_payload,
}
self.assertEqual(3, len(output_log))
self.assertEqual(upload_log, output_log[1])
def _test_upload_download_activate_notification(self):
request = unit_test_utils.get_fake_request()
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
output = self.controller.download(request, unit_test_utils.UUID2)
output_log = self.notifier.get_logs()
activate_payload = output['meta'].copy()
activate_log = {
'notification_type': "INFO",
'event_type': "image.activate",
'payload': activate_payload,
}
self.assertEqual(3, len(output_log))
self.assertEqual(activate_log, output_log[2])
def test_restore_image_when_upload_failed(self):
request = unit_test_utils.get_fake_request()
image = FakeImage('fake')
image.set_data = Raise(glance_store.StorageWriteDenied)
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPServiceUnavailable,
self.controller.upload,
request, unit_test_utils.UUID2, 'ZZZ', 3)
self.assertEqual('queued', self.image_repo.saved_image.status)
@mock.patch.object(filesystem.Store, 'add')
def test_restore_image_when_staging_failed(self, mock_store_add):
mock_store_add.side_effect = glance_store.StorageWriteDenied()
request = unit_test_utils.get_fake_request()
image_id = str(uuid.uuid4())
image = FakeImage('fake')
self.image_repo.result = image
self.assertRaises(webob.exc.HTTPServiceUnavailable,
self.controller.stage,
request, image_id, 'YYYYYYY', 7)
self.assertEqual('queued', self.image_repo.saved_image.status)
def test_stage(self):
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
with mock.patch.object(filesystem.Store, 'add'):
self.controller.stage(request, image_id, 'YYYY', 4)
self.assertEqual('uploading', image.status)
self.assertEqual(0, image.size)
def test_image_already_on_staging(self):
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
with mock.patch.object(filesystem.Store, 'add') as mock_store_add:
self.controller.stage(request, image_id, 'YYYY', 4)
self.assertEqual('uploading', image.status)
mock_store_add.side_effect = glance_store.Duplicate()
self.assertEqual(0, image.size)
self.assertRaises(webob.exc.HTTPConflict, self.controller.stage,
request, image_id, 'YYYY', 4)
@mock.patch.object(glance_store.driver.Store, 'configure')
def test_image_stage_raises_bad_store_uri(self, mock_store_configure):
mock_store_configure.side_effect = AttributeError()
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
self.assertRaises(exception.BadStoreUri, self.controller.stage,
request, image_id, 'YYYY', 4)
@mock.patch.object(filesystem.Store, 'add')
def test_image_stage_raises_storage_full(self, mock_store_add):
mock_store_add.side_effect = glance_store.StorageFull()
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
with mock.patch.object(self.controller, "_unstage"):
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.stage,
request, image_id, 'YYYYYYY', 7)
@mock.patch.object(filesystem.Store, 'add')
def test_image_stage_raises_storage_quota_full(self, mock_store_add):
mock_store_add.side_effect = exception.StorageQuotaFull("message")
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
with mock.patch.object(self.controller, "_unstage"):
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.stage,
request, image_id, 'YYYYYYY', 7)
@mock.patch.object(filesystem.Store, 'add')
def test_image_stage_raises_storage_write_denied(self, mock_store_add):
mock_store_add.side_effect = glance_store.StorageWriteDenied()
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
with mock.patch.object(self.controller, "_unstage"):
self.assertRaises(webob.exc.HTTPServiceUnavailable,
self.controller.stage,
request, image_id, 'YYYYYYY', 7)
def test_image_stage_raises_internal_error(self):
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.ServerError()
self.assertRaises(exception.ServerError,
self.controller.stage,
request, image_id, 'YYYYYYY', 7)
def test_image_stage_non_existent_image(self):
request = unit_test_utils.get_fake_request()
self.image_repo.result = exception.NotFound()
self.assertRaises(webob.exc.HTTPNotFound, self.controller.stage,
request, str(uuid.uuid4()), 'ABC', 3)
@mock.patch.object(filesystem.Store, 'add')
def test_image_stage_raises_image_size_exceeded(self, mock_store_add):
mock_store_add.side_effect = exception.ImageSizeLimitExceeded()
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
with mock.patch.object(self.controller, "_unstage"):
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.stage,
request, image_id, 'YYYYYYY', 7)
@mock.patch.object(filesystem.Store, 'add')
def test_image_stage_invalid_image_transition(self, mock_store_add):
image_id = str(uuid.uuid4())
request = unit_test_utils.get_fake_request()
image = FakeImage(image_id=image_id)
self.image_repo.result = image
self.controller.stage(request, image_id, 'YYYY', 4)
self.assertEqual('uploading', image.status)
self.assertEqual(0, image.size)
# try staging again
mock_store_add.side_effect = exception.InvalidImageStatusTransition(
cur_status='uploading', new_status='uploading')
self.assertRaises(webob.exc.HTTPConflict, self.controller.stage,
request, image_id, 'YYYY', 4)
class TestImageDataDeserializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImageDataDeserializer, self).setUp()
self.deserializer = glance.api.v2.image_data.RequestDeserializer()
def test_upload(self):
request = unit_test_utils.get_fake_request()
request.headers['Content-Type'] = 'application/octet-stream'
request.body = b'YYY'
request.headers['Content-Length'] = 3
output = self.deserializer.upload(request)
data = output.pop('data')
self.assertEqual(b'YYY', data.read())
expected = {'size': 3}
self.assertEqual(expected, output)
def test_upload_chunked(self):
request = unit_test_utils.get_fake_request()
request.headers['Content-Type'] = 'application/octet-stream'
# If we use body_file, webob assumes we want to do a chunked upload,
# ignoring the Content-Length header
request.body_file = six.StringIO('YYY')
output = self.deserializer.upload(request)
data = output.pop('data')
self.assertEqual('YYY', data.read())
expected = {'size': None}
self.assertEqual(expected, output)
def test_upload_chunked_with_content_length(self):
request = unit_test_utils.get_fake_request()
request.headers['Content-Type'] = 'application/octet-stream'
request.body_file = six.BytesIO(b'YYY')
# The deserializer shouldn't care if the Content-Length is
# set when the user is attempting to send chunked data.
request.headers['Content-Length'] = 3
output = self.deserializer.upload(request)
data = output.pop('data')
self.assertEqual(b'YYY', data.read())
expected = {'size': 3}
self.assertEqual(expected, output)
def test_upload_with_incorrect_content_length(self):
request = unit_test_utils.get_fake_request()
request.headers['Content-Type'] = 'application/octet-stream'
# The deserializer shouldn't care if the Content-Length and
# actual request body length differ. That job is left up
# to the controller
request.body = b'YYY'
request.headers['Content-Length'] = 4
output = self.deserializer.upload(request)
data = output.pop('data')
self.assertEqual(b'YYY', data.read())
expected = {'size': 4}
self.assertEqual(expected, output)
def test_upload_wrong_content_type(self):
request = unit_test_utils.get_fake_request()
request.headers['Content-Type'] = 'application/json'
request.body = b'YYYYY'
self.assertRaises(webob.exc.HTTPUnsupportedMediaType,
self.deserializer.upload, request)
request = unit_test_utils.get_fake_request()
request.headers['Content-Type'] = 'application/octet-st'
request.body = b'YYYYY'
self.assertRaises(webob.exc.HTTPUnsupportedMediaType,
self.deserializer.upload, request)
def test_stage(self):
self.config(enable_image_import=True)
req = unit_test_utils.get_fake_request()
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['Content-Length'] = 4
req.body_file = six.BytesIO(b'YYYY')
output = self.deserializer.stage(req)
data = output.pop('data')
self.assertEqual(b'YYYY', data.read())
def test_stage_if_image_import_is_disabled(self):
self.config(enable_image_import=False)
req = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.deserializer.stage,
req)
def test_stage_raises_invalid_content_type(self):
# TODO(abhishekk): change this when import methods are
# listed in the config file
self.config(enable_image_import=True)
req = unit_test_utils.get_fake_request()
req.headers['Content-Type'] = 'application/json'
self.assertRaises(webob.exc.HTTPUnsupportedMediaType,
self.deserializer.stage,
req)
class TestImageDataSerializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImageDataSerializer, self).setUp()
self.serializer = glance.api.v2.image_data.ResponseSerializer()
def test_download(self):
request = wsgi.Request.blank('/')
request.environ = {}
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
self.serializer.download(response, image)
self.assertEqual(b'ZZZ', response.body)
self.assertEqual('3', response.headers['Content-Length'])
self.assertNotIn('Content-MD5', response.headers)
self.assertEqual('application/octet-stream',
response.headers['Content-Type'])
def test_range_requests_for_image_downloads(self):
"""
Test partial download 'Range' requests for images (random image access)
"""
def download_successful_Range(d_range):
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Range'] = d_range
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'X', b'Y', b'Z'])
self.serializer.download(response, image)
self.assertEqual(206, response.status_code)
self.assertEqual('2', response.headers['Content-Length'])
self.assertEqual('bytes 1-2/3', response.headers['Content-Range'])
self.assertEqual(b'YZ', response.body)
download_successful_Range('bytes=1-2')
download_successful_Range('bytes=1-')
download_successful_Range('bytes=1-3')
download_successful_Range('bytes=-2')
download_successful_Range('bytes=1-100')
def full_image_download_w_range(d_range):
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Range'] = d_range
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'X', b'Y', b'Z'])
self.serializer.download(response, image)
self.assertEqual(206, response.status_code)
self.assertEqual('3', response.headers['Content-Length'])
self.assertEqual('bytes 0-2/3', response.headers['Content-Range'])
self.assertEqual(b'XYZ', response.body)
full_image_download_w_range('bytes=0-')
full_image_download_w_range('bytes=0-2')
full_image_download_w_range('bytes=0-3')
full_image_download_w_range('bytes=-3')
full_image_download_w_range('bytes=-4')
full_image_download_w_range('bytes=0-100')
full_image_download_w_range('bytes=-100')
def download_failures_Range(d_range):
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Range'] = d_range
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
self.serializer.download,
response, image)
return
download_failures_Range('bytes=4-1')
download_failures_Range('bytes=4-')
download_failures_Range('bytes=3-')
download_failures_Range('bytes=1')
download_failures_Range('bytes=100')
download_failures_Range('bytes=100-')
download_failures_Range('bytes=')
def test_multi_range_requests_raises_bad_request_error(self):
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Range'] = 'bytes=0-0,-1'
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
self.assertRaises(webob.exc.HTTPBadRequest,
self.serializer.download,
response, image)
def test_download_failure_with_valid_range(self):
with mock.patch.object(glance.api.policy.ImageProxy,
'get_data') as mock_get_data:
mock_get_data.side_effect = glance_store.NotFound(image="image")
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Range'] = 'bytes=1-2'
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
image.get_data = mock_get_data
self.assertRaises(webob.exc.HTTPNoContent,
self.serializer.download,
response, image)
def test_content_range_requests_for_image_downloads(self):
"""
Even though Content-Range is incorrect on requests, we support it
for backward compatibility with clients written for pre-Pike
Glance.
The following test is for 'Content-Range' requests, which we have
to ensure that we prevent regression.
"""
def download_successful_ContentRange(d_range):
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Content-Range'] = d_range
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'X', b'Y', b'Z'])
self.serializer.download(response, image)
self.assertEqual(206, response.status_code)
self.assertEqual('2', response.headers['Content-Length'])
self.assertEqual('bytes 1-2/3', response.headers['Content-Range'])
self.assertEqual(b'YZ', response.body)
download_successful_ContentRange('bytes 1-2/3')
download_successful_ContentRange('bytes 1-2/*')
def download_failures_ContentRange(d_range):
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Content-Range'] = d_range
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
self.serializer.download,
response, image)
return
download_failures_ContentRange('bytes -3/3')
download_failures_ContentRange('bytes 1-/3')
download_failures_ContentRange('bytes 1-3/3')
download_failures_ContentRange('bytes 1-4/3')
download_failures_ContentRange('bytes 1-4/*')
download_failures_ContentRange('bytes 4-1/3')
download_failures_ContentRange('bytes 4-1/*')
download_failures_ContentRange('bytes 4-8/*')
download_failures_ContentRange('bytes 4-8/10')
download_failures_ContentRange('bytes 4-8/3')
def test_download_failure_with_valid_content_range(self):
with mock.patch.object(glance.api.policy.ImageProxy,
'get_data') as mock_get_data:
mock_get_data.side_effect = glance_store.NotFound(image="image")
request = wsgi.Request.blank('/')
request.environ = {}
request.headers['Content-Range'] = 'bytes %s-%s/3' % (1, 2)
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
image.get_data = mock_get_data
self.assertRaises(webob.exc.HTTPNoContent,
self.serializer.download,
response, image)
def test_download_with_checksum(self):
request = wsgi.Request.blank('/')
request.environ = {}
response = webob.Response()
response.request = request
checksum = '0745064918b49693cca64d6b6a13d28a'
image = FakeImage(size=3, checksum=checksum, data=[b'Z', b'Z', b'Z'])
self.serializer.download(response, image)
self.assertEqual(b'ZZZ', response.body)
self.assertEqual('3', response.headers['Content-Length'])
self.assertEqual(checksum, response.headers['Content-MD5'])
self.assertEqual('application/octet-stream',
response.headers['Content-Type'])
def test_download_forbidden(self):
"""Make sure the serializer can return 403 forbidden error instead of
500 internal server error.
"""
def get_data(*args, **kwargs):
raise exception.Forbidden()
self.stubs.Set(glance.api.policy.ImageProxy,
'get_data',
get_data)
request = wsgi.Request.blank('/')
request.environ = {}
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=iter('ZZZ'))
image.get_data = get_data
self.assertRaises(webob.exc.HTTPForbidden,
self.serializer.download,
response, image)
def test_download_no_content(self):
"""Test image download returns HTTPNoContent
Make sure that serializer returns 204 no content error in case of
image data is not available at specified location.
"""
with mock.patch.object(glance.api.policy.ImageProxy,
'get_data') as mock_get_data:
mock_get_data.side_effect = glance_store.NotFound(image="image")
request = wsgi.Request.blank('/')
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=iter('ZZZ'))
image.get_data = mock_get_data
self.assertRaises(webob.exc.HTTPNoContent,
self.serializer.download,
response, image)
def test_download_service_unavailable(self):
"""Test image download returns HTTPServiceUnavailable."""
with mock.patch.object(glance.api.policy.ImageProxy,
'get_data') as mock_get_data:
mock_get_data.side_effect = glance_store.RemoteServiceUnavailable()
request = wsgi.Request.blank('/')
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=iter('ZZZ'))
image.get_data = mock_get_data
self.assertRaises(webob.exc.HTTPServiceUnavailable,
self.serializer.download,
response, image)
def test_download_store_get_not_support(self):
"""Test image download returns HTTPBadRequest.
Make sure that serializer returns 400 bad request error in case of
getting images from this store is not supported at specified location.
"""
with mock.patch.object(glance.api.policy.ImageProxy,
'get_data') as mock_get_data:
mock_get_data.side_effect = glance_store.StoreGetNotSupported()
request = wsgi.Request.blank('/')
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=iter('ZZZ'))
image.get_data = mock_get_data
self.assertRaises(webob.exc.HTTPBadRequest,
self.serializer.download,
response, image)
def test_download_store_random_get_not_support(self):
"""Test image download returns HTTPBadRequest.
Make sure that serializer returns 400 bad request error in case of
getting randomly images from this store is not supported at
specified location.
"""
with mock.patch.object(glance.api.policy.ImageProxy,
'get_data') as m_get_data:
err = glance_store.StoreRandomGetNotSupported(offset=0,
chunk_size=0)
m_get_data.side_effect = err
request = wsgi.Request.blank('/')
response = webob.Response()
response.request = request
image = FakeImage(size=3, data=iter('ZZZ'))
image.get_data = m_get_data
self.assertRaises(webob.exc.HTTPBadRequest,
self.serializer.download,
response, image)
def test_upload(self):
request = webob.Request.blank('/')
request.environ = {}
response = webob.Response()
response.request = request
self.serializer.upload(response, {})
self.assertEqual(http.NO_CONTENT, response.status_int)
self.assertEqual('0', response.headers['Content-Length'])
def test_stage(self):
request = webob.Request.blank('/')
request.environ = {}
response = webob.Response()
response.request = request
self.serializer.stage(response, {})
self.assertEqual(http.NO_CONTENT, response.status_int)
self.assertEqual('0', response.headers['Content-Length'])
glance-16.0.1/glance/tests/unit/v2/test_image_actions_resource.py 0000666 0001750 0001750 00000014527 13267672245 025126 0 ustar zuul zuul 0000000 0000000 # Copyright 2015 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import glance_store as store
import webob
import glance.api.v2.image_actions as image_actions
import glance.context
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
BASE_URI = unit_test_utils.BASE_URI
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
CHKSUM = '93264c3edf5972c9f1cb309543d38a5c'
def _db_fixture(id, **kwargs):
obj = {
'id': id,
'name': None,
'visibility': 'shared',
'properties': {},
'checksum': None,
'owner': None,
'status': 'queued',
'tags': [],
'size': None,
'virtual_size': None,
'locations': [],
'protected': False,
'disk_format': None,
'container_format': None,
'deleted': False,
'min_ram': None,
'min_disk': None,
}
obj.update(kwargs)
return obj
class TestImageActionsController(base.IsolatedUnitTest):
def setUp(self):
super(TestImageActionsController, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.policy = unit_test_utils.FakePolicyEnforcer()
self.notifier = unit_test_utils.FakeNotifier()
self.store = unit_test_utils.FakeStoreAPI()
for i in range(1, 4):
self.store.data['%s/fake_location_%i' % (BASE_URI, i)] = ('Z', 1)
self.store_utils = unit_test_utils.FakeStoreUtils(self.store)
self.controller = image_actions.ImageActionsController(
self.db,
self.policy,
self.notifier,
self.store)
self.controller.gateway.store_utils = self.store_utils
store.create_stores()
def _get_fake_context(self, user=USER1, tenant=TENANT1, roles=None,
is_admin=False):
if roles is None:
roles = ['member']
kwargs = {
'user': user,
'tenant': tenant,
'roles': roles,
'is_admin': is_admin,
}
context = glance.context.RequestContext(**kwargs)
return context
def _create_image(self, status):
self.images = [
_db_fixture(UUID1, owner=TENANT1, checksum=CHKSUM,
name='1', size=256, virtual_size=1024,
visibility='public',
locations=[{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}, 'status': 'active'}],
disk_format='raw',
container_format='bare',
status=status),
]
context = self._get_fake_context()
[self.db.image_create(context, image) for image in self.images]
def test_deactivate_from_active(self):
self._create_image('active')
request = unit_test_utils.get_fake_request()
self.controller.deactivate(request, UUID1)
image = self.db.image_get(request.context, UUID1)
self.assertEqual('deactivated', image['status'])
def test_deactivate_from_deactivated(self):
self._create_image('deactivated')
request = unit_test_utils.get_fake_request()
self.controller.deactivate(request, UUID1)
image = self.db.image_get(request.context, UUID1)
self.assertEqual('deactivated', image['status'])
def _test_deactivate_from_wrong_status(self, status):
# deactivate will yield an error if the initial status is anything
# other than 'active' or 'deactivated'
self._create_image(status)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.deactivate,
request, UUID1)
def test_deactivate_from_queued(self):
self._test_deactivate_from_wrong_status('queued')
def test_deactivate_from_saving(self):
self._test_deactivate_from_wrong_status('saving')
def test_deactivate_from_killed(self):
self._test_deactivate_from_wrong_status('killed')
def test_deactivate_from_pending_delete(self):
self._test_deactivate_from_wrong_status('pending_delete')
def test_deactivate_from_deleted(self):
self._test_deactivate_from_wrong_status('deleted')
def test_reactivate_from_active(self):
self._create_image('active')
request = unit_test_utils.get_fake_request()
self.controller.reactivate(request, UUID1)
image = self.db.image_get(request.context, UUID1)
self.assertEqual('active', image['status'])
def test_reactivate_from_deactivated(self):
self._create_image('deactivated')
request = unit_test_utils.get_fake_request()
self.controller.reactivate(request, UUID1)
image = self.db.image_get(request.context, UUID1)
self.assertEqual('active', image['status'])
def _test_reactivate_from_wrong_status(self, status):
# reactivate will yield an error if the initial status is anything
# other than 'active' or 'deactivated'
self._create_image(status)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.reactivate,
request, UUID1)
def test_reactivate_from_queued(self):
self._test_reactivate_from_wrong_status('queued')
def test_reactivate_from_saving(self):
self._test_reactivate_from_wrong_status('saving')
def test_reactivate_from_killed(self):
self._test_reactivate_from_wrong_status('killed')
def test_reactivate_from_pending_delete(self):
self._test_reactivate_from_wrong_status('pending_delete')
def test_reactivate_from_deleted(self):
self._test_reactivate_from_wrong_status('deleted')
glance-16.0.1/glance/tests/unit/v2/test_metadef_resources.py 0000666 0001750 0001750 00000261107 13267672245 024112 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import mock
from oslo_serialization import jsonutils
import webob
from glance.api.v2 import metadef_namespaces as namespaces
from glance.api.v2 import metadef_objects as objects
from glance.api.v2 import metadef_properties as properties
from glance.api.v2 import metadef_resource_types as resource_types
from glance.api.v2 import metadef_tags as tags
import glance.gateway
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
ISOTIME = '2012-05-16T15:27:36Z'
NAMESPACE1 = 'Namespace1'
NAMESPACE2 = 'Namespace2'
NAMESPACE3 = 'Namespace3'
NAMESPACE4 = 'Namespace4'
NAMESPACE5 = 'Namespace5'
NAMESPACE6 = 'Namespace6'
PROPERTY1 = 'Property1'
PROPERTY2 = 'Property2'
PROPERTY3 = 'Property3'
PROPERTY4 = 'Property4'
OBJECT1 = 'Object1'
OBJECT2 = 'Object2'
OBJECT3 = 'Object3'
RESOURCE_TYPE1 = 'ResourceType1'
RESOURCE_TYPE2 = 'ResourceType2'
RESOURCE_TYPE3 = 'ResourceType3'
RESOURCE_TYPE4 = 'ResourceType4'
TAG1 = 'Tag1'
TAG2 = 'Tag2'
TAG3 = 'Tag3'
TAG4 = 'Tag4'
TAG5 = 'Tag5'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
PREFIX1 = 'pref'
def _db_namespace_fixture(namespace, **kwargs):
obj = {
'namespace': namespace,
'display_name': None,
'description': None,
'visibility': 'public',
'protected': False,
'owner': None,
}
obj.update(kwargs)
return obj
def _db_property_fixture(name, **kwargs):
obj = {
'name': name,
'json_schema': {"type": "string", "title": "title"},
}
obj.update(kwargs)
return obj
def _db_object_fixture(name, **kwargs):
obj = {
'name': name,
'description': None,
'json_schema': {},
'required': '[]',
}
obj.update(kwargs)
return obj
def _db_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
'protected': False,
}
obj.update(kwargs)
return obj
def _db_tag_fixture(name, **kwargs):
obj = {
'name': name
}
obj.update(kwargs)
return obj
def _db_tags_fixture(tag_names=None):
tag_list = []
if not tag_names:
tag_names = [TAG1, TAG2, TAG3]
for tag_name in tag_names:
tag = tags.MetadefTag()
tag.name = tag_name
tag_list.append(tag)
return tag_list
def _db_namespace_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
'properties_target': None,
'prefix': None,
}
obj.update(kwargs)
return obj
class TestMetadefsControllers(base.IsolatedUnitTest):
def setUp(self):
super(TestMetadefsControllers, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.policy = unit_test_utils.FakePolicyEnforcer()
self.notifier = unit_test_utils.FakeNotifier()
self._create_namespaces()
self._create_properties()
self._create_objects()
self._create_resource_types()
self._create_namespaces_resource_types()
self._create_tags()
self.namespace_controller = namespaces.NamespaceController(
self.db, self.policy, self.notifier)
self.property_controller = properties.NamespacePropertiesController(
self.db, self.policy, self.notifier)
self.object_controller = objects.MetadefObjectsController(
self.db, self.policy, self.notifier)
self.rt_controller = resource_types.ResourceTypeController(
self.db, self.policy, self.notifier)
self.tag_controller = tags.TagsController(
self.db, self.policy, self.notifier)
self.deserializer = objects.RequestDeserializer()
self.property_deserializer = properties.RequestDeserializer()
def _create_namespaces(self):
req = unit_test_utils.get_fake_request()
self.namespaces = [
_db_namespace_fixture(NAMESPACE1, owner=TENANT1,
visibility='private', protected=True),
_db_namespace_fixture(NAMESPACE2, owner=TENANT2,
visibility='private'),
_db_namespace_fixture(NAMESPACE3, owner=TENANT3),
_db_namespace_fixture(NAMESPACE5, owner=TENANT4),
_db_namespace_fixture(NAMESPACE6, owner=TENANT4),
]
[self.db.metadef_namespace_create(req.context, namespace)
for namespace in self.namespaces]
def _create_properties(self):
req = unit_test_utils.get_fake_request()
self.properties = [
(NAMESPACE3, _db_property_fixture(PROPERTY1)),
(NAMESPACE3, _db_property_fixture(PROPERTY2)),
(NAMESPACE1, _db_property_fixture(PROPERTY1)),
(NAMESPACE6, _db_property_fixture(PROPERTY4)),
]
[self.db.metadef_property_create(req.context, namespace, property)
for namespace, property in self.properties]
def _create_objects(self):
req = unit_test_utils.get_fake_request()
self.objects = [
(NAMESPACE3, _db_object_fixture(OBJECT1)),
(NAMESPACE3, _db_object_fixture(OBJECT2)),
(NAMESPACE1, _db_object_fixture(OBJECT1)),
]
[self.db.metadef_object_create(req.context, namespace, object)
for namespace, object in self.objects]
def _create_resource_types(self):
req = unit_test_utils.get_fake_request()
self.resource_types = [
_db_resource_type_fixture(RESOURCE_TYPE1),
_db_resource_type_fixture(RESOURCE_TYPE2),
_db_resource_type_fixture(RESOURCE_TYPE4),
]
[self.db.metadef_resource_type_create(req.context, resource_type)
for resource_type in self.resource_types]
def _create_tags(self):
req = unit_test_utils.get_fake_request()
self.tags = [
(NAMESPACE3, _db_tag_fixture(TAG1)),
(NAMESPACE3, _db_tag_fixture(TAG2)),
(NAMESPACE1, _db_tag_fixture(TAG1)),
]
[self.db.metadef_tag_create(req.context, namespace, tag)
for namespace, tag in self.tags]
def _create_namespaces_resource_types(self):
req = unit_test_utils.get_fake_request(is_admin=True)
self.ns_resource_types = [
(NAMESPACE1, _db_namespace_resource_type_fixture(RESOURCE_TYPE1)),
(NAMESPACE3, _db_namespace_resource_type_fixture(RESOURCE_TYPE1)),
(NAMESPACE2, _db_namespace_resource_type_fixture(RESOURCE_TYPE1)),
(NAMESPACE2, _db_namespace_resource_type_fixture(RESOURCE_TYPE2)),
(NAMESPACE6, _db_namespace_resource_type_fixture(RESOURCE_TYPE4,
prefix=PREFIX1)),
]
[self.db.metadef_resource_type_association_create(req.context,
namespace,
ns_resource_type)
for namespace, ns_resource_type in self.ns_resource_types]
def assertNotificationLog(self, expected_event_type, expected_payloads):
events = [{'type': expected_event_type,
'payload': payload} for payload in expected_payloads]
self.assertNotificationsLog(events)
def assertNotificationsLog(self, expected_events):
output_logs = self.notifier.get_logs()
expected_logs_count = len(expected_events)
self.assertEqual(expected_logs_count, len(output_logs))
for output_log, event in zip(output_logs, expected_events):
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual(event['type'], output_log['event_type'])
self.assertDictContainsSubset(event['payload'],
output_log['payload'])
self.notifier.log = []
def test_namespace_index(self):
request = unit_test_utils.get_fake_request()
output = self.namespace_controller.index(request)
output = output.to_dict()
self.assertEqual(4, len(output['namespaces']))
actual = set([namespace.namespace for
namespace in output['namespaces']])
expected = set([NAMESPACE1, NAMESPACE3, NAMESPACE5, NAMESPACE6])
self.assertEqual(expected, actual)
def test_namespace_index_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.namespace_controller.index(request)
output = output.to_dict()
self.assertEqual(5, len(output['namespaces']))
actual = set([namespace.namespace for
namespace in output['namespaces']])
expected = set([NAMESPACE1, NAMESPACE2, NAMESPACE3, NAMESPACE5,
NAMESPACE6])
self.assertEqual(expected, actual)
def test_namespace_index_visibility_public(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
filters = {'visibility': 'public'}
output = self.namespace_controller.index(request, filters=filters)
output = output.to_dict()
self.assertEqual(3, len(output['namespaces']))
actual = set([namespace.namespace for namespace
in output['namespaces']])
expected = set([NAMESPACE3, NAMESPACE5, NAMESPACE6])
self.assertEqual(expected, actual)
def test_namespace_index_resource_type(self):
request = unit_test_utils.get_fake_request()
filters = {'resource_types': [RESOURCE_TYPE1]}
output = self.namespace_controller.index(request, filters=filters)
output = output.to_dict()
self.assertEqual(2, len(output['namespaces']))
actual = set([namespace.namespace for namespace
in output['namespaces']])
expected = set([NAMESPACE1, NAMESPACE3])
self.assertEqual(expected, actual)
def test_namespace_show(self):
request = unit_test_utils.get_fake_request()
output = self.namespace_controller.show(request, NAMESPACE1)
output = output.to_dict()
self.assertEqual(NAMESPACE1, output['namespace'])
self.assertEqual(TENANT1, output['owner'])
self.assertTrue(output['protected'])
self.assertEqual('private', output['visibility'])
def test_namespace_show_with_related_resources(self):
request = unit_test_utils.get_fake_request()
output = self.namespace_controller.show(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(NAMESPACE3, output['namespace'])
self.assertEqual(TENANT3, output['owner'])
self.assertFalse(output['protected'])
self.assertEqual('public', output['visibility'])
self.assertEqual(2, len(output['properties']))
actual = set([property for property in output['properties']])
expected = set([PROPERTY1, PROPERTY2])
self.assertEqual(expected, actual)
self.assertEqual(2, len(output['objects']))
actual = set([object.name for object in output['objects']])
expected = set([OBJECT1, OBJECT2])
self.assertEqual(expected, actual)
self.assertEqual(1, len(output['resource_type_associations']))
actual = set([rt.name for rt in output['resource_type_associations']])
expected = set([RESOURCE_TYPE1])
self.assertEqual(expected, actual)
def test_namespace_show_with_property_prefix(self):
request = unit_test_utils.get_fake_request()
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE2
rt.prefix = 'pref'
rt = self.rt_controller.create(request, rt, NAMESPACE3)
object = objects.MetadefObject()
object.name = OBJECT3
object.required = []
property = properties.PropertyType()
property.name = PROPERTY2
property.type = 'string'
property.title = 'title'
object.properties = {'prop1': property}
object = self.object_controller.create(request, object, NAMESPACE3)
self.assertNotificationsLog([
{
'type': 'metadef_resource_type.create',
'payload': {
'namespace': NAMESPACE3,
'name': RESOURCE_TYPE2,
'prefix': 'pref',
'properties_target': None,
}
},
{
'type': 'metadef_object.create',
'payload': {
'name': OBJECT3,
'namespace': NAMESPACE3,
'properties': [{
'name': 'prop1',
'additionalItems': None,
'confidential': None,
'title': u'title',
'default': None,
'pattern': None,
'enum': None,
'maximum': None,
'minItems': None,
'minimum': None,
'maxItems': None,
'minLength': None,
'uniqueItems': None,
'maxLength': None,
'items': None,
'type': u'string',
'description': None
}],
'required': [],
'description': None,
}
}
])
filters = {'resource_type': RESOURCE_TYPE2}
output = self.namespace_controller.show(request, NAMESPACE3, filters)
output = output.to_dict()
[self.assertTrue(property_name.startswith(rt.prefix)) for
property_name in output['properties'].keys()]
for object in output['objects']:
[self.assertTrue(property_name.startswith(rt.prefix)) for
property_name in object.properties.keys()]
@mock.patch('glance.api.v2.metadef_namespaces.LOG')
def test_cleanup_namespace_success(self, mock_log):
fake_gateway = glance.gateway.Gateway(db_api=self.db,
notifier=self.notifier,
policy_enforcer=self.policy)
req = unit_test_utils.get_fake_request()
ns_factory = fake_gateway.get_metadef_namespace_factory(
req.context)
ns_repo = fake_gateway.get_metadef_namespace_repo(req.context)
namespace = namespaces.Namespace()
namespace.namespace = 'FakeNamespace'
new_namespace = ns_factory.new_namespace(**namespace.to_dict())
ns_repo.add(new_namespace)
self.namespace_controller._cleanup_namespace(ns_repo, namespace, True)
mock_log.debug.assert_called_with(
"Cleaned up namespace %(namespace)s ",
{'namespace': namespace.namespace})
@mock.patch('glance.api.v2.metadef_namespaces.LOG')
@mock.patch('glance.api.authorization.MetadefNamespaceRepoProxy.remove')
def test_cleanup_namespace_exception(self, mock_remove, mock_log):
mock_remove.side_effect = Exception(u'Mock remove was called')
fake_gateway = glance.gateway.Gateway(db_api=self.db,
notifier=self.notifier,
policy_enforcer=self.policy)
req = unit_test_utils.get_fake_request()
ns_factory = fake_gateway.get_metadef_namespace_factory(
req.context)
ns_repo = fake_gateway.get_metadef_namespace_repo(req.context)
namespace = namespaces.Namespace()
namespace.namespace = 'FakeNamespace'
new_namespace = ns_factory.new_namespace(**namespace.to_dict())
ns_repo.add(new_namespace)
self.namespace_controller._cleanup_namespace(ns_repo, namespace, True)
called_msg = 'Failed to delete namespace %(namespace)s.' \
'Exception: %(exception)s'
called_args = {'exception': u'Mock remove was called',
'namespace': u'FakeNamespace'}
mock_log.error.assert_called_with((called_msg, called_args))
mock_remove.assert_called_once_with(mock.ANY)
def test_namespace_show_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, 'FakeName')
def test_namespace_show_non_visible(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE2)
def test_namespace_delete(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.namespace_controller.delete(request, NAMESPACE2)
self.assertNotificationLog("metadef_namespace.delete",
[{'namespace': NAMESPACE2}])
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE2)
def test_namespace_delete_notification_disabled(self):
self.config(disabled_notifications=["metadef_namespace.delete"])
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.namespace_controller.delete(request, NAMESPACE2)
self.assertNotificationsLog([])
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE2)
def test_namespace_delete_notification_group_disabled(self):
self.config(disabled_notifications=["metadef_namespace"])
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.namespace_controller.delete(request, NAMESPACE2)
self.assertNotificationsLog([])
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE2)
def test_namespace_delete_notification_create_disabled(self):
self.config(disabled_notifications=["metadef_namespace.create"])
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.namespace_controller.delete(request, NAMESPACE2)
self.assertNotificationLog("metadef_namespace.delete",
[{'namespace': NAMESPACE2}])
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE2)
def test_namespace_delete_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.delete, request,
'FakeName')
self.assertNotificationsLog([])
def test_namespace_delete_non_visible(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.delete, request,
NAMESPACE2)
self.assertNotificationsLog([])
def test_namespace_delete_non_visible_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.namespace_controller.delete(request, NAMESPACE2)
self.assertNotificationLog("metadef_namespace.delete",
[{'namespace': NAMESPACE2}])
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE2)
def test_namespace_delete_protected(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.namespace_controller.delete, request,
NAMESPACE1)
self.assertNotificationsLog([])
def test_namespace_delete_protected_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.assertRaises(webob.exc.HTTPForbidden,
self.namespace_controller.delete, request,
NAMESPACE1)
self.assertNotificationsLog([])
def test_namespace_delete_with_contents(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.namespace_controller.delete(request, NAMESPACE3)
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE3)
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.show,
request, NAMESPACE3, OBJECT1)
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE3,
OBJECT1)
def test_namespace_delete_properties(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.namespace_controller.delete_properties(request, NAMESPACE3)
output = self.property_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(0, len(output['properties']))
self.assertNotificationLog("metadef_namespace.delete_properties",
[{'namespace': NAMESPACE3}])
def test_namespace_delete_properties_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.namespace_controller.delete_properties,
request,
NAMESPACE3)
self.assertNotificationsLog([])
def test_namespace_delete_properties_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.namespace_controller.delete_properties(request, NAMESPACE3)
output = self.property_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(0, len(output['properties']))
self.assertNotificationLog("metadef_namespace.delete_properties",
[{'namespace': NAMESPACE3}])
def test_namespace_non_existing_delete_properties(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.delete_properties,
request,
NAMESPACE4)
self.assertNotificationsLog([])
def test_namespace_delete_objects(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.namespace_controller.delete_objects(request, NAMESPACE3)
output = self.object_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(0, len(output['objects']))
self.assertNotificationLog("metadef_namespace.delete_objects",
[{'namespace': NAMESPACE3}])
def test_namespace_delete_objects_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.namespace_controller.delete_objects,
request,
NAMESPACE3)
self.assertNotificationsLog([])
def test_namespace_delete_objects_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.namespace_controller.delete_objects(request, NAMESPACE3)
output = self.object_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(0, len(output['objects']))
self.assertNotificationLog("metadef_namespace.delete_objects",
[{'namespace': NAMESPACE3}])
def test_namespace_non_existing_delete_objects(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.delete_objects,
request,
NAMESPACE4)
self.assertNotificationsLog([])
def test_namespace_delete_tags(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.namespace_controller.delete_tags(request, NAMESPACE3)
output = self.tag_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(0, len(output['tags']))
self.assertNotificationLog("metadef_namespace.delete_tags",
[{'namespace': NAMESPACE3}])
def test_namespace_delete_tags_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.namespace_controller.delete_tags,
request,
NAMESPACE3)
self.assertNotificationsLog([])
def test_namespace_delete_tags_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.namespace_controller.delete_tags(request, NAMESPACE3)
output = self.tag_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(0, len(output['tags']))
self.assertNotificationLog("metadef_namespace.delete_tags",
[{'namespace': NAMESPACE3}])
def test_namespace_non_existing_delete_tags(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.delete_tags,
request,
NAMESPACE4)
self.assertNotificationsLog([])
def test_namespace_create(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE4
namespace = self.namespace_controller.create(request, namespace)
self.assertEqual(NAMESPACE4, namespace.namespace)
self.assertNotificationLog("metadef_namespace.create",
[{'namespace': NAMESPACE4}])
namespace = self.namespace_controller.show(request, NAMESPACE4)
self.assertEqual(NAMESPACE4, namespace.namespace)
def test_namespace_create_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = u'\U0001f693'
self.assertRaises(webob.exc.HTTPBadRequest,
self.namespace_controller.create, request,
namespace)
def test_namespace_create_duplicate(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = 'new-namespace'
new_ns = self.namespace_controller.create(request, namespace)
self.assertEqual('new-namespace', new_ns.namespace)
self.assertRaises(webob.exc.HTTPConflict,
self.namespace_controller.create,
request, namespace)
def test_namespace_create_different_owner(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE4
namespace.owner = TENANT4
self.assertRaises(webob.exc.HTTPForbidden,
self.namespace_controller.create, request, namespace)
self.assertNotificationsLog([])
def test_namespace_create_different_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE4
namespace.owner = TENANT4
namespace = self.namespace_controller.create(request, namespace)
self.assertEqual(NAMESPACE4, namespace.namespace)
self.assertNotificationLog("metadef_namespace.create",
[{'namespace': NAMESPACE4}])
namespace = self.namespace_controller.show(request, NAMESPACE4)
self.assertEqual(NAMESPACE4, namespace.namespace)
def test_namespace_create_with_related_resources(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE4
prop1 = properties.PropertyType()
prop1.type = 'string'
prop1.title = 'title'
prop2 = properties.PropertyType()
prop2.type = 'string'
prop2.title = 'title'
namespace.properties = {PROPERTY1: prop1, PROPERTY2: prop2}
object1 = objects.MetadefObject()
object1.name = OBJECT1
object1.required = []
object1.properties = {}
object2 = objects.MetadefObject()
object2.name = OBJECT2
object2.required = []
object2.properties = {}
namespace.objects = [object1, object2]
output = self.namespace_controller.create(request, namespace)
self.assertEqual(NAMESPACE4, namespace.namespace)
output = output.to_dict()
self.assertEqual(2, len(output['properties']))
actual = set([property for property in output['properties']])
expected = set([PROPERTY1, PROPERTY2])
self.assertEqual(expected, actual)
self.assertEqual(2, len(output['objects']))
actual = set([object.name for object in output['objects']])
expected = set([OBJECT1, OBJECT2])
self.assertEqual(expected, actual)
output = self.namespace_controller.show(request, NAMESPACE4)
self.assertEqual(NAMESPACE4, namespace.namespace)
output = output.to_dict()
self.assertEqual(2, len(output['properties']))
actual = set([property for property in output['properties']])
expected = set([PROPERTY1, PROPERTY2])
self.assertEqual(expected, actual)
self.assertEqual(2, len(output['objects']))
actual = set([object.name for object in output['objects']])
expected = set([OBJECT1, OBJECT2])
self.assertEqual(expected, actual)
self.assertNotificationsLog([
{
'type': 'metadef_namespace.create',
'payload': {
'namespace': NAMESPACE4,
'owner': TENANT1,
}
},
{
'type': 'metadef_object.create',
'payload': {
'namespace': NAMESPACE4,
'name': OBJECT1,
'properties': [],
}
},
{
'type': 'metadef_object.create',
'payload': {
'namespace': NAMESPACE4,
'name': OBJECT2,
'properties': [],
}
},
{
'type': 'metadef_property.create',
'payload': {
'namespace': NAMESPACE4,
'type': 'string',
'title': 'title',
}
},
{
'type': 'metadef_property.create',
'payload': {
'namespace': NAMESPACE4,
'type': 'string',
'title': 'title',
}
}
])
def test_namespace_create_conflict(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE1
self.assertRaises(webob.exc.HTTPConflict,
self.namespace_controller.create, request, namespace)
self.assertNotificationsLog([])
def test_namespace_update(self):
request = unit_test_utils.get_fake_request()
namespace = self.namespace_controller.show(request, NAMESPACE1)
namespace.protected = False
namespace = self.namespace_controller.update(request, namespace,
NAMESPACE1)
self.assertFalse(namespace.protected)
self.assertNotificationLog("metadef_namespace.update", [
{'namespace': NAMESPACE1, 'protected': False}
])
namespace = self.namespace_controller.show(request, NAMESPACE1)
self.assertFalse(namespace.protected)
def test_namespace_update_non_existing(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE4
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.update, request, namespace,
NAMESPACE4)
self.assertNotificationsLog([])
def test_namespace_update_non_visible(self):
request = unit_test_utils.get_fake_request()
namespace = namespaces.Namespace()
namespace.namespace = NAMESPACE2
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.update, request, namespace,
NAMESPACE2)
self.assertNotificationsLog([])
def test_namespace_update_non_visible_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
namespace = self.namespace_controller.show(request, NAMESPACE2)
namespace.protected = False
namespace = self.namespace_controller.update(request, namespace,
NAMESPACE2)
self.assertFalse(namespace.protected)
self.assertNotificationLog("metadef_namespace.update", [
{'namespace': NAMESPACE2, 'protected': False}
])
namespace = self.namespace_controller.show(request, NAMESPACE2)
self.assertFalse(namespace.protected)
def test_namespace_update_name(self):
request = unit_test_utils.get_fake_request()
namespace = self.namespace_controller.show(request, NAMESPACE1)
namespace.namespace = NAMESPACE4
namespace = self.namespace_controller.update(request, namespace,
NAMESPACE1)
self.assertEqual(NAMESPACE4, namespace.namespace)
self.assertNotificationLog("metadef_namespace.update", [
{'namespace': NAMESPACE4, 'namespace_old': NAMESPACE1}
])
namespace = self.namespace_controller.show(request, NAMESPACE4)
self.assertEqual(NAMESPACE4, namespace.namespace)
self.assertRaises(webob.exc.HTTPNotFound,
self.namespace_controller.show, request, NAMESPACE1)
def test_namespace_update_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
namespace = self.namespace_controller.show(request, NAMESPACE1)
namespace.namespace = u'\U0001f693'
self.assertRaises(webob.exc.HTTPBadRequest,
self.namespace_controller.update, request,
namespace, NAMESPACE1)
def test_namespace_update_name_conflict(self):
request = unit_test_utils.get_fake_request()
namespace = self.namespace_controller.show(request, NAMESPACE1)
namespace.namespace = NAMESPACE2
self.assertRaises(webob.exc.HTTPConflict,
self.namespace_controller.update, request, namespace,
NAMESPACE1)
self.assertNotificationsLog([])
def test_property_index(self):
request = unit_test_utils.get_fake_request()
output = self.property_controller.index(request, NAMESPACE3)
self.assertEqual(2, len(output.properties))
actual = set([property for property in output.properties])
expected = set([PROPERTY1, PROPERTY2])
self.assertEqual(expected, actual)
def test_property_index_empty(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
output = self.property_controller.index(request, NAMESPACE2)
self.assertEqual(0, len(output.properties))
def test_property_index_non_existing_namespace(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.index, request, NAMESPACE4)
def test_property_show(self):
request = unit_test_utils.get_fake_request()
output = self.property_controller.show(request, NAMESPACE3, PROPERTY1)
self.assertEqual(PROPERTY1, output.name)
def test_property_show_specific_resource_type(self):
request = unit_test_utils.get_fake_request()
output = self.property_controller.show(
request, NAMESPACE6, ''.join([PREFIX1, PROPERTY4]),
filters={'resource_type': RESOURCE_TYPE4})
self.assertEqual(PROPERTY4, output.name)
def test_property_show_prefix_mismatch(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE6,
PROPERTY4, filters={'resource_type': RESOURCE_TYPE4})
def test_property_show_non_existing_resource_type(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE2,
PROPERTY1, filters={'resource_type': 'test'})
def test_property_show_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE2,
PROPERTY1)
def test_property_show_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE1,
PROPERTY1)
def test_property_show_non_visible_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
output = self.property_controller.show(request, NAMESPACE1, PROPERTY1)
self.assertEqual(PROPERTY1, output.name)
def test_property_delete(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.property_controller.delete(request, NAMESPACE3, PROPERTY1)
self.assertNotificationLog("metadef_property.delete",
[{'name': PROPERTY1,
'namespace': NAMESPACE3}])
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE3,
PROPERTY1)
def test_property_delete_disabled_notification(self):
self.config(disabled_notifications=["metadef_property.delete"])
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.property_controller.delete(request, NAMESPACE3, PROPERTY1)
self.assertNotificationsLog([])
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE3,
PROPERTY1)
def test_property_delete_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.property_controller.delete, request, NAMESPACE3,
PROPERTY1)
self.assertNotificationsLog([])
def test_property_delete_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.property_controller.delete(request, NAMESPACE3, PROPERTY1)
self.assertNotificationLog("metadef_property.delete",
[{'name': PROPERTY1,
'namespace': NAMESPACE3}])
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.show, request, NAMESPACE3,
PROPERTY1)
def test_property_delete_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.delete, request, NAMESPACE5,
PROPERTY2)
self.assertNotificationsLog([])
def test_property_delete_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.delete, request, NAMESPACE4,
PROPERTY1)
self.assertNotificationsLog([])
def test_property_delete_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.delete, request, NAMESPACE1,
PROPERTY1)
self.assertNotificationsLog([])
def test_property_delete_admin_protected(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.assertRaises(webob.exc.HTTPForbidden,
self.property_controller.delete, request, NAMESPACE1,
PROPERTY1)
self.assertNotificationsLog([])
def test_property_create(self):
request = unit_test_utils.get_fake_request()
property = properties.PropertyType()
property.name = PROPERTY2
property.type = 'string'
property.title = 'title'
property = self.property_controller.create(request, NAMESPACE1,
property)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
self.assertNotificationLog("metadef_property.create",
[{'name': PROPERTY2,
'namespace': NAMESPACE1}])
property = self.property_controller.show(request, NAMESPACE1,
PROPERTY2)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
def test_property_create_overlimit_name(self):
request = unit_test_utils.get_fake_request('/metadefs/namespaces/'
'Namespace3/'
'properties')
request.body = jsonutils.dump_as_bytes({
'name': 'a' * 81, 'type': 'string', 'title': 'fake'})
exc = self.assertRaises(webob.exc.HTTPBadRequest,
self.property_deserializer.create,
request)
self.assertIn("Failed validating 'maxLength' in "
"schema['properties']['name']", exc.explanation)
def test_property_create_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
property = properties.PropertyType()
property.name = u'\U0001f693'
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPBadRequest,
self.property_controller.create,
request, NAMESPACE1, property)
def test_property_create_with_operators(self):
request = unit_test_utils.get_fake_request()
property = properties.PropertyType()
property.name = PROPERTY2
property.type = 'string'
property.title = 'title'
property.operators = ['']
property = self.property_controller.create(request, NAMESPACE1,
property)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
self.assertEqual([''], property.operators)
property = self.property_controller.show(request, NAMESPACE1,
PROPERTY2)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
self.assertEqual([''], property.operators)
def test_property_create_conflict(self):
request = unit_test_utils.get_fake_request()
property = properties.PropertyType()
property.name = PROPERTY1
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPConflict,
self.property_controller.create, request, NAMESPACE1,
property)
self.assertNotificationsLog([])
def test_property_create_non_visible_namespace(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
property = properties.PropertyType()
property.name = PROPERTY1
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPForbidden,
self.property_controller.create, request, NAMESPACE1,
property)
self.assertNotificationsLog([])
def test_property_create_non_visible_namespace_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
property = properties.PropertyType()
property.name = PROPERTY2
property.type = 'string'
property.title = 'title'
property = self.property_controller.create(request, NAMESPACE1,
property)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
self.assertNotificationLog("metadef_property.create",
[{'name': PROPERTY2,
'namespace': NAMESPACE1}])
property = self.property_controller.show(request, NAMESPACE1,
PROPERTY2)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
def test_property_create_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
property = properties.PropertyType()
property.name = PROPERTY1
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.create, request, NAMESPACE4,
property)
self.assertNotificationsLog([])
def test_property_create_duplicate(self):
request = unit_test_utils.get_fake_request()
property = properties.PropertyType()
property.name = 'new-property'
property.type = 'string'
property.title = 'title'
new_property = self.property_controller.create(request, NAMESPACE1,
property)
self.assertEqual('new-property', new_property.name)
self.assertRaises(webob.exc.HTTPConflict,
self.property_controller.create, request,
NAMESPACE1, property)
def test_property_update(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
property = self.property_controller.show(request, NAMESPACE3,
PROPERTY1)
property.name = PROPERTY1
property.type = 'string123'
property.title = 'title123'
property = self.property_controller.update(request, NAMESPACE3,
PROPERTY1, property)
self.assertEqual(PROPERTY1, property.name)
self.assertEqual('string123', property.type)
self.assertEqual('title123', property.title)
self.assertNotificationLog("metadef_property.update", [
{
'name': PROPERTY1,
'namespace': NAMESPACE3,
'type': 'string123',
'title': 'title123',
}
])
property = self.property_controller.show(request, NAMESPACE3,
PROPERTY1)
self.assertEqual(PROPERTY1, property.name)
self.assertEqual('string123', property.type)
self.assertEqual('title123', property.title)
def test_property_update_name(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
property = self.property_controller.show(request, NAMESPACE3,
PROPERTY1)
property.name = PROPERTY3
property.type = 'string'
property.title = 'title'
property = self.property_controller.update(request, NAMESPACE3,
PROPERTY1, property)
self.assertEqual(PROPERTY3, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
self.assertNotificationLog("metadef_property.update", [
{
'name': PROPERTY3,
'name_old': PROPERTY1,
'namespace': NAMESPACE3,
'type': 'string',
'title': 'title',
}
])
property = self.property_controller.show(request, NAMESPACE3,
PROPERTY2)
self.assertEqual(PROPERTY2, property.name)
self.assertEqual('string', property.type)
self.assertEqual('title', property.title)
def test_property_update_conflict(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
property = self.property_controller.show(request, NAMESPACE3,
PROPERTY1)
property.name = PROPERTY2
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPConflict,
self.property_controller.update, request, NAMESPACE3,
PROPERTY1, property)
self.assertNotificationsLog([])
def test_property_update_with_overlimit_name(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({
'name': 'a' * 81, 'type': 'string', 'title': 'fake'})
exc = self.assertRaises(webob.exc.HTTPBadRequest,
self.property_deserializer.create,
request)
self.assertIn("Failed validating 'maxLength' in "
"schema['properties']['name']", exc.explanation)
def test_property_update_with_4byte_character(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
property = self.property_controller.show(request, NAMESPACE3,
PROPERTY1)
property.name = u'\U0001f693'
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPBadRequest,
self.property_controller.update, request,
NAMESPACE3, PROPERTY1, property)
def test_property_update_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
property = properties.PropertyType()
property.name = PROPERTY1
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.update, request, NAMESPACE5,
PROPERTY1, property)
self.assertNotificationsLog([])
def test_property_update_namespace_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
property = properties.PropertyType()
property.name = PROPERTY1
property.type = 'string'
property.title = 'title'
self.assertRaises(webob.exc.HTTPNotFound,
self.property_controller.update, request, NAMESPACE4,
PROPERTY1, property)
self.assertNotificationsLog([])
def test_object_index(self):
request = unit_test_utils.get_fake_request()
output = self.object_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(2, len(output['objects']))
actual = set([object.name for object in output['objects']])
expected = set([OBJECT1, OBJECT2])
self.assertEqual(expected, actual)
def test_object_index_zero_limit(self):
request = unit_test_utils.get_fake_request('/metadefs/namespaces/'
'Namespace3/'
'objects?limit=0')
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.index,
request)
def test_object_index_empty(self):
request = unit_test_utils.get_fake_request()
output = self.object_controller.index(request, NAMESPACE5)
output = output.to_dict()
self.assertEqual(0, len(output['objects']))
def test_object_index_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.index,
request, NAMESPACE4)
def test_object_show(self):
request = unit_test_utils.get_fake_request()
output = self.object_controller.show(request, NAMESPACE3, OBJECT1)
self.assertEqual(OBJECT1, output.name)
def test_object_show_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.show,
request, NAMESPACE5, OBJECT1)
def test_object_show_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.show,
request, NAMESPACE1, OBJECT1)
def test_object_show_non_visible_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
output = self.object_controller.show(request, NAMESPACE1, OBJECT1)
self.assertEqual(OBJECT1, output.name)
def test_object_delete(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.object_controller.delete(request, NAMESPACE3, OBJECT1)
self.assertNotificationLog("metadef_object.delete",
[{'name': OBJECT1,
'namespace': NAMESPACE3}])
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.show,
request, NAMESPACE3, OBJECT1)
def test_object_delete_disabled_notification(self):
self.config(disabled_notifications=["metadef_object.delete"])
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.object_controller.delete(request, NAMESPACE3, OBJECT1)
self.assertNotificationsLog([])
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.show,
request, NAMESPACE3, OBJECT1)
def test_object_delete_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.object_controller.delete, request, NAMESPACE3,
OBJECT1)
self.assertNotificationsLog([])
def test_object_delete_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.object_controller.delete(request, NAMESPACE3, OBJECT1)
self.assertNotificationLog("metadef_object.delete",
[{'name': OBJECT1,
'namespace': NAMESPACE3}])
self.assertRaises(webob.exc.HTTPNotFound, self.object_controller.show,
request, NAMESPACE3, OBJECT1)
def test_object_delete_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.object_controller.delete, request, NAMESPACE5,
OBJECT1)
self.assertNotificationsLog([])
def test_object_delete_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.object_controller.delete, request, NAMESPACE4,
OBJECT1)
self.assertNotificationsLog([])
def test_object_delete_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound,
self.object_controller.delete, request, NAMESPACE1,
OBJECT1)
self.assertNotificationsLog([])
def test_object_delete_admin_protected(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.assertRaises(webob.exc.HTTPForbidden,
self.object_controller.delete, request, NAMESPACE1,
OBJECT1)
self.assertNotificationsLog([])
def test_object_create(self):
request = unit_test_utils.get_fake_request()
object = objects.MetadefObject()
object.name = OBJECT2
object.required = []
object.properties = {}
object = self.object_controller.create(request, object, NAMESPACE1)
self.assertEqual(OBJECT2, object.name)
self.assertEqual([], object.required)
self.assertEqual({}, object.properties)
self.assertNotificationLog("metadef_object.create",
[{'name': OBJECT2,
'namespace': NAMESPACE1,
'properties': []}])
object = self.object_controller.show(request, NAMESPACE1, OBJECT2)
self.assertEqual(OBJECT2, object.name)
self.assertEqual([], object.required)
self.assertEqual({}, object.properties)
def test_object_create_invalid_properties(self):
request = unit_test_utils.get_fake_request('/metadefs/namespaces/'
'Namespace3/'
'objects')
body = {
"name": "My Object",
"description": "object1 description.",
"properties": {
"property1": {
"type": "integer",
"title": "property",
"description": "property description",
"test-key": "test-value",
}
}
}
request.body = jsonutils.dump_as_bytes(body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create,
request)
def test_object_create_overlimit_name(self):
request = unit_test_utils.get_fake_request('/metadefs/namespaces/'
'Namespace3/'
'objects')
request.body = jsonutils.dump_as_bytes({'name': 'a' * 81})
exc = self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create,
request)
self.assertIn("Failed validating 'maxLength' in "
"schema['properties']['name']", exc.explanation)
def test_object_create_duplicate(self):
request = unit_test_utils.get_fake_request()
object = objects.MetadefObject()
object.name = 'New-Object'
object.required = []
object.properties = {}
new_obj = self.object_controller.create(request, object, NAMESPACE3)
self.assertEqual('New-Object', new_obj.name)
self.assertRaises(webob.exc.HTTPConflict,
self.object_controller.create, request, object,
NAMESPACE3)
def test_object_create_conflict(self):
request = unit_test_utils.get_fake_request()
object = objects.MetadefObject()
object.name = OBJECT1
object.required = []
object.properties = {}
self.assertRaises(webob.exc.HTTPConflict,
self.object_controller.create, request, object,
NAMESPACE1)
self.assertNotificationsLog([])
def test_object_create_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
object = objects.MetadefObject()
object.name = u'\U0001f693'
object.required = []
object.properties = {}
self.assertRaises(webob.exc.HTTPBadRequest,
self.object_controller.create, request,
object, NAMESPACE1)
def test_object_create_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
object = objects.MetadefObject()
object.name = PROPERTY1
object.required = []
object.properties = {}
self.assertRaises(webob.exc.HTTPNotFound,
self.object_controller.create, request, object,
NAMESPACE4)
self.assertNotificationsLog([])
def test_object_create_non_visible_namespace(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
object = objects.MetadefObject()
object.name = OBJECT1
object.required = []
object.properties = {}
self.assertRaises(webob.exc.HTTPForbidden,
self.object_controller.create, request, object,
NAMESPACE1)
self.assertNotificationsLog([])
def test_object_create_non_visible_namespace_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
object = objects.MetadefObject()
object.name = OBJECT2
object.required = []
object.properties = {}
object = self.object_controller.create(request, object, NAMESPACE1)
self.assertEqual(OBJECT2, object.name)
self.assertEqual([], object.required)
self.assertEqual({}, object.properties)
self.assertNotificationLog("metadef_object.create",
[{'name': OBJECT2,
'namespace': NAMESPACE1}])
object = self.object_controller.show(request, NAMESPACE1, OBJECT2)
self.assertEqual(OBJECT2, object.name)
self.assertEqual([], object.required)
self.assertEqual({}, object.properties)
def test_object_create_missing_properties(self):
request = unit_test_utils.get_fake_request()
object = objects.MetadefObject()
object.name = OBJECT2
object.required = []
object = self.object_controller.create(request, object, NAMESPACE1)
self.assertEqual(OBJECT2, object.name)
self.assertEqual([], object.required)
self.assertNotificationLog("metadef_object.create",
[{'name': OBJECT2,
'namespace': NAMESPACE1,
'properties': []}])
object = self.object_controller.show(request, NAMESPACE1, OBJECT2)
self.assertEqual(OBJECT2, object.name)
self.assertEqual([], object.required)
self.assertEqual({}, object.properties)
def test_object_update(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
object = self.object_controller.show(request, NAMESPACE3, OBJECT1)
object.name = OBJECT1
object.description = 'description'
object = self.object_controller.update(request, object, NAMESPACE3,
OBJECT1)
self.assertEqual(OBJECT1, object.name)
self.assertEqual('description', object.description)
self.assertNotificationLog("metadef_object.update", [
{
'name': OBJECT1,
'namespace': NAMESPACE3,
'description': 'description',
}
])
property = self.object_controller.show(request, NAMESPACE3, OBJECT1)
self.assertEqual(OBJECT1, property.name)
self.assertEqual('description', object.description)
def test_object_update_name(self):
request = unit_test_utils.get_fake_request()
object = self.object_controller.show(request, NAMESPACE1, OBJECT1)
object.name = OBJECT2
object = self.object_controller.update(request, object, NAMESPACE1,
OBJECT1)
self.assertEqual(OBJECT2, object.name)
self.assertNotificationLog("metadef_object.update", [
{
'name': OBJECT2,
'name_old': OBJECT1,
'namespace': NAMESPACE1,
}
])
object = self.object_controller.show(request, NAMESPACE1, OBJECT2)
self.assertEqual(OBJECT2, object.name)
def test_object_update_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
object = self.object_controller.show(request, NAMESPACE1, OBJECT1)
object.name = u'\U0001f693'
self.assertRaises(webob.exc.HTTPBadRequest,
self.object_controller.update, request,
object, NAMESPACE1, OBJECT1)
def test_object_update_with_overlimit_name(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes(
{"properties": {}, "name": "a" * 81, "required": []})
exc = self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
self.assertIn("Failed validating 'maxLength' in "
"schema['properties']['name']", exc.explanation)
def test_object_update_conflict(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
object = self.object_controller.show(request, NAMESPACE3, OBJECT1)
object.name = OBJECT2
self.assertRaises(webob.exc.HTTPConflict,
self.object_controller.update, request, object,
NAMESPACE3, OBJECT1)
self.assertNotificationsLog([])
def test_object_update_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
object = objects.MetadefObject()
object.name = OBJECT1
object.required = []
object.properties = {}
self.assertRaises(webob.exc.HTTPNotFound,
self.object_controller.update, request, object,
NAMESPACE5, OBJECT1)
self.assertNotificationsLog([])
def test_object_update_namespace_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
object = objects.MetadefObject()
object.name = OBJECT1
object.required = []
object.properties = {}
self.assertRaises(webob.exc.HTTPNotFound,
self.object_controller.update, request, object,
NAMESPACE4, OBJECT1)
self.assertNotificationsLog([])
def test_resource_type_index(self):
request = unit_test_utils.get_fake_request()
output = self.rt_controller.index(request)
self.assertEqual(3, len(output.resource_types))
actual = set([rtype.name for rtype in output.resource_types])
expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2, RESOURCE_TYPE4])
self.assertEqual(expected, actual)
def test_resource_type_show(self):
request = unit_test_utils.get_fake_request()
output = self.rt_controller.show(request, NAMESPACE3)
self.assertEqual(1, len(output.resource_type_associations))
actual = set([rt.name for rt in output.resource_type_associations])
expected = set([RESOURCE_TYPE1])
self.assertEqual(expected, actual)
def test_resource_type_show_empty(self):
request = unit_test_utils.get_fake_request()
output = self.rt_controller.show(request, NAMESPACE5)
self.assertEqual(0, len(output.resource_type_associations))
def test_resource_type_show_non_visible(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.show,
request, NAMESPACE2)
def test_resource_type_show_non_visible_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
output = self.rt_controller.show(request, NAMESPACE2)
self.assertEqual(2, len(output.resource_type_associations))
actual = set([rt.name for rt in output.resource_type_associations])
expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2])
self.assertEqual(expected, actual)
def test_resource_type_show_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.show,
request, NAMESPACE4)
def test_resource_type_association_delete(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.rt_controller.delete(request, NAMESPACE3, RESOURCE_TYPE1)
self.assertNotificationLog("metadef_resource_type.delete",
[{'name': RESOURCE_TYPE1,
'namespace': NAMESPACE3}])
output = self.rt_controller.show(request, NAMESPACE3)
self.assertEqual(0, len(output.resource_type_associations))
def test_resource_type_association_delete_disabled_notification(self):
self.config(disabled_notifications=["metadef_resource_type.delete"])
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.rt_controller.delete(request, NAMESPACE3, RESOURCE_TYPE1)
self.assertNotificationsLog([])
output = self.rt_controller.show(request, NAMESPACE3)
self.assertEqual(0, len(output.resource_type_associations))
def test_resource_type_association_delete_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.rt_controller.delete,
request, NAMESPACE3, RESOURCE_TYPE1)
self.assertNotificationsLog([])
def test_resource_type_association_delete_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.rt_controller.delete(request, NAMESPACE3, RESOURCE_TYPE1)
self.assertNotificationLog("metadef_resource_type.delete",
[{'name': RESOURCE_TYPE1,
'namespace': NAMESPACE3}])
output = self.rt_controller.show(request, NAMESPACE3)
self.assertEqual(0, len(output.resource_type_associations))
def test_resource_type_association_delete_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.delete,
request, NAMESPACE1, RESOURCE_TYPE2)
self.assertNotificationsLog([])
def test_resource_type_association_delete_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.delete,
request, NAMESPACE4, RESOURCE_TYPE1)
self.assertNotificationsLog([])
def test_resource_type_association_delete_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.delete,
request, NAMESPACE1, RESOURCE_TYPE1)
self.assertNotificationsLog([])
def test_resource_type_association_delete_protected_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.assertRaises(webob.exc.HTTPForbidden, self.rt_controller.delete,
request, NAMESPACE1, RESOURCE_TYPE1)
self.assertNotificationsLog([])
def test_resource_type_association_create(self):
request = unit_test_utils.get_fake_request()
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE2
rt.prefix = 'pref'
rt = self.rt_controller.create(request, rt, NAMESPACE1)
self.assertEqual(RESOURCE_TYPE2, rt.name)
self.assertEqual('pref', rt.prefix)
self.assertNotificationLog("metadef_resource_type.create",
[{'name': RESOURCE_TYPE2,
'namespace': NAMESPACE1}])
output = self.rt_controller.show(request, NAMESPACE1)
self.assertEqual(2, len(output.resource_type_associations))
actual = set([x.name for x in output.resource_type_associations])
expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2])
self.assertEqual(expected, actual)
def test_resource_type_association_create_conflict(self):
request = unit_test_utils.get_fake_request()
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE1
rt.prefix = 'pref'
self.assertRaises(webob.exc.HTTPConflict, self.rt_controller.create,
request, rt, NAMESPACE1)
self.assertNotificationsLog([])
def test_resource_type_association_create_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE1
rt.prefix = 'pref'
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.create,
request, rt, NAMESPACE4)
self.assertNotificationsLog([])
def test_resource_type_association_create_non_existing_resource_type(self):
request = unit_test_utils.get_fake_request()
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE3
rt.prefix = 'pref'
self.assertRaises(webob.exc.HTTPNotFound, self.rt_controller.create,
request, rt, NAMESPACE1)
self.assertNotificationsLog([])
def test_resource_type_association_create_non_visible_namespace(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE2
rt.prefix = 'pref'
self.assertRaises(webob.exc.HTTPForbidden, self.rt_controller.create,
request, rt, NAMESPACE1)
self.assertNotificationsLog([])
def test_resource_type_association_create_non_visible_namesp_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
rt = resource_types.ResourceTypeAssociation()
rt.name = RESOURCE_TYPE2
rt.prefix = 'pref'
rt = self.rt_controller.create(request, rt, NAMESPACE1)
self.assertEqual(RESOURCE_TYPE2, rt.name)
self.assertEqual('pref', rt.prefix)
self.assertNotificationLog("metadef_resource_type.create",
[{'name': RESOURCE_TYPE2,
'namespace': NAMESPACE1}])
output = self.rt_controller.show(request, NAMESPACE1)
self.assertEqual(2, len(output.resource_type_associations))
actual = set([x.name for x in output.resource_type_associations])
expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2])
self.assertEqual(expected, actual)
def test_tag_index(self):
request = unit_test_utils.get_fake_request()
output = self.tag_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(2, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2])
self.assertEqual(expected, actual)
def test_tag_index_empty(self):
request = unit_test_utils.get_fake_request()
output = self.tag_controller.index(request, NAMESPACE5)
output = output.to_dict()
self.assertEqual(0, len(output['tags']))
def test_tag_index_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.index,
request, NAMESPACE4)
def test_tag_show(self):
request = unit_test_utils.get_fake_request()
output = self.tag_controller.show(request, NAMESPACE3, TAG1)
self.assertEqual(TAG1, output.name)
def test_tag_show_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE5, TAG1)
def test_tag_show_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE1, TAG1)
def test_tag_show_non_visible_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
output = self.tag_controller.show(request, NAMESPACE1, TAG1)
self.assertEqual(TAG1, output.name)
def test_tag_delete(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.tag_controller.delete(request, NAMESPACE3, TAG1)
self.assertNotificationLog("metadef_tag.delete",
[{'name': TAG1,
'namespace': NAMESPACE3}])
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE3, TAG1)
def test_tag_delete_disabled_notification(self):
self.config(disabled_notifications=["metadef_tag.delete"])
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.tag_controller.delete(request, NAMESPACE3, TAG1)
self.assertNotificationsLog([])
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE3, TAG1)
def test_tag_delete_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.tag_controller.delete, request, NAMESPACE3,
TAG1)
self.assertNotificationsLog([])
def test_tag_delete_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.tag_controller.delete(request, NAMESPACE3, TAG1)
self.assertNotificationLog("metadef_tag.delete",
[{'name': TAG1,
'namespace': NAMESPACE3}])
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE3, TAG1)
def test_tag_delete_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.delete, request, NAMESPACE5,
TAG1)
self.assertNotificationsLog([])
def test_tag_delete_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.delete, request, NAMESPACE4,
TAG1)
self.assertNotificationsLog([])
def test_tag_delete_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.delete, request, NAMESPACE1,
TAG1)
self.assertNotificationsLog([])
def test_tag_delete_admin_protected(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.assertRaises(webob.exc.HTTPForbidden,
self.tag_controller.delete, request, NAMESPACE1,
TAG1)
self.assertNotificationsLog([])
def test_tag_create(self):
request = unit_test_utils.get_fake_request()
tag = self.tag_controller.create(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
self.assertNotificationLog("metadef_tag.create",
[{'name': TAG2,
'namespace': NAMESPACE1}])
tag = self.tag_controller.show(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
def test_tag_create_overlimit_name(self):
request = unit_test_utils.get_fake_request()
exc = self.assertRaises(webob.exc.HTTPBadRequest,
self.tag_controller.create,
request, NAMESPACE1, 'a' * 81)
self.assertIn("Failed validating 'maxLength' in "
"schema['properties']['name']", exc.explanation)
def test_tag_create_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest,
self.tag_controller.create,
request, NAMESPACE1, u'\U0001f693')
def test_tag_create_tags(self):
request = unit_test_utils.get_fake_request()
metadef_tags = tags.MetadefTags()
metadef_tags.tags = _db_tags_fixture()
output = self.tag_controller.create_tags(
request, metadef_tags, NAMESPACE1)
output = output.to_dict()
self.assertEqual(3, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2, TAG3])
self.assertEqual(expected, actual)
self.assertNotificationLog(
"metadef_tag.create", [
{'name': TAG1, 'namespace': NAMESPACE1},
{'name': TAG2, 'namespace': NAMESPACE1},
{'name': TAG3, 'namespace': NAMESPACE1},
]
)
def test_tag_create_duplicate_tags(self):
request = unit_test_utils.get_fake_request()
metadef_tags = tags.MetadefTags()
metadef_tags.tags = _db_tags_fixture([TAG4, TAG5, TAG4])
self.assertRaises(
webob.exc.HTTPConflict,
self.tag_controller.create_tags,
request, metadef_tags, NAMESPACE1)
self.assertNotificationsLog([])
def test_tag_create_duplicate_with_pre_existing_tags(self):
request = unit_test_utils.get_fake_request()
metadef_tags = tags.MetadefTags()
metadef_tags.tags = _db_tags_fixture([TAG1, TAG2, TAG3])
output = self.tag_controller.create_tags(
request, metadef_tags, NAMESPACE1)
output = output.to_dict()
self.assertEqual(3, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2, TAG3])
self.assertEqual(expected, actual)
self.assertNotificationLog(
"metadef_tag.create", [
{'name': TAG1, 'namespace': NAMESPACE1},
{'name': TAG2, 'namespace': NAMESPACE1},
{'name': TAG3, 'namespace': NAMESPACE1},
]
)
metadef_tags = tags.MetadefTags()
metadef_tags.tags = _db_tags_fixture([TAG4, TAG5, TAG4])
self.assertRaises(
webob.exc.HTTPConflict,
self.tag_controller.create_tags,
request, metadef_tags, NAMESPACE1)
self.assertNotificationsLog([])
output = self.tag_controller.index(request, NAMESPACE1)
output = output.to_dict()
self.assertEqual(3, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2, TAG3])
self.assertEqual(expected, actual)
def test_tag_create_conflict(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPConflict,
self.tag_controller.create, request,
NAMESPACE1, TAG1)
self.assertNotificationsLog([])
def test_tag_create_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.create, request,
NAMESPACE4, TAG1)
self.assertNotificationsLog([])
def test_tag_create_non_visible_namespace(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPForbidden,
self.tag_controller.create, request,
NAMESPACE1, TAG1)
self.assertNotificationsLog([])
def test_tag_create_non_visible_namespace_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
tag = self.tag_controller.create(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
self.assertNotificationLog("metadef_tag.create",
[{'name': TAG2,
'namespace': NAMESPACE1}])
tag = self.tag_controller.show(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
def test_tag_update(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = self.tag_controller.show(request, NAMESPACE3, TAG1)
tag.name = TAG3
tag = self.tag_controller.update(request, tag, NAMESPACE3, TAG1)
self.assertEqual(TAG3, tag.name)
self.assertNotificationLog("metadef_tag.update", [
{'name': TAG3, 'namespace': NAMESPACE3}
])
property = self.tag_controller.show(request, NAMESPACE3, TAG3)
self.assertEqual(TAG3, property.name)
def test_tag_update_name(self):
request = unit_test_utils.get_fake_request()
tag = self.tag_controller.show(request, NAMESPACE1, TAG1)
tag.name = TAG2
tag = self.tag_controller.update(request, tag, NAMESPACE1, TAG1)
self.assertEqual(TAG2, tag.name)
self.assertNotificationLog("metadef_tag.update", [
{'name': TAG2, 'name_old': TAG1, 'namespace': NAMESPACE1}
])
tag = self.tag_controller.show(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
def test_tag_update_with_4byte_character(self):
request = unit_test_utils.get_fake_request()
tag = self.tag_controller.show(request, NAMESPACE1, TAG1)
tag.name = u'\U0001f693'
self.assertRaises(webob.exc.HTTPBadRequest,
self.tag_controller.update, request, tag,
NAMESPACE1, TAG1)
def test_tag_update_with_name_overlimit(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes(
{"properties": {}, "name": "a" * 81, "required": []})
exc = self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.update, request)
self.assertIn("Failed validating 'maxLength' in "
"schema['properties']['name']", exc.explanation)
def test_tag_update_conflict(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = self.tag_controller.show(request, NAMESPACE3, TAG1)
tag.name = TAG2
self.assertRaises(webob.exc.HTTPConflict,
self.tag_controller.update, request, tag,
NAMESPACE3, TAG1)
self.assertNotificationsLog([])
def test_tag_update_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = tags.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.update, request, tag,
NAMESPACE5, TAG1)
self.assertNotificationsLog([])
def test_tag_update_namespace_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = tags.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.update, request, tag,
NAMESPACE4, TAG1)
self.assertNotificationsLog([])
class TestMetadefNamespaceResponseSerializers(base.IsolatedUnitTest):
def setUp(self):
super(TestMetadefNamespaceResponseSerializers, self).setUp()
self.serializer = namespaces.ResponseSerializer(schema={})
self.response = mock.Mock()
self.result = mock.Mock()
def test_delete_tags(self):
self.serializer.delete_tags(self.response, self.result)
self.assertEqual(204, self.response.status_int)
glance-16.0.1/glance/tests/unit/v2/test_registry_client.py 0000666 0001750 0001750 00000073715 13267672245 023627 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests for Glance Registry's client.
This tests are temporary and will be removed once
the registry's driver tests will be added.
"""
import copy
import datetime
import os
import uuid
from mock import patch
from six.moves import reload_module
from glance.common import config
from glance.common import exception
from glance.common import timeutils
from glance import context
from glance.db.sqlalchemy import api as db_api
from glance.i18n import _
from glance.registry.api import v2 as rserver
import glance.registry.client.v2.api as rapi
from glance.registry.client.v2.api import client as rclient
from glance.tests.unit import base
from glance.tests import utils as test_utils
_gen_uuid = lambda: str(uuid.uuid4())
UUID1 = str(uuid.uuid4())
UUID2 = str(uuid.uuid4())
# NOTE(bcwaldon): needed to init config_dir cli opt
config.parse_args(args=[])
class TestRegistryV2Client(base.IsolatedUnitTest,
test_utils.RegistryAPIMixIn):
"""Test proper actions made against a registry service.
Test for both valid and invalid requests.
"""
# Registry server to user
# in the stub.
registry = rserver
def setUp(self):
"""Establish a clean test environment"""
super(TestRegistryV2Client, self).setUp()
db_api.get_engine()
self.context = context.RequestContext(is_admin=True)
uuid1_time = timeutils.utcnow()
uuid2_time = uuid1_time + datetime.timedelta(seconds=5)
self.FIXTURES = [
self.get_extra_fixture(
id=UUID1, name='fake image #1', visibility='shared',
disk_format='ami', container_format='ami', size=13,
virtual_size=26, properties={'type': 'kernel'},
location="swift://user:passwd@acct/container/obj.tar.0",
created_at=uuid1_time),
self.get_extra_fixture(id=UUID2, name='fake image #2',
properties={}, size=19, virtual_size=38,
location="file:///tmp/glance-tests/2",
created_at=uuid2_time)]
self.destroy_fixtures()
self.create_fixtures()
self.client = rclient.RegistryClient("0.0.0.0")
def tearDown(self):
"""Clear the test environment"""
super(TestRegistryV2Client, self).tearDown()
self.destroy_fixtures()
def test_image_get_index(self):
"""Test correct set of public image returned"""
images = self.client.image_get_all()
self.assertEqual(2, len(images))
def test_create_image_with_null_min_disk_min_ram(self):
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf', min_disk=None,
min_ram=None)
db_api.image_create(self.context, extra_fixture)
image = self.client.image_get(image_id=UUID3)
self.assertEqual(0, image["min_ram"])
self.assertEqual(0, image["min_disk"])
def test_get_index_sort_name_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by name in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz')
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['name'],
sort_dir=['asc'])
self.assertEqualImages(images, (UUID3, UUID1, UUID2, UUID4),
unjsonify=False)
def test_get_index_sort_status_desc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by status in descending order.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
status='queued')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
created_at=uuid4_time)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['status'],
sort_dir=['desc'])
self.assertEqualImages(images, (UUID3, UUID4, UUID2, UUID1),
unjsonify=False)
def test_get_index_sort_disk_format_asc(self):
"""Tests that the registry API returns list of public images.
Must besorted alphabetically by disk_format in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
disk_format='vdi')
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['disk_format'],
sort_dir=['asc'])
self.assertEqualImages(images, (UUID1, UUID3, UUID4, UUID2),
unjsonify=False)
def test_get_index_sort_container_format_desc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by container_format in descending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
disk_format='iso',
container_format='bare')
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['container_format'],
sort_dir=['desc'])
self.assertEqualImages(images, (UUID2, UUID4, UUID3, UUID1),
unjsonify=False)
def test_get_index_sort_size_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted by size in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami',
size=100, virtual_size=200)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='asdf',
disk_format='iso',
container_format='bare',
size=2, virtual_size=4)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['size'], sort_dir=['asc'])
self.assertEqualImages(images, (UUID4, UUID1, UUID2, UUID3),
unjsonify=False)
def test_get_index_sort_created_at_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted by created_at in ascending order.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, created_at=uuid3_time)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=uuid4_time)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['created_at'],
sort_dir=['asc'])
self.assertEqualImages(images, (UUID1, UUID2, UUID4, UUID3),
unjsonify=False)
def test_get_index_sort_updated_at_desc(self):
"""Tests that the registry API returns list of public images.
Must be sorted by updated_at in descending order.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, created_at=None,
updated_at=uuid3_time)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=None,
updated_at=uuid4_time)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['updated_at'],
sort_dir=['desc'])
self.assertEqualImages(images, (UUID3, UUID4, UUID2, UUID1),
unjsonify=False)
def test_get_image_details_sort_multiple_keys(self):
"""
Tests that a detailed call returns list of
public images sorted by name-size and
size-name in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name=u'xyz',
size=20)
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID5, name=u'asdf',
size=20)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['name', 'size'],
sort_dir=['asc'])
self.assertEqualImages(images, (UUID3, UUID5, UUID1, UUID2, UUID4),
unjsonify=False)
images = self.client.image_get_all(sort_key=['size', 'name'],
sort_dir=['asc'])
self.assertEqualImages(images, (UUID1, UUID3, UUID2, UUID5, UUID4),
unjsonify=False)
def test_get_image_details_sort_multiple_dirs(self):
"""
Tests that a detailed call returns list of
public images sorted by name-size and
size-name in ascending and descending orders.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
size=20)
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID5, name='asdf',
size=20)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(sort_key=['name', 'size'],
sort_dir=['asc', 'desc'])
self.assertEqualImages(images, (UUID5, UUID3, UUID1, UUID2, UUID4),
unjsonify=False)
images = self.client.image_get_all(sort_key=['name', 'size'],
sort_dir=['desc', 'asc'])
self.assertEqualImages(images, (UUID4, UUID2, UUID1, UUID3, UUID5),
unjsonify=False)
images = self.client.image_get_all(sort_key=['size', 'name'],
sort_dir=['asc', 'desc'])
self.assertEqualImages(images, (UUID1, UUID2, UUID3, UUID4, UUID5),
unjsonify=False)
images = self.client.image_get_all(sort_key=['size', 'name'],
sort_dir=['desc', 'asc'])
self.assertEqualImages(images, (UUID5, UUID4, UUID3, UUID2, UUID1),
unjsonify=False)
def test_image_get_index_marker(self):
"""Test correct set of images returned with marker param."""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='new name! #123',
status='saving',
created_at=uuid3_time)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='new name! #125',
status='saving',
created_at=uuid4_time)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(marker=UUID3)
self.assertEqualImages(images, (UUID4, UUID2, UUID1), unjsonify=False)
def test_image_get_index_limit(self):
"""Test correct number of images returned with limit param."""
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #123',
status='saving')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #125',
status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(limit=2)
self.assertEqual(2, len(images))
def test_image_get_index_marker_limit(self):
"""Test correct set of images returned with marker/limit params."""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='new name! #123',
status='saving',
created_at=uuid3_time)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='new name! #125',
status='saving',
created_at=uuid4_time)
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(marker=UUID4, limit=1)
self.assertEqualImages(images, (UUID2,), unjsonify=False)
def test_image_get_index_limit_None(self):
"""Test correct set of images returned with limit param == None."""
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #123',
status='saving')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #125',
status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(limit=None)
self.assertEqual(4, len(images))
def test_image_get_index_by_name(self):
"""Test correct set of public, name-filtered image returned.
This is just a sanity check, we test the details call more in-depth.
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #123')
db_api.image_create(self.context, extra_fixture)
images = self.client.image_get_all(filters={'name': 'new name! #123'})
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_image_get_is_public_v2(self):
"""Tests that a detailed call can be filtered by a property"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving',
properties={'is_public': 'avalue'})
context = copy.copy(self.context)
db_api.image_create(context, extra_fixture)
filters = {'is_public': 'avalue'}
images = self.client.image_get_all(filters=filters)
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('avalue', image['properties'][0]['value'])
def test_image_get(self):
"""Tests that the detailed info about an image returned"""
fixture = self.get_fixture(id=UUID1, name='fake image #1',
visibility='shared',
size=13, virtual_size=26,
disk_format='ami', container_format='ami')
data = self.client.image_get(image_id=UUID1)
for k, v in fixture.items():
el = data[k]
self.assertEqual(v, data[k],
"Failed v != data[k] where v = %(v)s and "
"k = %(k)s and data[k] = %(el)s" %
dict(v=v, k=k, el=el))
def test_image_get_non_existing(self):
"""Tests that NotFound is raised when getting a non-existing image"""
self.assertRaises(exception.NotFound,
self.client.image_get,
image_id=_gen_uuid())
def test_image_create_basic(self):
"""Tests that we can add image metadata and returns the new id"""
fixture = self.get_fixture()
new_image = self.client.image_create(values=fixture)
# Test all other attributes set
data = self.client.image_get(image_id=new_image['id'])
for k, v in fixture.items():
self.assertEqual(v, data[k])
# Test status was updated properly
self.assertIn('status', data)
self.assertEqual('active', data['status'])
def test_image_create_with_properties(self):
"""Tests that we can add image metadata with properties"""
fixture = self.get_fixture(location="file:///tmp/glance-tests/2",
properties={'distro': 'Ubuntu 10.04 LTS'})
new_image = self.client.image_create(values=fixture)
self.assertIn('properties', new_image)
self.assertEqual(new_image['properties'][0]['value'],
fixture['properties']['distro'])
del fixture['location']
del fixture['properties']
for k, v in fixture.items():
self.assertEqual(v, new_image[k])
# Test status was updated properly
self.assertIn('status', new_image.keys())
self.assertEqual('active', new_image['status'])
def test_image_create_already_exists(self):
"""Tests proper exception is raised if image with ID already exists"""
fixture = self.get_fixture(id=UUID2,
location="file:///tmp/glance-tests/2")
self.assertRaises(exception.Duplicate,
self.client.image_create,
values=fixture)
def test_image_create_with_bad_status(self):
"""Tests proper exception is raised if a bad status is set"""
fixture = self.get_fixture(status='bad status',
location="file:///tmp/glance-tests/2")
self.assertRaises(exception.Invalid,
self.client.image_create,
values=fixture)
def test_image_update(self):
"""Tests that the registry API updates the image"""
fixture = {'name': 'fake public image #2',
'disk_format': 'vmdk',
'status': 'saving'}
self.assertTrue(self.client.image_update(image_id=UUID2,
values=fixture))
# Test all other attributes set
data = self.client.image_get(image_id=UUID2)
for k, v in fixture.items():
self.assertEqual(v, data[k])
def test_image_update_conflict(self):
"""Tests that the registry API updates the image"""
next_state = 'saving'
fixture = {'name': 'fake public image #2',
'disk_format': 'vmdk',
'status': next_state}
image = self.client.image_get(image_id=UUID2)
current = image['status']
self.assertEqual('active', current)
# image is in 'active' state so this should cause a failure.
from_state = 'saving'
self.assertRaises(exception.Conflict, self.client.image_update,
image_id=UUID2, values=fixture,
from_state=from_state)
try:
self.client.image_update(image_id=UUID2, values=fixture,
from_state=from_state)
except exception.Conflict as exc:
msg = (_('cannot transition from %(current)s to '
'%(next)s in update (wanted '
'from_state=%(from)s)') %
{'current': current, 'next': next_state,
'from': from_state})
self.assertEqual(str(exc), msg)
def test_image_update_with_invalid_min_disk(self):
"""Tests that the registry API updates the image"""
next_state = 'saving'
fixture = {'name': 'fake image',
'disk_format': 'vmdk',
'min_disk': 2 ** 31 + 1,
'status': next_state}
image = self.client.image_get(image_id=UUID2)
current = image['status']
self.assertEqual('active', current)
# image is in 'active' state so this should cause a failure.
from_state = 'saving'
self.assertRaises(exception.Invalid, self.client.image_update,
image_id=UUID2, values=fixture,
from_state=from_state)
def test_image_update_with_invalid_min_ram(self):
"""Tests that the registry API updates the image"""
next_state = 'saving'
fixture = {'name': 'fake image',
'disk_format': 'vmdk',
'min_ram': 2 ** 31 + 1,
'status': next_state}
image = self.client.image_get(image_id=UUID2)
current = image['status']
self.assertEqual('active', current)
# image is in 'active' state so this should cause a failure.
from_state = 'saving'
self.assertRaises(exception.Invalid, self.client.image_update,
image_id=UUID2, values=fixture,
from_state=from_state)
def _test_image_update_not_existing(self):
"""Tests non existing image update doesn't work"""
fixture = self.get_fixture(status='bad status')
self.assertRaises(exception.NotFound,
self.client.image_update,
image_id=_gen_uuid(),
values=fixture)
def test_image_destroy(self):
"""Tests that image metadata is deleted properly"""
# Grab the original number of images
orig_num_images = len(self.client.image_get_all())
# Delete image #2
image = self.FIXTURES[1]
deleted_image = self.client.image_destroy(image_id=image['id'])
self.assertTrue(deleted_image)
self.assertEqual(image['id'], deleted_image['id'])
self.assertTrue(deleted_image['deleted'])
self.assertTrue(deleted_image['deleted_at'])
# Verify one less image
filters = {'deleted': False}
new_num_images = len(self.client.image_get_all(filters=filters))
self.assertEqual(new_num_images, orig_num_images - 1)
def test_image_destroy_not_existing(self):
"""Tests cannot delete non-existing image"""
self.assertRaises(exception.NotFound,
self.client.image_destroy,
image_id=_gen_uuid())
def test_image_get_members(self):
"""Tests getting image members"""
memb_list = self.client.image_member_find(image_id=UUID2)
num_members = len(memb_list)
self.assertEqual(0, num_members)
def test_image_get_members_not_existing(self):
"""Tests getting non-existent image members"""
self.assertRaises(exception.NotFound,
self.client.image_get_members,
image_id=_gen_uuid())
def test_image_member_find(self):
"""Tests getting member images"""
memb_list = self.client.image_member_find(member='pattieblack')
num_members = len(memb_list)
self.assertEqual(0, num_members)
def test_image_member_find_include_deleted(self):
"""Tests getting image members including the deleted member"""
values = dict(image_id=UUID2, member='pattieblack')
# create a member
member = self.client.image_member_create(values=values)
memb_list = self.client.image_member_find(member='pattieblack')
memb_list2 = self.client.image_member_find(member='pattieblack',
include_deleted=True)
self.assertEqual(1, len(memb_list))
self.assertEqual(1, len(memb_list2))
# delete the member
self.client.image_member_delete(memb_id=member['id'])
memb_list = self.client.image_member_find(member='pattieblack')
memb_list2 = self.client.image_member_find(member='pattieblack',
include_deleted=True)
self.assertEqual(0, len(memb_list))
self.assertEqual(1, len(memb_list2))
# create it again
member = self.client.image_member_create(values=values)
memb_list = self.client.image_member_find(member='pattieblack')
memb_list2 = self.client.image_member_find(member='pattieblack',
include_deleted=True)
self.assertEqual(1, len(memb_list))
self.assertEqual(2, len(memb_list2))
def test_add_update_members(self):
"""Tests updating image members"""
values = dict(image_id=UUID2, member='pattieblack')
member = self.client.image_member_create(values=values)
self.assertTrue(member)
values['member'] = 'pattieblack2'
self.assertTrue(self.client.image_member_update(memb_id=member['id'],
values=values))
def test_add_delete_member(self):
"""Tests deleting image members"""
values = dict(image_id=UUID2, member='pattieblack')
member = self.client.image_member_create(values=values)
self.client.image_member_delete(memb_id=member['id'])
memb_list = self.client.image_member_find(member='pattieblack')
self.assertEqual(0, len(memb_list))
class TestRegistryV2ClientApi(base.IsolatedUnitTest):
"""Test proper actions made against a registry service.
Test for both valid and invalid requests.
"""
def setUp(self):
"""Establish a clean test environment"""
super(TestRegistryV2ClientApi, self).setUp()
reload_module(rapi)
def test_configure_registry_client_not_using_use_user_token(self):
self.config(use_user_token=False)
with patch.object(rapi,
'configure_registry_admin_creds') as mock_rapi:
rapi.configure_registry_client()
mock_rapi.assert_called_once_with()
def _get_fake_config_creds(self, auth_url='auth_url', strategy='keystone'):
return {
'user': 'user',
'password': 'password',
'username': 'user',
'tenant': 'tenant',
'auth_url': auth_url,
'strategy': strategy,
'region': 'region'
}
def test_configure_registry_admin_creds(self):
expected = self._get_fake_config_creds(auth_url=None,
strategy='configured_strategy')
self.config(admin_user=expected['user'])
self.config(admin_password=expected['password'])
self.config(admin_tenant_name=expected['tenant'])
self.config(auth_strategy=expected['strategy'])
self.config(auth_region=expected['region'])
self.stubs.Set(os, 'getenv', lambda x: None)
self.assertIsNone(rapi._CLIENT_CREDS)
rapi.configure_registry_admin_creds()
self.assertEqual(expected, rapi._CLIENT_CREDS)
def test_configure_registry_admin_creds_with_auth_url(self):
expected = self._get_fake_config_creds()
self.config(admin_user=expected['user'])
self.config(admin_password=expected['password'])
self.config(admin_tenant_name=expected['tenant'])
self.config(auth_url=expected['auth_url'])
self.config(auth_strategy='test_strategy')
self.config(auth_region=expected['region'])
self.assertIsNone(rapi._CLIENT_CREDS)
rapi.configure_registry_admin_creds()
self.assertEqual(expected, rapi._CLIENT_CREDS)
glance-16.0.1/glance/tests/unit/v2/test_tasks_resource.py 0000666 0001750 0001750 00000112275 13267672245 023450 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 IBM Corp.
# All Rights Reserved.
#
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
from six.moves import http_client as http
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
import webob
import glance.api.v2.tasks
from glance.common import timeutils
import glance.domain
import glance.gateway
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
UUID4 = '6bbe7cc2-eae7-4c0f-b50d-a7160b0c6a86'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
DATETIME = datetime.datetime(2013, 9, 28, 15, 27, 36, 325355)
ISOTIME = '2013-09-28T15:27:36Z'
def _db_fixture(task_id, **kwargs):
default_datetime = timeutils.utcnow()
obj = {
'id': task_id,
'status': 'pending',
'type': 'import',
'input': {},
'result': None,
'owner': None,
'message': None,
'expires_at': default_datetime + datetime.timedelta(days=365),
'created_at': default_datetime,
'updated_at': default_datetime,
'deleted_at': None,
'deleted': False
}
obj.update(kwargs)
return obj
def _domain_fixture(task_id, **kwargs):
default_datetime = timeutils.utcnow()
task_properties = {
'task_id': task_id,
'status': kwargs.get('status', 'pending'),
'task_type': kwargs.get('type', 'import'),
'owner': kwargs.get('owner'),
'expires_at': kwargs.get('expires_at'),
'created_at': kwargs.get('created_at', default_datetime),
'updated_at': kwargs.get('updated_at', default_datetime),
'task_input': kwargs.get('task_input', {}),
'message': kwargs.get('message'),
'result': kwargs.get('result')
}
task = glance.domain.Task(**task_properties)
return task
CONF = cfg.CONF
CONF.import_opt('task_time_to_live', 'glance.common.config', group='task')
class TestTasksController(test_utils.BaseTestCase):
def setUp(self):
super(TestTasksController, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.policy = unit_test_utils.FakePolicyEnforcer()
self.notifier = unit_test_utils.FakeNotifier()
self.store = unit_test_utils.FakeStoreAPI()
self._create_tasks()
self.controller = glance.api.v2.tasks.TasksController(self.db,
self.policy,
self.notifier,
self.store)
self.gateway = glance.gateway.Gateway(self.db, self.store,
self.notifier, self.policy)
def _create_tasks(self):
now = timeutils.utcnow()
times = [now + datetime.timedelta(seconds=5 * i) for i in range(4)]
self.tasks = [
_db_fixture(UUID1, owner=TENANT1,
created_at=times[0], updated_at=times[0]),
# FIXME(venkatesh): change the type to include clone and export
# once they are included as a valid types under Task domain model.
_db_fixture(UUID2, owner=TENANT2, type='import',
created_at=times[1], updated_at=times[1]),
_db_fixture(UUID3, owner=TENANT3, type='import',
created_at=times[2], updated_at=times[2]),
_db_fixture(UUID4, owner=TENANT4, type='import',
created_at=times[3], updated_at=times[3])]
[self.db.task_create(None, task) for task in self.tasks]
def test_index(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request)
self.assertEqual(1, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([UUID1])
self.assertEqual(expected, actual)
def test_index_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.controller.index(request)
self.assertEqual(4, len(output['tasks']))
def test_index_return_parameters(self):
self.config(limit_param_default=1, api_limit_max=4)
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.controller.index(request, marker=UUID3, limit=1,
sort_key='created_at', sort_dir='desc')
self.assertEqual(1, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([UUID2])
self.assertEqual(expected, actual)
self.assertEqual(UUID2, output['next_marker'])
def test_index_next_marker(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.controller.index(request, marker=UUID3, limit=2)
self.assertEqual(2, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([UUID2, UUID1])
self.assertEqual(expected, actual)
self.assertEqual(UUID1, output['next_marker'])
def test_index_no_next_marker(self):
self.config(limit_param_default=1, api_limit_max=3)
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.controller.index(request, marker=UUID1, limit=2)
self.assertEqual(0, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([])
self.assertEqual(expected, actual)
self.assertNotIn('next_marker', output)
def test_index_with_id_filter(self):
request = unit_test_utils.get_fake_request('/tasks?id=%s' % UUID1)
output = self.controller.index(request, filters={'id': UUID1})
self.assertEqual(1, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([UUID1])
self.assertEqual(expected, actual)
def test_index_with_filters_return_many(self):
path = '/tasks?status=pending'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request, filters={'status': 'pending'})
self.assertEqual(4, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([UUID1, UUID2, UUID3, UUID4])
self.assertEqual(sorted(expected), sorted(actual))
def test_index_with_many_filters(self):
url = '/tasks?status=pending&type=import'
request = unit_test_utils.get_fake_request(url, is_admin=True)
output = self.controller.index(request,
filters={
'status': 'pending',
'type': 'import',
'owner': TENANT1,
})
self.assertEqual(1, len(output['tasks']))
actual = set([task.task_id for task in output['tasks']])
expected = set([UUID1])
self.assertEqual(expected, actual)
def test_index_with_marker(self):
self.config(limit_param_default=1, api_limit_max=3)
path = '/tasks'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request, marker=UUID3)
actual = set([task.task_id for task in output['tasks']])
self.assertEqual(1, len(actual))
self.assertIn(UUID2, actual)
def test_index_with_limit(self):
path = '/tasks'
limit = 2
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request, limit=limit)
actual = set([task.task_id for task in output['tasks']])
self.assertEqual(limit, len(actual))
def test_index_greater_than_limit_max(self):
self.config(limit_param_default=1, api_limit_max=3)
path = '/tasks'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request, limit=4)
actual = set([task.task_id for task in output['tasks']])
self.assertEqual(3, len(actual))
self.assertNotIn(output['next_marker'], output)
def test_index_default_limit(self):
self.config(limit_param_default=1, api_limit_max=3)
path = '/tasks'
request = unit_test_utils.get_fake_request(path)
output = self.controller.index(request)
actual = set([task.task_id for task in output['tasks']])
self.assertEqual(1, len(actual))
def test_index_with_sort_dir(self):
path = '/tasks'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request, sort_dir='asc', limit=3)
actual = [task.task_id for task in output['tasks']]
self.assertEqual(3, len(actual))
self.assertEqual([UUID1, UUID2, UUID3], actual)
def test_index_with_sort_key(self):
path = '/tasks'
request = unit_test_utils.get_fake_request(path, is_admin=True)
output = self.controller.index(request, sort_key='created_at', limit=3)
actual = [task.task_id for task in output['tasks']]
self.assertEqual(3, len(actual))
self.assertEqual(UUID4, actual[0])
self.assertEqual(UUID3, actual[1])
self.assertEqual(UUID2, actual[2])
def test_index_with_marker_not_found(self):
fake_uuid = str(uuid.uuid4())
path = '/tasks'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, request, marker=fake_uuid)
def test_index_with_marker_is_not_like_uuid(self):
marker = 'INVALID_UUID'
path = '/tasks'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, request, marker=marker)
def test_index_invalid_sort_key(self):
path = '/tasks'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, request, sort_key='foo')
def test_index_zero_tasks(self):
self.db.reset()
request = unit_test_utils.get_fake_request()
output = self.controller.index(request)
self.assertEqual([], output['tasks'])
def test_get(self):
request = unit_test_utils.get_fake_request()
task = self.controller.get(request, task_id=UUID1)
self.assertEqual(UUID1, task.task_id)
self.assertEqual('import', task.type)
def test_get_non_existent(self):
request = unit_test_utils.get_fake_request()
task_id = str(uuid.uuid4())
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.get, request, task_id)
def test_get_not_allowed(self):
request = unit_test_utils.get_fake_request()
self.assertEqual(TENANT1, request.context.tenant)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.get, request, UUID4)
@mock.patch.object(glance.gateway.Gateway, 'get_task_factory')
@mock.patch.object(glance.gateway.Gateway, 'get_task_executor_factory')
@mock.patch.object(glance.gateway.Gateway, 'get_task_repo')
def test_create(self, mock_get_task_repo, mock_get_task_executor_factory,
mock_get_task_factory):
# setup
request = unit_test_utils.get_fake_request()
task = {
"type": "import",
"input": {
"import_from": "swift://cloud.foo/myaccount/mycontainer/path",
"import_from_format": "qcow2",
"image_properties": {}
}
}
get_task_factory = mock.Mock()
mock_get_task_factory.return_value = get_task_factory
new_task = mock.Mock()
get_task_factory.new_task.return_value = new_task
new_task.run.return_value = mock.ANY
get_task_executor_factory = mock.Mock()
mock_get_task_executor_factory.return_value = get_task_executor_factory
get_task_executor_factory.new_task_executor.return_value = mock.Mock()
get_task_repo = mock.Mock()
mock_get_task_repo.return_value = get_task_repo
get_task_repo.add.return_value = mock.Mock()
# call
self.controller.create(request, task=task)
# assert
self.assertEqual(1, get_task_factory.new_task.call_count)
self.assertEqual(1, get_task_repo.add.call_count)
self.assertEqual(
1, get_task_executor_factory.new_task_executor.call_count)
@mock.patch('glance.common.scripts.utils.get_image_data_iter')
@mock.patch('glance.common.scripts.utils.validate_location_uri')
def test_create_with_live_time(self, mock_validate_location_uri,
mock_get_image_data_iter):
request = unit_test_utils.get_fake_request()
task = {
"type": "import",
"input": {
"import_from": "http://download.cirros-cloud.net/0.3.4/"
"cirros-0.3.4-x86_64-disk.img",
"import_from_format": "qcow2",
"image_properties": {
"disk_format": "qcow2",
"container_format": "bare",
"name": "test-task"
}
}
}
new_task = self.controller.create(request, task=task)
executor_factory = self.gateway.get_task_executor_factory(
request.context)
task_executor = executor_factory.new_task_executor(request.context)
task_executor.begin_processing(new_task.task_id)
success_task = self.controller.get(request, new_task.task_id)
# ignore second and microsecond to avoid flaky runs
task_live_time = (success_task.expires_at.replace(second=0,
microsecond=0) -
success_task.updated_at.replace(second=0,
microsecond=0))
task_live_time_hour = (task_live_time.days * 24 +
task_live_time.seconds / 3600)
self.assertEqual(CONF.task.task_time_to_live, task_live_time_hour)
def test_create_with_wrong_import_form(self):
request = unit_test_utils.get_fake_request()
wrong_import_from = [
"swift://cloud.foo/myaccount/mycontainer/path",
"file:///path",
"cinder://volume-id"
]
executor_factory = self.gateway.get_task_executor_factory(
request.context)
task_repo = self.gateway.get_task_repo(request.context)
for import_from in wrong_import_from:
task = {
"type": "import",
"input": {
"import_from": import_from,
"import_from_format": "qcow2",
"image_properties": {
"disk_format": "qcow2",
"container_format": "bare",
"name": "test-task"
}
}
}
new_task = self.controller.create(request, task=task)
task_executor = executor_factory.new_task_executor(request.context)
task_executor.begin_processing(new_task.task_id)
final_task = task_repo.get(new_task.task_id)
self.assertEqual('failure', final_task.status)
if import_from.startswith("file:///"):
msg = ("File based imports are not allowed. Please use a "
"non-local source of image data.")
else:
supported = ['http', ]
msg = ("The given uri is not valid. Please specify a "
"valid uri from the following list of supported uri "
"%(supported)s") % {'supported': supported}
self.assertEqual(msg, final_task.message)
def test_create_with_properties_missed(self):
request = unit_test_utils.get_fake_request()
executor_factory = self.gateway.get_task_executor_factory(
request.context)
task_repo = self.gateway.get_task_repo(request.context)
task = {
"type": "import",
"input": {
"import_from": "swift://cloud.foo/myaccount/mycontainer/path",
"import_from_format": "qcow2",
}
}
new_task = self.controller.create(request, task=task)
task_executor = executor_factory.new_task_executor(request.context)
task_executor.begin_processing(new_task.task_id)
final_task = task_repo.get(new_task.task_id)
self.assertEqual('failure', final_task.status)
msg = "Input does not contain 'image_properties' field"
self.assertEqual(msg, final_task.message)
@mock.patch.object(glance.gateway.Gateway, 'get_task_factory')
def test_notifications_on_create(self, mock_get_task_factory):
request = unit_test_utils.get_fake_request()
new_task = mock.MagicMock(type='import')
mock_get_task_factory.new_task.return_value = new_task
new_task.run.return_value = mock.ANY
task = {"type": "import", "input": {
"import_from": "http://cloud.foo/myaccount/mycontainer/path",
"import_from_format": "qcow2",
"image_properties": {}
}
}
task = self.controller.create(request, task=task)
output_logs = [nlog for nlog in self.notifier.get_logs()
if nlog['event_type'] == 'task.create']
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.create', output_log['event_type'])
class TestTasksControllerPolicies(base.IsolatedUnitTest):
def setUp(self):
super(TestTasksControllerPolicies, self).setUp()
self.db = unit_test_utils.FakeDB()
self.policy = unit_test_utils.FakePolicyEnforcer()
self.controller = glance.api.v2.tasks.TasksController(self.db,
self.policy)
def test_index_unauthorized(self):
rules = {"get_tasks": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.index,
request)
def test_get_unauthorized(self):
rules = {"get_task": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.get,
request, task_id=UUID2)
def test_access_get_unauthorized(self):
rules = {"tasks_api_access": False,
"get_task": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.get,
request, task_id=UUID2)
def test_create_task_unauthorized(self):
rules = {"add_task": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
task = {'type': 'import', 'input': {"import_from": "fake"}}
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, task)
def test_delete(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPMethodNotAllowed,
self.controller.delete,
request,
'fake_id')
def test_access_delete_unauthorized(self):
rules = {"tasks_api_access": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.delete,
request,
'fake_id')
class TestTasksDeserializerPolicies(test_utils.BaseTestCase):
# NOTE(rosmaita): this is a bit weird, but we check the access
# policy in the RequestDeserializer for calls that take bodies
# or query strings because we want to make sure the failure is
# a 403, not a 400 due to bad request format
def setUp(self):
super(TestTasksDeserializerPolicies, self).setUp()
self.policy = unit_test_utils.FakePolicyEnforcer()
self.deserializer = glance.api.v2.tasks.RequestDeserializer(
schema=None, policy_engine=self.policy)
bad_path = '/tasks?limit=NaN'
def test_access_index_authorized_bad_query_string(self):
"""Allow access, fail with 400"""
rules = {"tasks_api_access": True,
"get_tasks": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(self.bad_path)
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.index,
request)
def test_access_index_unauthorized(self):
"""Disallow access with bad request, fail with 403"""
rules = {"tasks_api_access": False,
"get_tasks": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(self.bad_path)
self.assertRaises(webob.exc.HTTPForbidden, self.deserializer.index,
request)
bad_task = {'typo': 'import', 'input': {"import_from": "fake"}}
def test_access_create_authorized_bad_format(self):
"""Allow access, fail with 400"""
rules = {"tasks_api_access": True,
"add_task": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes(self.bad_task)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create,
request)
def test_access_create_unauthorized(self):
"""Disallow access with bad request, fail with 403"""
rules = {"tasks_api_access": False,
"add_task": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes(self.bad_task)
self.assertRaises(webob.exc.HTTPForbidden,
self.deserializer.create,
request)
class TestTasksDeserializer(test_utils.BaseTestCase):
def setUp(self):
super(TestTasksDeserializer, self).setUp()
self.deserializer = glance.api.v2.tasks.RequestDeserializer()
def test_create_no_body(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.create, request)
def test_create(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({
'type': 'import',
'input': {'import_from':
'swift://cloud.foo/myaccount/mycontainer/path',
'import_from_format': 'qcow2',
'image_properties': {'name': 'fake1'}},
})
output = self.deserializer.create(request)
properties = {
'type': 'import',
'input': {'import_from':
'swift://cloud.foo/myaccount/mycontainer/path',
'import_from_format': 'qcow2',
'image_properties': {'name': 'fake1'}},
}
self.maxDiff = None
expected = {'task': properties}
self.assertEqual(expected, output)
def test_index(self):
marker = str(uuid.uuid4())
path = '/tasks?limit=1&marker=%s' % marker
request = unit_test_utils.get_fake_request(path)
expected = {'limit': 1,
'marker': marker,
'sort_key': 'created_at',
'sort_dir': 'desc',
'filters': {}}
output = self.deserializer.index(request)
self.assertEqual(expected, output)
def test_index_strip_params_from_filters(self):
type = 'import'
path = '/tasks?type=%s' % type
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(type, output['filters']['type'])
def test_index_with_many_filter(self):
status = 'success'
type = 'import'
path = '/tasks?status=%(status)s&type=%(type)s' % {'status': status,
'type': type}
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(status, output['filters']['status'])
self.assertEqual(type, output['filters']['type'])
def test_index_with_filter_and_limit(self):
status = 'success'
path = '/tasks?status=%s&limit=1' % status
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(status, output['filters']['status'])
self.assertEqual(1, output['limit'])
def test_index_non_integer_limit(self):
request = unit_test_utils.get_fake_request('/tasks?limit=blah')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_zero_limit(self):
request = unit_test_utils.get_fake_request('/tasks?limit=0')
expected = {'limit': 0,
'sort_key': 'created_at',
'sort_dir': 'desc',
'filters': {}}
output = self.deserializer.index(request)
self.assertEqual(expected, output)
def test_index_negative_limit(self):
path = '/tasks?limit=-1'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_fraction(self):
request = unit_test_utils.get_fake_request('/tasks?limit=1.1')
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_invalid_status(self):
path = '/tasks?status=blah'
request = unit_test_utils.get_fake_request(path)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_marker(self):
marker = str(uuid.uuid4())
path = '/tasks?marker=%s' % marker
request = unit_test_utils.get_fake_request(path)
output = self.deserializer.index(request)
self.assertEqual(marker, output.get('marker'))
def test_index_marker_not_specified(self):
request = unit_test_utils.get_fake_request('/tasks')
output = self.deserializer.index(request)
self.assertNotIn('marker', output)
def test_index_limit_not_specified(self):
request = unit_test_utils.get_fake_request('/tasks')
output = self.deserializer.index(request)
self.assertNotIn('limit', output)
def test_index_sort_key_id(self):
request = unit_test_utils.get_fake_request('/tasks?sort_key=id')
output = self.deserializer.index(request)
expected = {
'sort_key': 'id',
'sort_dir': 'desc',
'filters': {}
}
self.assertEqual(expected, output)
def test_index_sort_dir_asc(self):
request = unit_test_utils.get_fake_request('/tasks?sort_dir=asc')
output = self.deserializer.index(request)
expected = {
'sort_key': 'created_at',
'sort_dir': 'asc',
'filters': {}}
self.assertEqual(expected, output)
def test_index_sort_dir_bad_value(self):
request = unit_test_utils.get_fake_request('/tasks?sort_dir=invalid')
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.index,
request)
class TestTasksSerializer(test_utils.BaseTestCase):
def setUp(self):
super(TestTasksSerializer, self).setUp()
self.serializer = glance.api.v2.tasks.ResponseSerializer()
self.fixtures = [
_domain_fixture(UUID1, type='import', status='pending',
task_input={'loc': 'fake'}, result={},
owner=TENANT1, message='', created_at=DATETIME,
updated_at=DATETIME),
_domain_fixture(UUID2, type='import', status='processing',
task_input={'loc': 'bake'}, owner=TENANT2,
message='', created_at=DATETIME,
updated_at=DATETIME, result={}),
_domain_fixture(UUID3, type='import', status='success',
task_input={'loc': 'foo'}, owner=TENANT3,
message='', created_at=DATETIME,
updated_at=DATETIME, result={},
expires_at=DATETIME),
_domain_fixture(UUID4, type='import', status='failure',
task_input={'loc': 'boo'}, owner=TENANT4,
message='', created_at=DATETIME,
updated_at=DATETIME, result={},
expires_at=DATETIME),
]
def test_index(self):
expected = {
'tasks': [
{
'id': UUID1,
'type': 'import',
'status': 'pending',
'owner': TENANT1,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID1,
'schema': '/v2/schemas/task',
},
{
'id': UUID2,
'type': 'import',
'status': 'processing',
'owner': TENANT2,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID2,
'schema': '/v2/schemas/task',
},
{
'id': UUID3,
'type': 'import',
'status': 'success',
'owner': TENANT3,
'expires_at': ISOTIME,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID3,
'schema': '/v2/schemas/task',
},
{
'id': UUID4,
'type': 'import',
'status': 'failure',
'owner': TENANT4,
'expires_at': ISOTIME,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID4,
'schema': '/v2/schemas/task',
},
],
'first': '/v2/tasks',
'schema': '/v2/schemas/tasks',
}
request = webob.Request.blank('/v2/tasks')
response = webob.Response(request=request)
task_fixtures = [f for f in self.fixtures]
result = {'tasks': task_fixtures}
self.serializer.index(response, result)
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_index_next_marker(self):
request = webob.Request.blank('/v2/tasks')
response = webob.Response(request=request)
task_fixtures = [f for f in self.fixtures]
result = {'tasks': task_fixtures, 'next_marker': UUID2}
self.serializer.index(response, result)
output = jsonutils.loads(response.body)
self.assertEqual('/v2/tasks?marker=%s' % UUID2, output['next'])
def test_index_carries_query_parameters(self):
url = '/v2/tasks?limit=10&sort_key=id&sort_dir=asc'
request = webob.Request.blank(url)
response = webob.Response(request=request)
task_fixtures = [f for f in self.fixtures]
result = {'tasks': task_fixtures, 'next_marker': UUID2}
self.serializer.index(response, result)
output = jsonutils.loads(response.body)
expected_url = '/v2/tasks?limit=10&sort_dir=asc&sort_key=id'
self.assertEqual(unit_test_utils.sort_url_by_qs_keys(expected_url),
unit_test_utils.sort_url_by_qs_keys(output['first']))
expect_next = '/v2/tasks?limit=10&marker=%s&sort_dir=asc&sort_key=id'
self.assertEqual(unit_test_utils.sort_url_by_qs_keys(
expect_next % UUID2),
unit_test_utils.sort_url_by_qs_keys(output['next']))
def test_get(self):
expected = {
'id': UUID4,
'type': 'import',
'status': 'failure',
'input': {'loc': 'boo'},
'result': {},
'owner': TENANT4,
'message': '',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'expires_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID4,
'schema': '/v2/schemas/task',
}
response = webob.Response()
self.serializer.get(response, self.fixtures[3])
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_get_ensure_expires_at_not_returned(self):
expected = {
'id': UUID1,
'type': 'import',
'status': 'pending',
'input': {'loc': 'fake'},
'result': {},
'owner': TENANT1,
'message': '',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID1,
'schema': '/v2/schemas/task',
}
response = webob.Response()
self.serializer.get(response, self.fixtures[0])
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
expected = {
'id': UUID2,
'type': 'import',
'status': 'processing',
'input': {'loc': 'bake'},
'result': {},
'owner': TENANT2,
'message': '',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/tasks/%s' % UUID2,
'schema': '/v2/schemas/task',
}
response = webob.Response()
self.serializer.get(response, self.fixtures[1])
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_create(self):
response = webob.Response()
self.serializer.create(response, self.fixtures[3])
serialized_task = jsonutils.loads(response.body)
self.assertEqual(http.CREATED, response.status_int)
self.assertEqual(self.fixtures[3].task_id,
serialized_task['id'])
self.assertEqual(self.fixtures[3].task_input,
serialized_task['input'])
self.assertIn('expires_at', serialized_task)
self.assertEqual('application/json', response.content_type)
def test_create_ensure_expires_at_is_not_returned(self):
response = webob.Response()
self.serializer.create(response, self.fixtures[0])
serialized_task = jsonutils.loads(response.body)
self.assertEqual(http.CREATED, response.status_int)
self.assertEqual(self.fixtures[0].task_id,
serialized_task['id'])
self.assertEqual(self.fixtures[0].task_input,
serialized_task['input'])
self.assertNotIn('expires_at', serialized_task)
self.assertEqual('application/json', response.content_type)
response = webob.Response()
self.serializer.create(response, self.fixtures[1])
serialized_task = jsonutils.loads(response.body)
self.assertEqual(http.CREATED, response.status_int)
self.assertEqual(self.fixtures[1].task_id,
serialized_task['id'])
self.assertEqual(self.fixtures[1].task_input,
serialized_task['input'])
self.assertNotIn('expires_at', serialized_task)
self.assertEqual('application/json', response.content_type)
glance-16.0.1/glance/tests/unit/v2/test_registry_api.py 0000666 0001750 0001750 00000164245 13267672245 023121 0 ustar zuul zuul 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
from oslo_serialization import jsonutils
import routes
import six
from six.moves import http_client as http
import webob
import glance.api.common
import glance.common.config
from glance.common import timeutils
import glance.context
from glance.db.sqlalchemy import api as db_api
from glance.db.sqlalchemy import models as db_models
from glance.registry.api import v2 as rserver
from glance.tests.unit import base
from glance.tests import utils as test_utils
_gen_uuid = lambda: str(uuid.uuid4())
UUID1 = _gen_uuid()
UUID2 = _gen_uuid()
class TestRegistryRPC(base.IsolatedUnitTest):
def setUp(self):
super(TestRegistryRPC, self).setUp()
self.mapper = routes.Mapper()
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=True)
uuid1_time = timeutils.utcnow()
uuid2_time = uuid1_time + datetime.timedelta(seconds=5)
self.FIXTURES = [
{'id': UUID1,
'name': 'fake image #1',
'status': 'active',
'disk_format': 'ami',
'container_format': 'ami',
'visibility': 'shared',
'created_at': uuid1_time,
'updated_at': uuid1_time,
'deleted_at': None,
'deleted': False,
'checksum': None,
'min_disk': 0,
'min_ram': 0,
'size': 13,
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}, 'status': 'active'}],
'properties': {'type': 'kernel'}},
{'id': UUID2,
'name': 'fake image #2',
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'visibility': 'public',
'created_at': uuid2_time,
'updated_at': uuid2_time,
'deleted_at': None,
'deleted': False,
'checksum': None,
'min_disk': 5,
'min_ram': 256,
'size': 19,
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID2),
'metadata': {}, 'status': 'active'}],
'properties': {}}]
self.context = glance.context.RequestContext(is_admin=True)
db_api.get_engine()
self.destroy_fixtures()
self.create_fixtures()
def tearDown(self):
"""Clear the test environment"""
super(TestRegistryRPC, self).tearDown()
self.destroy_fixtures()
def create_fixtures(self):
for fixture in self.FIXTURES:
db_api.image_create(self.context, fixture)
# We write a fake image file to the filesystem
with open("%s/%s" % (self.test_dir, fixture['id']), 'wb') as image:
image.write(b"chunk00000remainder")
image.flush()
def destroy_fixtures(self):
# Easiest to just drop the models and re-create them...
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def _compare_images_and_uuids(self, uuids, images):
self.assertListEqual(uuids, [image['id'] for image in images])
def test_show(self):
"""Tests that registry API endpoint returns the expected image."""
fixture = {'id': UUID2,
'name': 'fake image #2',
'size': 19,
'min_ram': 256,
'min_disk': 5,
'checksum': None}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get',
'kwargs': {'image_id': UUID2},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
image = res_dict
for k, v in six.iteritems(fixture):
self.assertEqual(v, image[k])
def test_show_unknown(self):
"""Tests the registry API endpoint returns 404 for an unknown id."""
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get',
'kwargs': {'image_id': _gen_uuid()},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual('glance.common.exception.ImageNotFound',
res_dict["_error"]["cls"])
def test_get_index(self):
"""Tests that the image_get_all command returns list of images."""
fixture = {'id': UUID2,
'name': 'fake image #2',
'size': 19,
'checksum': None}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': fixture},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(1, len(images))
for k, v in six.iteritems(fixture):
self.assertEqual(v, images[0][k])
def test_get_index_marker(self):
"""Tests that the registry API returns list of public images.
Must conforms to a marker query param.
"""
uuid5_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid4_time = uuid5_time + datetime.timedelta(seconds=5)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid4_time,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = {'id': UUID5,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid5_time,
'updated_at': uuid5_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID4, "is_public": True},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
# should be sorted by created_at desc, id desc
# page should start after marker 4
uuid_list = [UUID5, UUID2]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_marker_and_name_asc(self):
"""Test marker and null name ascending
Tests that the registry API returns 200
when a marker and a null name are combined
ascending order
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': None,
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'sort_key': ['name'],
'sort_dir': ['asc']},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(2, len(images))
def test_get_index_marker_and_name_desc(self):
"""Test marker and null name descending
Tests that the registry API returns 200
when a marker and a null name are combined
descending order
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': None,
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'sort_key': ['name'],
'sort_dir': ['desc']},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
def test_get_index_marker_and_disk_format_asc(self):
"""Test marker and null disk format ascending
Tests that the registry API returns 200
when a marker and a null disk_format are combined
ascending order
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': None,
'container_format': 'ovf',
'name': 'Fake image',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'sort_key': ['disk_format'],
'sort_dir': ['asc']},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(2, len(images))
def test_get_index_marker_and_disk_format_desc(self):
"""Test marker and null disk format descending
Tests that the registry API returns 200
when a marker and a null disk_format are combined
descending order
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': None,
'container_format': 'ovf',
'name': 'Fake image',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'sort_key': ['disk_format'],
'sort_dir': ['desc']},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
def test_get_index_marker_and_container_format_asc(self):
"""Test marker and null container format ascending
Tests that the registry API returns 200
when a marker and a null container_format are combined
ascending order
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': None,
'name': 'Fake image',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'sort_key': ['container_format'],
'sort_dir': ['asc']},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(2, len(images))
def test_get_index_marker_and_container_format_desc(self):
"""Test marker and null container format descending
Tests that the registry API returns 200
when a marker and a null container_format are combined
descending order
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': None,
'name': 'Fake image',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'sort_key': ['container_format'],
'sort_dir': ['desc']},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
def test_get_index_unknown_marker(self):
"""Tests the registry API returns a NotFound with unknown marker."""
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': _gen_uuid()},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
result = jsonutils.loads(res.body)[0]
self.assertIn("_error", result)
self.assertIn("NotFound", result["_error"]["cls"])
def test_get_index_limit(self):
"""Tests that the registry API returns list of public images.
Must conforms to a limit query param.
"""
uuid3_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid4_time = uuid3_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid4_time,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'limit': 1},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
images = jsonutils.loads(res.body)[0]
self.assertEqual(http.OK, res.status_int)
self._compare_images_and_uuids([UUID4], images)
def test_get_index_limit_marker(self):
"""Tests that the registry API returns list of public images.
Must conforms to limit and marker query params.
"""
uuid3_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid4_time = uuid3_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
extra_fixture = {'id': _gen_uuid(),
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid4_time,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'marker': UUID3, 'limit': 1},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(http.OK, res.status_int)
images = res_dict
self._compare_images_and_uuids([UUID2], images)
def test_get_index_filter_name(self):
"""Tests that the registry API returns list of public images.
Use a specific name. This is really a sanity check, filtering is
tested more in-depth using /images/detail
"""
extra_fixture = {'id': _gen_uuid(),
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
extra_fixture = {'id': _gen_uuid(),
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'name': 'new name! #123'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(http.OK, res.status_int)
images = res_dict
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_get_index_filter_on_user_defined_properties(self):
"""Tests that the registry API returns list of public images.
Use a specific user-defined properties.
"""
properties = {'distro': 'ubuntu', 'arch': 'i386', 'type': 'kernel'}
extra_id = _gen_uuid()
extra_fixture = {'id': extra_id,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'image-extra-1',
'size': 19, 'properties': properties,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
# testing with a common property.
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'type': 'kernel'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(2, len(images))
self.assertEqual(extra_id, images[0]['id'])
self.assertEqual(UUID1, images[1]['id'])
# testing with a non-existent value for a common property.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'type': 'random'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
# testing with a non-existent value for a common property.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'type': 'random'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
# testing with a non-existent property.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'poo': 'random'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
# testing with multiple existing properties.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'type': 'kernel', 'distro': 'ubuntu'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(1, len(images))
self.assertEqual(extra_id, images[0]['id'])
# testing with multiple existing properties but non-existent values.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'type': 'random', 'distro': 'random'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
# testing with multiple non-existing properties.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'typo': 'random', 'poo': 'random'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
# testing with one existing property and the other non-existing.
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'type': 'kernel', 'poo': 'random'}},
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
images = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(images))
def test_get_index_sort_default_created_at_desc(self):
"""Tests that the registry API returns list of public images.
Must conforms to a default sort key/dir.
"""
uuid5_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid4_time = uuid5_time + datetime.timedelta(seconds=5)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid4_time,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = {'id': UUID5,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid5_time,
'updated_at': uuid5_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(http.OK, res.status_int)
images = res_dict
# (flaper87)registry's v1 forced is_public to True
# when no value was specified. This is not
# the default behaviour anymore.
uuid_list = [UUID3, UUID4, UUID5, UUID2, UUID1]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_name_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by name in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'xyz',
'size': 20,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = {'id': UUID5,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': None,
'size': 20,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['name'], 'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID5, UUID3, UUID1, UUID2, UUID4]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_status_desc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by status in descending order.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'queued',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'xyz',
'size': 20,
'checksum': None,
'created_at': uuid4_time,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['status'], 'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID1, UUID2, UUID4, UUID3]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_disk_format_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by disk_format in ascending order.
"""
uuid3_time = timeutils.utcnow() + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'ami',
'container_format': 'ami',
'name': 'asdf',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vdi',
'container_format': 'ovf',
'name': 'xyz',
'size': 20,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['disk_format'], 'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID1, UUID3, UUID4, UUID2]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_container_format_desc(self):
"""Tests that the registry API returns list of public images.
Must be sorted alphabetically by container_format in descending order.
"""
uuid3_time = timeutils.utcnow() + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'ami',
'container_format': 'ami',
'name': 'asdf',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'iso',
'container_format': 'bare',
'name': 'xyz',
'size': 20,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['container_format'],
'sort_dir': ['desc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID2, UUID4, UUID3, UUID1]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_size_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted by size in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'ami',
'container_format': 'ami',
'name': 'asdf',
'size': 100,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'iso',
'container_format': 'bare',
'name': 'xyz',
'size': 2,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['size'],
'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID4, UUID1, UUID2, UUID3]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_created_at_asc(self):
"""Tests that the registry API returns list of public images.
Must be sorted by created_at in ascending order.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None,
'created_at': uuid3_time,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': uuid4_time,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['created_at'],
'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID1, UUID2, UUID4, UUID3]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_updated_at_desc(self):
"""Tests that the registry API returns list of public images.
Must be sorted by updated_at in descending order.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 19,
'checksum': None,
'created_at': None,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'new name! #123',
'size': 20,
'checksum': None,
'created_at': None,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['updated_at'],
'sort_dir': ['desc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID3, UUID4, UUID2, UUID1]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_multiple_keys_one_sort_dir(self):
"""
Tests that the registry API returns list of
public images sorted by name-size and size-name with ascending
sort direction.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 19,
'checksum': None,
'created_at': None,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'xyz',
'size': 20,
'checksum': None,
'created_at': None,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = {'id': UUID5,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 20,
'checksum': None,
'created_at': None,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['name', 'size'],
'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID3, UUID5, UUID1, UUID2, UUID4]
self._compare_images_and_uuids(uuid_list, images)
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['size', 'name'],
'sort_dir': ['asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID1, UUID3, UUID2, UUID5, UUID4]
self._compare_images_and_uuids(uuid_list, images)
def test_get_index_sort_multiple_keys_multiple_sort_dirs(self):
"""
Tests that the registry API returns list of
public images sorted by name-size and size-name
with ascending and descending directions.
"""
uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)
uuid3_time = uuid4_time + datetime.timedelta(seconds=5)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 19,
'checksum': None,
'created_at': None,
'updated_at': uuid3_time}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'xyz',
'size': 20,
'checksum': None,
'created_at': None,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = {'id': UUID5,
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 20,
'checksum': None,
'created_at': None,
'updated_at': uuid4_time}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID4, UUID2, UUID1, UUID3, UUID5]
self._compare_images_and_uuids(uuid_list, images)
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['size', 'name'],
'sort_dir': ['desc', 'asc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID5, UUID4, UUID3, UUID2, UUID1]
self._compare_images_and_uuids(uuid_list, images)
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['name', 'size'],
'sort_dir': ['asc', 'desc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID5, UUID3, UUID1, UUID2, UUID4]
self._compare_images_and_uuids(uuid_list, images)
cmd = [{
'command': 'image_get_all',
'kwargs': {'sort_key': ['size', 'name'],
'sort_dir': ['asc', 'desc']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
images = res_dict
uuid_list = [UUID1, UUID2, UUID3, UUID4, UUID5]
self._compare_images_and_uuids(uuid_list, images)
def test_create_image(self):
"""Tests that the registry API creates the image"""
fixture = {'name': 'fake public image',
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf'}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_create',
'kwargs': {'values': fixture}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
for k, v in six.iteritems(fixture):
self.assertEqual(v, res_dict[k])
# Test status was updated properly
self.assertEqual('active', res_dict['status'])
def test_create_image_with_min_disk(self):
"""Tests that the registry API creates the image"""
fixture = {'name': 'fake public image',
'visibility': 'public',
'status': 'active',
'min_disk': 5,
'disk_format': 'vhd',
'container_format': 'ovf'}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_create',
'kwargs': {'values': fixture}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(fixture['min_disk'], res_dict['min_disk'])
def test_create_image_with_min_ram(self):
"""Tests that the registry API creates the image"""
fixture = {'name': 'fake public image',
'visibility': 'public',
'status': 'active',
'min_ram': 256,
'disk_format': 'vhd',
'container_format': 'ovf'}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_create',
'kwargs': {'values': fixture}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(fixture['min_ram'], res_dict['min_ram'])
def test_create_image_with_min_ram_default(self):
"""Tests that the registry API creates the image"""
fixture = {'name': 'fake public image',
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf'}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_create',
'kwargs': {'values': fixture}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(0, res_dict['min_ram'])
def test_create_image_with_min_disk_default(self):
"""Tests that the registry API creates the image"""
fixture = {'name': 'fake public image',
'status': 'active',
'visibility': 'public',
'disk_format': 'vhd',
'container_format': 'ovf'}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_create',
'kwargs': {'values': fixture}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(0, res_dict['min_disk'])
def test_update_image(self):
"""Tests that the registry API updates the image"""
fixture = {'name': 'fake public image #2',
'min_disk': 5,
'min_ram': 256,
'disk_format': 'raw'}
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_update',
'kwargs': {'values': fixture,
'image_id': UUID2}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)[0]
self.assertNotEqual(res_dict['created_at'],
res_dict['updated_at'])
for k, v in six.iteritems(fixture):
self.assertEqual(v, res_dict[k])
def _send_request(self, command, kwargs, method):
req = webob.Request.blank('/rpc')
req.method = method
cmd = [{'command': command, 'kwargs': kwargs}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
return res.status_int, res_dict
def _expect_fail(self, command, kwargs, error_cls, method='POST'):
# on any exception status_int is always 200, so have to check _error
# dict
code, res_dict = self._send_request(command, kwargs, method)
self.assertIn('_error', res_dict)
self.assertEqual(error_cls, res_dict['_error']['cls'])
return res_dict
def _expect_ok(self, command, kwargs, method, expected_status=http.OK):
code, res_dict = self._send_request(command, kwargs)
self.assertEqual(expected_status, code)
return res_dict
def test_create_image_bad_name(self):
fixture = {'name': u'A bad name \U0001fff2', 'status': 'queued'}
self._expect_fail('image_create',
{'values': fixture},
'glance.common.exception.Invalid')
def test_create_image_bad_location(self):
fixture = {'status': 'queued',
'locations': [{'url': u'file:///tmp/tests/\U0001fee2',
'metadata': {},
'status': 'active'}]}
self._expect_fail('image_create',
{'values': fixture},
'glance.common.exception.Invalid')
def test_create_image_bad_property(self):
fixture = {'status': 'queued',
'properties': {'ok key': u' bad value \U0001f2aa'}}
self._expect_fail('image_create',
{'values': fixture},
'glance.common.exception.Invalid')
fixture = {'status': 'queued',
'properties': {u'invalid key \U00010020': 'ok value'}}
self._expect_fail('image_create',
{'values': fixture},
'glance.common.exception.Invalid')
def test_update_image_bad_tag(self):
self._expect_fail('image_tag_create',
{'value': u'\U0001fff2', 'image_id': UUID2},
'glance.common.exception.Invalid')
def test_update_image_bad_name(self):
fixture = {'name': u'A bad name \U0001fff2'}
self._expect_fail('image_update',
{'values': fixture, 'image_id': UUID1},
'glance.common.exception.Invalid')
def test_update_image_bad_location(self):
fixture = {'locations':
[{'url': u'file:///tmp/glance-tests/\U0001fee2',
'metadata': {},
'status': 'active'}]}
self._expect_fail('image_update',
{'values': fixture, 'image_id': UUID1},
'glance.common.exception.Invalid')
def test_update_bad_property(self):
fixture = {'properties': {'ok key': u' bad value \U0001f2aa'}}
self._expect_fail('image_update',
{'values': fixture, 'image_id': UUID2},
'glance.common.exception.Invalid')
fixture = {'properties': {u'invalid key \U00010020': 'ok value'}}
self._expect_fail('image_update',
{'values': fixture, 'image_id': UUID2},
'glance.common.exception.Invalid')
def test_delete_image(self):
"""Tests that the registry API deletes the image"""
# Grab the original number of images
req = webob.Request.blank('/rpc')
req.method = "POST"
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'deleted': False}}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(http.OK, res.status_int)
orig_num_images = len(res_dict)
# Delete image #2
cmd = [{
'command': 'image_destroy',
'kwargs': {'image_id': UUID2}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
# Verify one less image
cmd = [{
'command': 'image_get_all',
'kwargs': {'filters': {'deleted': False}}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
res_dict = jsonutils.loads(res.body)[0]
self.assertEqual(http.OK, res.status_int)
new_num_images = len(res_dict)
self.assertEqual(new_num_images, orig_num_images - 1)
def test_delete_image_response(self):
"""Tests that the registry API delete returns the image metadata"""
image = self.FIXTURES[0]
req = webob.Request.blank('/rpc')
req.method = 'POST'
cmd = [{
'command': 'image_destroy',
'kwargs': {'image_id': image['id']}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
deleted_image = jsonutils.loads(res.body)[0]
self.assertEqual(image['id'], deleted_image['id'])
self.assertTrue(deleted_image['deleted'])
self.assertTrue(deleted_image['deleted_at'])
def test_get_image_members(self):
"""Tests members listing for existing images."""
req = webob.Request.blank('/rpc')
req.method = 'POST'
cmd = [{
'command': 'image_member_find',
'kwargs': {'image_id': UUID2}
}]
req.body = jsonutils.dump_as_bytes(cmd)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
memb_list = jsonutils.loads(res.body)[0]
self.assertEqual(0, len(memb_list))
glance-16.0.1/glance/tests/unit/v2/test_image_members_resource.py 0000666 0001750 0001750 00000056277 13267672245 025130 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import glance_store
from oslo_config import cfg
from oslo_serialization import jsonutils
from six.moves import http_client as http
import webob
import glance.api.v2.image_members
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
ISOTIME = '2012-05-16T15:27:36Z'
CONF = cfg.CONF
BASE_URI = unit_test_utils.BASE_URI
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
UUID4 = '6bbe7cc2-eae7-4c0f-b50d-a7160b0c6a86'
UUID5 = '3eee7cc2-eae7-4c0f-b50d-a7160b0c62ed'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
def _db_fixture(id, **kwargs):
obj = {
'id': id,
'name': None,
'visibility': 'shared',
'properties': {},
'checksum': None,
'owner': None,
'status': 'queued',
'tags': [],
'size': None,
'locations': [],
'protected': False,
'disk_format': None,
'container_format': None,
'deleted': False,
'min_ram': None,
'min_disk': None,
}
obj.update(kwargs)
return obj
def _db_image_member_fixture(image_id, member_id, **kwargs):
obj = {
'image_id': image_id,
'member': member_id,
'status': 'pending',
}
obj.update(kwargs)
return obj
def _domain_fixture(id, **kwargs):
properties = {
'id': id,
}
properties.update(kwargs)
return glance.domain.ImageMembership(**properties)
class TestImageMembersController(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMembersController, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.store = unit_test_utils.FakeStoreAPI()
self.policy = unit_test_utils.FakePolicyEnforcer()
self.notifier = unit_test_utils.FakeNotifier()
self._create_images()
self._create_image_members()
self.controller = glance.api.v2.image_members.ImageMembersController(
self.db,
self.policy,
self.notifier,
self.store)
glance_store.register_opts(CONF)
self.config(default_store='filesystem',
filesystem_store_datadir=self.test_dir,
group="glance_store")
glance_store.create_stores()
def _create_images(self):
self.images = [
_db_fixture(UUID1, owner=TENANT1, name='1', size=256,
visibility='public',
locations=[{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}, 'status': 'active'}]),
_db_fixture(UUID2, owner=TENANT1, name='2', size=512),
_db_fixture(UUID3, owner=TENANT3, name='3', size=512),
_db_fixture(UUID4, owner=TENANT4, name='4', size=1024),
_db_fixture(UUID5, owner=TENANT1, name='5', size=1024),
]
[self.db.image_create(None, image) for image in self.images]
self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
def _create_image_members(self):
self.image_members = [
_db_image_member_fixture(UUID2, TENANT4),
_db_image_member_fixture(UUID3, TENANT4),
_db_image_member_fixture(UUID3, TENANT2),
_db_image_member_fixture(UUID4, TENANT1),
]
[self.db.image_member_create(None, image_member)
for image_member in self.image_members]
def test_index(self):
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, UUID2)
self.assertEqual(1, len(output['members']))
actual = set([image_member.member_id
for image_member in output['members']])
expected = set([TENANT4])
self.assertEqual(expected, actual)
def test_index_no_members(self):
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, UUID5)
self.assertEqual(0, len(output['members']))
self.assertEqual({'members': []}, output)
def test_index_member_view(self):
# UUID3 is a shared image owned by TENANT3
# UUID3 has members TENANT2 and TENANT4
# When TENANT4 lists members for UUID3, should not see TENANT2
request = unit_test_utils.get_fake_request(tenant=TENANT4)
output = self.controller.index(request, UUID3)
self.assertEqual(1, len(output['members']))
actual = set([image_member.member_id
for image_member in output['members']])
expected = set([TENANT4])
self.assertEqual(expected, actual)
def test_index_private_image(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound, self.controller.index,
request, UUID5)
def test_index_public_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.index,
request, UUID1)
def test_index_private_image_visible_members_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
output = self.controller.index(request, UUID4)
self.assertEqual(1, len(output['members']))
actual = set([image_member.member_id
for image_member in output['members']])
expected = set([TENANT1])
self.assertEqual(expected, actual)
def test_index_allowed_by_get_members_policy(self):
rules = {"get_members": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, UUID2)
self.assertEqual(1, len(output['members']))
def test_index_forbidden_by_get_members_policy(self):
rules = {"get_members": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.index,
request, image_id=UUID2)
def test_show(self):
request = unit_test_utils.get_fake_request(tenant=TENANT1)
output = self.controller.show(request, UUID2, TENANT4)
expected = self.image_members[0]
self.assertEqual(expected['image_id'], output.image_id)
self.assertEqual(expected['member'], output.member_id)
self.assertEqual(expected['status'], output.status)
def test_show_by_member(self):
request = unit_test_utils.get_fake_request(tenant=TENANT4)
output = self.controller.show(request, UUID2, TENANT4)
expected = self.image_members[0]
self.assertEqual(expected['image_id'], output.image_id)
self.assertEqual(expected['member'], output.member_id)
self.assertEqual(expected['status'], output.status)
def test_show_forbidden(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
request, UUID2, TENANT4)
def test_show_not_found(self):
# one member should not be able to view status of another member
# of the same image
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
request, UUID3, TENANT4)
def test_create(self):
request = unit_test_utils.get_fake_request()
image_id = UUID2
member_id = TENANT3
output = self.controller.create(request, image_id=image_id,
member_id=member_id)
self.assertEqual(UUID2, output.image_id)
self.assertEqual(TENANT3, output.member_id)
def test_create_allowed_by_add_policy(self):
rules = {"add_member": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
output = self.controller.create(request, image_id=UUID2,
member_id=TENANT3)
self.assertEqual(UUID2, output.image_id)
self.assertEqual(TENANT3, output.member_id)
def test_create_forbidden_by_add_policy(self):
rules = {"add_member": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, image_id=UUID2, member_id=TENANT3)
def test_create_duplicate_member(self):
request = unit_test_utils.get_fake_request()
image_id = UUID2
member_id = TENANT3
output = self.controller.create(request, image_id=image_id,
member_id=member_id)
self.assertEqual(UUID2, output.image_id)
self.assertEqual(TENANT3, output.member_id)
self.assertRaises(webob.exc.HTTPConflict, self.controller.create,
request, image_id=image_id, member_id=member_id)
def test_create_overlimit(self):
self.config(image_member_quota=0)
request = unit_test_utils.get_fake_request()
image_id = UUID2
member_id = TENANT3
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, request,
image_id=image_id, member_id=member_id)
def test_create_unlimited(self):
self.config(image_member_quota=-1)
request = unit_test_utils.get_fake_request()
image_id = UUID2
member_id = TENANT3
output = self.controller.create(request, image_id=image_id,
member_id=member_id)
self.assertEqual(UUID2, output.image_id)
self.assertEqual(TENANT3, output.member_id)
def test_member_create_raises_bad_request_for_unicode_value(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
request, image_id=UUID5,
member_id=u'\U0001f693')
def test_update_done_by_member(self):
request = unit_test_utils.get_fake_request(tenant=TENANT4)
image_id = UUID2
member_id = TENANT4
output = self.controller.update(request, image_id=image_id,
member_id=member_id,
status='accepted')
self.assertEqual(UUID2, output.image_id)
self.assertEqual(TENANT4, output.member_id)
self.assertEqual('accepted', output.status)
def test_update_done_by_member_forbidden_by_policy(self):
rules = {"modify_member": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(tenant=TENANT4)
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, image_id=UUID2, member_id=TENANT4,
status='accepted')
def test_update_done_by_member_allowed_by_policy(self):
rules = {"modify_member": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(tenant=TENANT4)
output = self.controller.update(request, image_id=UUID2,
member_id=TENANT4,
status='accepted')
self.assertEqual(UUID2, output.image_id)
self.assertEqual(TENANT4, output.member_id)
self.assertEqual('accepted', output.status)
def test_update_done_by_owner(self):
request = unit_test_utils.get_fake_request(tenant=TENANT1)
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
request, UUID2, TENANT4, status='accepted')
def test_update_non_existent_image(self):
request = unit_test_utils.get_fake_request(tenant=TENANT1)
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
request, '123', TENANT4, status='accepted')
def test_update_invalid_status(self):
request = unit_test_utils.get_fake_request(tenant=TENANT4)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
request, UUID2, TENANT4, status='accept')
def test_create_private_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, UUID4, TENANT2)
def test_create_public_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
request, UUID1, TENANT2)
def test_create_image_does_not_exist(self):
request = unit_test_utils.get_fake_request()
image_id = 'fake-image-id'
member_id = TENANT3
self.assertRaises(webob.exc.HTTPNotFound, self.controller.create,
request, image_id=image_id, member_id=member_id)
def test_delete(self):
request = unit_test_utils.get_fake_request()
member_id = TENANT4
image_id = UUID2
res = self.controller.delete(request, image_id, member_id)
self.assertEqual(b'', res.body)
self.assertEqual(http.NO_CONTENT, res.status_code)
found_member = self.db.image_member_find(
request.context, image_id=image_id, member=member_id)
self.assertEqual([], found_member)
def test_delete_by_member(self):
request = unit_test_utils.get_fake_request(tenant=TENANT4)
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
request, UUID2, TENANT4)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, UUID2)
self.assertEqual(1, len(output['members']))
actual = set([image_member.member_id
for image_member in output['members']])
expected = set([TENANT4])
self.assertEqual(expected, actual)
def test_delete_allowed_by_policies(self):
rules = {"get_member": True, "delete_member": True}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(tenant=TENANT1)
output = self.controller.delete(request, image_id=UUID2,
member_id=TENANT4)
request = unit_test_utils.get_fake_request()
output = self.controller.index(request, UUID2)
self.assertEqual(0, len(output['members']))
def test_delete_forbidden_by_get_member_policy(self):
rules = {"get_member": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(tenant=TENANT1)
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
request, UUID2, TENANT4)
def test_delete_forbidden_by_delete_member_policy(self):
rules = {"delete_member": False}
self.policy.set_rules(rules)
request = unit_test_utils.get_fake_request(tenant=TENANT1)
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
request, UUID2, TENANT4)
def test_delete_private_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
request, UUID4, TENANT1)
def test_delete_public_image(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
request, UUID1, TENANT1)
def test_delete_image_does_not_exist(self):
request = unit_test_utils.get_fake_request()
member_id = TENANT2
image_id = 'fake-image-id'
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
request, image_id, member_id)
def test_delete_member_does_not_exist(self):
request = unit_test_utils.get_fake_request()
member_id = 'fake-member-id'
image_id = UUID2
found_member = self.db.image_member_find(
request.context, image_id=image_id, member=member_id)
self.assertEqual([], found_member)
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
request, image_id, member_id)
class TestImageMembersSerializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMembersSerializer, self).setUp()
self.serializer = glance.api.v2.image_members.ResponseSerializer()
self.fixtures = [
_domain_fixture(id='1', image_id=UUID2, member_id=TENANT1,
status='accepted',
created_at=DATETIME, updated_at=DATETIME),
_domain_fixture(id='2', image_id=UUID2, member_id=TENANT2,
status='pending',
created_at=DATETIME, updated_at=DATETIME),
]
def test_index(self):
expected = {
'members': [
{
'image_id': UUID2,
'member_id': TENANT1,
'status': 'accepted',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'schema': '/v2/schemas/member',
},
{
'image_id': UUID2,
'member_id': TENANT2,
'status': 'pending',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'schema': '/v2/schemas/member',
},
],
'schema': '/v2/schemas/members',
}
request = webob.Request.blank('/v2/images/%s/members' % UUID2)
response = webob.Response(request=request)
result = {'members': self.fixtures}
self.serializer.index(response, result)
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_show(self):
expected = {
'image_id': UUID2,
'member_id': TENANT1,
'status': 'accepted',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'schema': '/v2/schemas/member',
}
request = webob.Request.blank('/v2/images/%s/members/%s'
% (UUID2, TENANT1))
response = webob.Response(request=request)
result = self.fixtures[0]
self.serializer.show(response, result)
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_create(self):
expected = {'image_id': UUID2,
'member_id': TENANT1,
'status': 'accepted',
'schema': '/v2/schemas/member',
'created_at': ISOTIME,
'updated_at': ISOTIME}
request = webob.Request.blank('/v2/images/%s/members/%s'
% (UUID2, TENANT1))
response = webob.Response(request=request)
result = self.fixtures[0]
self.serializer.create(response, result)
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
def test_update(self):
expected = {'image_id': UUID2,
'member_id': TENANT1,
'status': 'accepted',
'schema': '/v2/schemas/member',
'created_at': ISOTIME,
'updated_at': ISOTIME}
request = webob.Request.blank('/v2/images/%s/members/%s'
% (UUID2, TENANT1))
response = webob.Response(request=request)
result = self.fixtures[0]
self.serializer.update(response, result)
actual = jsonutils.loads(response.body)
self.assertEqual(expected, actual)
self.assertEqual('application/json', response.content_type)
class TestImagesDeserializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializer, self).setUp()
self.deserializer = glance.api.v2.image_members.RequestDeserializer()
def test_create(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'member': TENANT1})
output = self.deserializer.create(request)
expected = {'member_id': TENANT1}
self.assertEqual(expected, output)
def test_create_invalid(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'mem': TENANT1})
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_create_no_body(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_create_member_empty(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'member': ''})
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_create_list_return_error(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes([TENANT1])
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
request)
def test_update_list_return_error(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes([TENANT1])
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.update,
request)
def test_update(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'status': 'accepted'})
output = self.deserializer.update(request)
expected = {'status': 'accepted'}
self.assertEqual(expected, output)
def test_update_invalid(self):
request = unit_test_utils.get_fake_request()
request.body = jsonutils.dump_as_bytes({'mem': TENANT1})
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.update,
request)
def test_update_no_body(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.update,
request)
glance-16.0.1/glance/tests/unit/v2/test_schemas_resource.py 0000666 0001750 0001750 00000006017 13267672245 023742 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import glance.api.v2.schemas
import glance.db.sqlalchemy.api as db_api
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
class TestSchemasController(test_utils.BaseTestCase):
def setUp(self):
super(TestSchemasController, self).setUp()
self.controller = glance.api.v2.schemas.Controller()
def test_image(self):
req = unit_test_utils.get_fake_request()
output = self.controller.image(req)
self.assertEqual('image', output['name'])
expected = set(['status', 'name', 'tags', 'checksum', 'created_at',
'disk_format', 'updated_at', 'visibility', 'self',
'file', 'container_format', 'schema', 'id', 'size',
'direct_url', 'min_ram', 'min_disk', 'protected',
'locations', 'owner', 'virtual_size'])
self.assertEqual(expected, set(output['properties'].keys()))
def test_image_has_correct_statuses(self):
req = unit_test_utils.get_fake_request()
output = self.controller.image(req)
self.assertEqual('image', output['name'])
expected_statuses = set(db_api.STATUSES)
actual_statuses = set(output['properties']['status']['enum'])
self.assertEqual(expected_statuses, actual_statuses)
def test_images(self):
req = unit_test_utils.get_fake_request()
output = self.controller.images(req)
self.assertEqual('images', output['name'])
expected = set(['images', 'schema', 'first', 'next'])
self.assertEqual(expected, set(output['properties'].keys()))
expected = set(['{schema}', '{first}', '{next}'])
actual = set([link['href'] for link in output['links']])
self.assertEqual(expected, actual)
def test_member(self):
req = unit_test_utils.get_fake_request()
output = self.controller.member(req)
self.assertEqual('member', output['name'])
expected = set(['status', 'created_at', 'updated_at', 'image_id',
'member_id', 'schema'])
self.assertEqual(expected, set(output['properties'].keys()))
def test_members(self):
req = unit_test_utils.get_fake_request()
output = self.controller.members(req)
self.assertEqual('members', output['name'])
expected = set(['schema', 'members'])
self.assertEqual(expected, set(output['properties'].keys()))
glance-16.0.1/glance/tests/unit/test_db_metadef.py 0000666 0001750 0001750 00000055605 13267672245 022142 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# Copyright 2014 Intel Corporation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import encodeutils
from glance.common import exception
import glance.context
import glance.db
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
NAMESPACE1 = 'namespace1'
NAMESPACE2 = 'namespace2'
NAMESPACE3 = 'namespace3'
NAMESPACE4 = 'namespace4'
PROPERTY1 = 'Property1'
PROPERTY2 = 'Property2'
PROPERTY3 = 'Property3'
OBJECT1 = 'Object1'
OBJECT2 = 'Object2'
OBJECT3 = 'Object3'
TAG1 = 'Tag1'
TAG2 = 'Tag2'
TAG3 = 'Tag3'
TAG4 = 'Tag4'
TAG5 = 'Tag5'
RESOURCE_TYPE1 = 'ResourceType1'
RESOURCE_TYPE2 = 'ResourceType2'
RESOURCE_TYPE3 = 'ResourceType3'
def _db_namespace_fixture(**kwargs):
namespace = {
'namespace': None,
'display_name': None,
'description': None,
'visibility': True,
'protected': False,
'owner': None
}
namespace.update(kwargs)
return namespace
def _db_property_fixture(name, **kwargs):
property = {
'name': name,
'json_schema': {"type": "string", "title": "title"},
}
property.update(kwargs)
return property
def _db_object_fixture(name, **kwargs):
obj = {
'name': name,
'description': None,
'json_schema': {},
'required': '[]',
}
obj.update(kwargs)
return obj
def _db_tag_fixture(name, **kwargs):
obj = {
'name': name
}
obj.update(kwargs)
return obj
def _db_tags_fixture(names=None):
tags = []
if names:
tag_name_list = names
else:
tag_name_list = [TAG1, TAG2, TAG3]
for tag_name in tag_name_list:
tags.append(_db_tag_fixture(tag_name))
return tags
def _db_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
'protected': False,
}
obj.update(kwargs)
return obj
def _db_namespace_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
'properties_target': None,
'prefix': None,
}
obj.update(kwargs)
return obj
class TestMetadefRepo(test_utils.BaseTestCase):
def setUp(self):
super(TestMetadefRepo, self).setUp()
self.db = unit_test_utils.FakeDB(initialize=False)
self.context = glance.context.RequestContext(user=USER1,
tenant=TENANT1)
self.namespace_repo = glance.db.MetadefNamespaceRepo(self.context,
self.db)
self.property_repo = glance.db.MetadefPropertyRepo(self.context,
self.db)
self.object_repo = glance.db.MetadefObjectRepo(self.context,
self.db)
self.tag_repo = glance.db.MetadefTagRepo(self.context,
self.db)
self.resource_type_repo = glance.db.MetadefResourceTypeRepo(
self.context, self.db)
self.namespace_factory = glance.domain.MetadefNamespaceFactory()
self.property_factory = glance.domain.MetadefPropertyFactory()
self.object_factory = glance.domain.MetadefObjectFactory()
self.tag_factory = glance.domain.MetadefTagFactory()
self.resource_type_factory = glance.domain.MetadefResourceTypeFactory()
self._create_namespaces()
self._create_properties()
self._create_objects()
self._create_tags()
self._create_resource_types()
def _create_namespaces(self):
self.namespaces = [
_db_namespace_fixture(namespace=NAMESPACE1,
display_name='1',
description='desc1',
visibility='private',
protected=True,
owner=TENANT1),
_db_namespace_fixture(namespace=NAMESPACE2,
display_name='2',
description='desc2',
visibility='public',
protected=False,
owner=TENANT1),
_db_namespace_fixture(namespace=NAMESPACE3,
display_name='3',
description='desc3',
visibility='private',
protected=True,
owner=TENANT3),
_db_namespace_fixture(namespace=NAMESPACE4,
display_name='4',
description='desc4',
visibility='public',
protected=True,
owner=TENANT3)
]
[self.db.metadef_namespace_create(None, namespace)
for namespace in self.namespaces]
def _create_properties(self):
self.properties = [
_db_property_fixture(name=PROPERTY1),
_db_property_fixture(name=PROPERTY2),
_db_property_fixture(name=PROPERTY3)
]
[self.db.metadef_property_create(self.context, NAMESPACE1, property)
for property in self.properties]
[self.db.metadef_property_create(self.context, NAMESPACE4, property)
for property in self.properties]
def _create_objects(self):
self.objects = [
_db_object_fixture(name=OBJECT1,
description='desc1'),
_db_object_fixture(name=OBJECT2,
description='desc2'),
_db_object_fixture(name=OBJECT3,
description='desc3'),
]
[self.db.metadef_object_create(self.context, NAMESPACE1, object)
for object in self.objects]
[self.db.metadef_object_create(self.context, NAMESPACE4, object)
for object in self.objects]
def _create_tags(self):
self.tags = [
_db_tag_fixture(name=TAG1),
_db_tag_fixture(name=TAG2),
_db_tag_fixture(name=TAG3),
]
[self.db.metadef_tag_create(self.context, NAMESPACE1, tag)
for tag in self.tags]
[self.db.metadef_tag_create(self.context, NAMESPACE4, tag)
for tag in self.tags]
def _create_resource_types(self):
self.resource_types = [
_db_resource_type_fixture(name=RESOURCE_TYPE1,
protected=False),
_db_resource_type_fixture(name=RESOURCE_TYPE2,
protected=False),
_db_resource_type_fixture(name=RESOURCE_TYPE3,
protected=True),
]
[self.db.metadef_resource_type_create(self.context, resource_type)
for resource_type in self.resource_types]
def test_get_namespace(self):
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(NAMESPACE1, namespace.namespace)
self.assertEqual('desc1', namespace.description)
self.assertEqual('1', namespace.display_name)
self.assertEqual(TENANT1, namespace.owner)
self.assertTrue(namespace.protected)
self.assertEqual('private', namespace.visibility)
def test_get_namespace_not_found(self):
fake_namespace = "fake_namespace"
exc = self.assertRaises(exception.NotFound,
self.namespace_repo.get,
fake_namespace)
self.assertIn(fake_namespace, encodeutils.exception_to_unicode(exc))
def test_get_namespace_forbidden(self):
self.assertRaises(exception.NotFound,
self.namespace_repo.get,
NAMESPACE3)
def test_list_namespace(self):
namespaces = self.namespace_repo.list()
namespace_names = set([n.namespace for n in namespaces])
self.assertEqual(set([NAMESPACE1, NAMESPACE2, NAMESPACE4]),
namespace_names)
def test_list_private_namespaces(self):
filters = {'visibility': 'private'}
namespaces = self.namespace_repo.list(filters=filters)
namespace_names = set([n.namespace for n in namespaces])
self.assertEqual(set([NAMESPACE1]), namespace_names)
def test_add_namespace(self):
# NOTE(pawel-koniszewski): Change db_namespace_fixture to
# namespace_factory when namespace primary key in DB
# will be changed from Integer to UUID
namespace = _db_namespace_fixture(namespace='added_namespace',
display_name='fake',
description='fake_desc',
visibility='public',
protected=True,
owner=TENANT1)
self.assertEqual('added_namespace', namespace['namespace'])
self.db.metadef_namespace_create(None, namespace)
retrieved_namespace = self.namespace_repo.get(namespace['namespace'])
self.assertEqual('added_namespace', retrieved_namespace.namespace)
def test_save_namespace(self):
namespace = self.namespace_repo.get(NAMESPACE1)
namespace.display_name = 'save_name'
namespace.description = 'save_desc'
self.namespace_repo.save(namespace)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual('save_name', namespace.display_name)
self.assertEqual('save_desc', namespace.description)
def test_remove_namespace(self):
namespace = self.namespace_repo.get(NAMESPACE1)
self.namespace_repo.remove(namespace)
self.assertRaises(exception.NotFound, self.namespace_repo.get,
NAMESPACE1)
def test_remove_namespace_not_found(self):
fake_name = 'fake_name'
namespace = self.namespace_repo.get(NAMESPACE1)
namespace.namespace = fake_name
exc = self.assertRaises(exception.NotFound, self.namespace_repo.remove,
namespace)
self.assertIn(fake_name, encodeutils.exception_to_unicode(exc))
def test_get_property(self):
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(PROPERTY1, property.name)
self.assertEqual(namespace.namespace, property.namespace.namespace)
def test_get_property_not_found(self):
exc = self.assertRaises(exception.NotFound,
self.property_repo.get,
NAMESPACE2, PROPERTY1)
self.assertIn(PROPERTY1, encodeutils.exception_to_unicode(exc))
def test_list_property(self):
properties = self.property_repo.list(filters={'namespace': NAMESPACE1})
property_names = set([p.name for p in properties])
self.assertEqual(set([PROPERTY1, PROPERTY2, PROPERTY3]),
property_names)
def test_list_property_empty_result(self):
properties = self.property_repo.list(filters={'namespace': NAMESPACE2})
property_names = set([p.name for p in properties])
self.assertEqual(set([]),
property_names)
def test_list_property_namespace_not_found(self):
exc = self.assertRaises(exception.NotFound, self.property_repo.list,
filters={'namespace': 'not-a-namespace'})
self.assertIn('not-a-namespace', encodeutils.exception_to_unicode(exc))
def test_add_property(self):
# NOTE(pawel-koniszewski): Change db_property_fixture to
# property_factory when property primary key in DB
# will be changed from Integer to UUID
property = _db_property_fixture(name='added_property')
self.assertEqual('added_property', property['name'])
self.db.metadef_property_create(self.context, NAMESPACE1, property)
retrieved_property = self.property_repo.get(NAMESPACE1,
'added_property')
self.assertEqual('added_property', retrieved_property.name)
def test_add_property_namespace_forbidden(self):
# NOTE(pawel-koniszewski): Change db_property_fixture to
# property_factory when property primary key in DB
# will be changed from Integer to UUID
property = _db_property_fixture(name='added_property')
self.assertEqual('added_property', property['name'])
self.assertRaises(exception.Forbidden, self.db.metadef_property_create,
self.context, NAMESPACE3, property)
def test_add_property_namespace_not_found(self):
# NOTE(pawel-koniszewski): Change db_property_fixture to
# property_factory when property primary key in DB
# will be changed from Integer to UUID
property = _db_property_fixture(name='added_property')
self.assertEqual('added_property', property['name'])
self.assertRaises(exception.NotFound, self.db.metadef_property_create,
self.context, 'not_a_namespace', property)
def test_save_property(self):
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
property.schema = '{"save": "schema"}'
self.property_repo.save(property)
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
self.assertEqual(PROPERTY1, property.name)
self.assertEqual('{"save": "schema"}', property.schema)
def test_remove_property(self):
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
self.property_repo.remove(property)
self.assertRaises(exception.NotFound, self.property_repo.get,
NAMESPACE1, PROPERTY1)
def test_remove_property_not_found(self):
fake_name = 'fake_name'
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
property.name = fake_name
self.assertRaises(exception.NotFound, self.property_repo.remove,
property)
def test_get_object(self):
object = self.object_repo.get(NAMESPACE1, OBJECT1)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(OBJECT1, object.name)
self.assertEqual('desc1', object.description)
self.assertEqual(['[]'], object.required)
self.assertEqual({}, object.properties)
self.assertEqual(namespace.namespace, object.namespace.namespace)
def test_get_object_not_found(self):
exc = self.assertRaises(exception.NotFound, self.object_repo.get,
NAMESPACE2, OBJECT1)
self.assertIn(OBJECT1, encodeutils.exception_to_unicode(exc))
def test_list_object(self):
objects = self.object_repo.list(filters={'namespace': NAMESPACE1})
object_names = set([o.name for o in objects])
self.assertEqual(set([OBJECT1, OBJECT2, OBJECT3]), object_names)
def test_list_object_empty_result(self):
objects = self.object_repo.list(filters={'namespace': NAMESPACE2})
object_names = set([o.name for o in objects])
self.assertEqual(set([]), object_names)
def test_list_object_namespace_not_found(self):
exc = self.assertRaises(exception.NotFound, self.object_repo.list,
filters={'namespace': 'not-a-namespace'})
self.assertIn('not-a-namespace', encodeutils.exception_to_unicode(exc))
def test_add_object(self):
# NOTE(pawel-koniszewski): Change db_object_fixture to
# object_factory when object primary key in DB
# will be changed from Integer to UUID
object = _db_object_fixture(name='added_object')
self.assertEqual('added_object', object['name'])
self.db.metadef_object_create(self.context, NAMESPACE1, object)
retrieved_object = self.object_repo.get(NAMESPACE1,
'added_object')
self.assertEqual('added_object', retrieved_object.name)
def test_add_object_namespace_forbidden(self):
# NOTE(pawel-koniszewski): Change db_object_fixture to
# object_factory when object primary key in DB
# will be changed from Integer to UUID
object = _db_object_fixture(name='added_object')
self.assertEqual('added_object', object['name'])
self.assertRaises(exception.Forbidden, self.db.metadef_object_create,
self.context, NAMESPACE3, object)
def test_add_object_namespace_not_found(self):
# NOTE(pawel-koniszewski): Change db_object_fixture to
# object_factory when object primary key in DB
# will be changed from Integer to UUID
object = _db_object_fixture(name='added_object')
self.assertEqual('added_object', object['name'])
self.assertRaises(exception.NotFound, self.db.metadef_object_create,
self.context, 'not-a-namespace', object)
def test_save_object(self):
object = self.object_repo.get(NAMESPACE1, OBJECT1)
object.required = ['save_req']
object.description = 'save_desc'
self.object_repo.save(object)
object = self.object_repo.get(NAMESPACE1, OBJECT1)
self.assertEqual(OBJECT1, object.name)
self.assertEqual(['save_req'], object.required)
self.assertEqual('save_desc', object.description)
def test_remove_object(self):
object = self.object_repo.get(NAMESPACE1, OBJECT1)
self.object_repo.remove(object)
self.assertRaises(exception.NotFound, self.object_repo.get,
NAMESPACE1, OBJECT1)
def test_remove_object_not_found(self):
fake_name = 'fake_name'
object = self.object_repo.get(NAMESPACE1, OBJECT1)
object.name = fake_name
self.assertRaises(exception.NotFound, self.object_repo.remove,
object)
def test_list_resource_type(self):
resource_type = self.resource_type_repo.list(
filters={'namespace': NAMESPACE1})
self.assertEqual(0, len(resource_type))
def test_get_tag(self):
tag = self.tag_repo.get(NAMESPACE1, TAG1)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(TAG1, tag.name)
self.assertEqual(namespace.namespace, tag.namespace.namespace)
def test_get_tag_not_found(self):
exc = self.assertRaises(exception.NotFound, self.tag_repo.get,
NAMESPACE2, TAG1)
self.assertIn(TAG1, encodeutils.exception_to_unicode(exc))
def test_list_tag(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
def test_list_tag_empty_result(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE2})
tag_names = set([t.name for t in tags])
self.assertEqual(set([]), tag_names)
def test_list_tag_namespace_not_found(self):
exc = self.assertRaises(exception.NotFound, self.tag_repo.list,
filters={'namespace': 'not-a-namespace'})
self.assertIn('not-a-namespace', encodeutils.exception_to_unicode(exc))
def test_add_tag(self):
# NOTE(pawel-koniszewski): Change db_tag_fixture to
# tag_factory when tag primary key in DB
# will be changed from Integer to UUID
tag = _db_tag_fixture(name='added_tag')
self.assertEqual('added_tag', tag['name'])
self.db.metadef_tag_create(self.context, NAMESPACE1, tag)
retrieved_tag = self.tag_repo.get(NAMESPACE1, 'added_tag')
self.assertEqual('added_tag', retrieved_tag.name)
def test_add_tags(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
tags = _db_tags_fixture([TAG3, TAG4, TAG5])
self.db.metadef_tag_create_tags(self.context, NAMESPACE1, tags)
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG3, TAG4, TAG5]), tag_names)
def test_add_duplicate_tags_with_pre_existing_tags(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
tags = _db_tags_fixture([TAG5, TAG4, TAG5])
self.assertRaises(exception.Duplicate,
self.db.metadef_tag_create_tags,
self.context, NAMESPACE1, tags)
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
def test_add_tag_namespace_forbidden(self):
# NOTE(pawel-koniszewski): Change db_tag_fixture to
# tag_factory when tag primary key in DB
# will be changed from Integer to UUID
tag = _db_tag_fixture(name='added_tag')
self.assertEqual('added_tag', tag['name'])
self.assertRaises(exception.Forbidden, self.db.metadef_tag_create,
self.context, NAMESPACE3, tag)
def test_add_tag_namespace_not_found(self):
# NOTE(pawel-koniszewski): Change db_tag_fixture to
# tag_factory when tag primary key in DB
# will be changed from Integer to UUID
tag = _db_tag_fixture(name='added_tag')
self.assertEqual('added_tag', tag['name'])
self.assertRaises(exception.NotFound, self.db.metadef_tag_create,
self.context, 'not-a-namespace', tag)
def test_save_tag(self):
tag = self.tag_repo.get(NAMESPACE1, TAG1)
self.tag_repo.save(tag)
tag = self.tag_repo.get(NAMESPACE1, TAG1)
self.assertEqual(TAG1, tag.name)
def test_remove_tag(self):
tag = self.tag_repo.get(NAMESPACE1, TAG1)
self.tag_repo.remove(tag)
self.assertRaises(exception.NotFound, self.tag_repo.get,
NAMESPACE1, TAG1)
def test_remove_tag_not_found(self):
fake_name = 'fake_name'
tag = self.tag_repo.get(NAMESPACE1, TAG1)
tag.name = fake_name
self.assertRaises(exception.NotFound, self.tag_repo.remove, tag)
glance-16.0.1/glance/tests/unit/__init__.py 0000666 0001750 0001750 00000000000 13267672245 020543 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/test_auth.py 0000666 0001750 0001750 00000114231 13267672245 021020 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
from oslotest import moxstubout
from six.moves import http_client as http
import webob
from glance.api import authorization
from glance.common import auth
from glance.common import exception
from glance.common import timeutils
import glance.domain
from glance.tests.unit import utils as unittest_utils
from glance.tests import utils
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
class FakeResponse(object):
"""
Simple class that masks the inconsistency between
webob.Response.status_int and httplib.Response.status
"""
def __init__(self, resp):
self.resp = resp
def __getitem__(self, key):
return self.resp.headers.get(key)
@property
def status(self):
return self.resp.status_int
class V2Token(object):
def __init__(self):
self.tok = self.base_token
def add_service_no_type(self):
catalog = self.tok['access']['serviceCatalog']
service_type = {"name": "glance_no_type"}
catalog.append(service_type)
service = catalog[-1]
service['endpoints'] = [self.base_endpoint]
def add_service(self, s_type, region_list=None):
if region_list is None:
region_list = []
catalog = self.tok['access']['serviceCatalog']
service_type = {"type": s_type, "name": "glance"}
catalog.append(service_type)
service = catalog[-1]
endpoint_list = []
if region_list == []:
endpoint_list.append(self.base_endpoint)
else:
for region in region_list:
endpoint = self.base_endpoint
endpoint['region'] = region
endpoint_list.append(endpoint)
service['endpoints'] = endpoint_list
@property
def token(self):
return self.tok
@property
def base_endpoint(self):
return {
"adminURL": "http://localhost:9292",
"internalURL": "http://localhost:9292",
"publicURL": "http://localhost:9292"
}
@property
def base_token(self):
return {
"access": {
"token": {
"expires": "2010-11-23T16:40:53.321584",
"id": "5c7f8799-2e54-43e4-851b-31f81871b6c",
"tenant": {"id": "1", "name": "tenant-ok"}
},
"serviceCatalog": [
],
"user": {
"id": "2",
"roles": [{
"tenantId": "1",
"id": "1",
"name": "Admin"
}],
"name": "joeadmin"
}
}
}
class TestKeystoneAuthPlugin(utils.BaseTestCase):
"""Test that the Keystone auth plugin works properly"""
def setUp(self):
super(TestKeystoneAuthPlugin, self).setUp()
mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.stubs = mox_fixture.stubs
def test_get_plugin_from_strategy_keystone(self):
strategy = auth.get_plugin_from_strategy('keystone')
self.assertIsInstance(strategy, auth.KeystoneStrategy)
self.assertTrue(strategy.configure_via_auth)
def test_get_plugin_from_strategy_keystone_configure_via_auth_false(self):
strategy = auth.get_plugin_from_strategy('keystone',
configure_via_auth=False)
self.assertIsInstance(strategy, auth.KeystoneStrategy)
self.assertFalse(strategy.configure_via_auth)
def test_required_creds(self):
"""
Test that plugin created without required
credential pieces raises an exception
"""
bad_creds = [
{}, # missing everything
{
'username': 'user1',
'strategy': 'keystone',
'password': 'pass'
}, # missing auth_url
{
'password': 'pass',
'strategy': 'keystone',
'auth_url': 'http://localhost/v1'
}, # missing username
{
'username': 'user1',
'strategy': 'keystone',
'auth_url': 'http://localhost/v1'
}, # missing password
{
'username': 'user1',
'password': 'pass',
'auth_url': 'http://localhost/v1'
}, # missing strategy
{
'username': 'user1',
'password': 'pass',
'strategy': 'keystone',
'auth_url': 'http://localhost/v2.0/'
}, # v2.0: missing tenant
{
'username': None,
'password': 'pass',
'auth_url': 'http://localhost/v2.0/'
}, # None parameter
{
'username': 'user1',
'password': 'pass',
'auth_url': 'http://localhost/v2.0/',
'tenant': None
} # None tenant
]
for creds in bad_creds:
try:
plugin = auth.KeystoneStrategy(creds)
plugin.authenticate()
self.fail("Failed to raise correct exception when supplying "
"bad credentials: %r" % creds)
except exception.MissingCredentialError:
continue # Expected
def test_invalid_auth_url_v1(self):
"""
Test that a 400 during authenticate raises exception.AuthBadRequest
"""
def fake_do_request(*args, **kwargs):
resp = webob.Response()
resp.status = http.BAD_REQUEST
return FakeResponse(resp), ""
self.stubs.Set(auth.KeystoneStrategy, '_do_request', fake_do_request)
bad_creds = {
'username': 'user1',
'auth_url': 'http://localhost/badauthurl/',
'password': 'pass',
'strategy': 'keystone',
'region': 'RegionOne'
}
plugin = auth.KeystoneStrategy(bad_creds)
self.assertRaises(exception.AuthBadRequest, plugin.authenticate)
def test_invalid_auth_url_v2(self):
"""
Test that a 400 during authenticate raises exception.AuthBadRequest
"""
def fake_do_request(*args, **kwargs):
resp = webob.Response()
resp.status = http.BAD_REQUEST
return FakeResponse(resp), ""
self.stubs.Set(auth.KeystoneStrategy, '_do_request', fake_do_request)
bad_creds = {
'username': 'user1',
'auth_url': 'http://localhost/badauthurl/v2.0/',
'password': 'pass',
'tenant': 'tenant1',
'strategy': 'keystone',
'region': 'RegionOne'
}
plugin = auth.KeystoneStrategy(bad_creds)
self.assertRaises(exception.AuthBadRequest, plugin.authenticate)
def test_v1_auth(self):
"""Test v1 auth code paths"""
def fake_do_request(cls, url, method, headers=None, body=None):
if url.find("2.0") != -1:
self.fail("Invalid v1.0 token path (%s)" % url)
headers = headers or {}
resp = webob.Response()
if (headers.get('X-Auth-User') != 'user1' or
headers.get('X-Auth-Key') != 'pass'):
resp.status = http.UNAUTHORIZED
else:
resp.status = http.OK
resp.headers.update({"x-image-management-url": "example.com"})
return FakeResponse(resp), ""
self.stubs.Set(auth.KeystoneStrategy, '_do_request', fake_do_request)
unauthorized_creds = [
{
'username': 'wronguser',
'auth_url': 'http://localhost/badauthurl/',
'strategy': 'keystone',
'region': 'RegionOne',
'password': 'pass'
}, # wrong username
{
'username': 'user1',
'auth_url': 'http://localhost/badauthurl/',
'strategy': 'keystone',
'region': 'RegionOne',
'password': 'badpass'
}, # bad password...
]
for creds in unauthorized_creds:
try:
plugin = auth.KeystoneStrategy(creds)
plugin.authenticate()
self.fail("Failed to raise NotAuthenticated when supplying "
"bad credentials: %r" % creds)
except exception.NotAuthenticated:
continue # Expected
no_strategy_creds = {
'username': 'user1',
'auth_url': 'http://localhost/redirect/',
'password': 'pass',
'region': 'RegionOne'
}
try:
plugin = auth.KeystoneStrategy(no_strategy_creds)
plugin.authenticate()
self.fail("Failed to raise MissingCredentialError when "
"supplying no strategy: %r" % no_strategy_creds)
except exception.MissingCredentialError:
pass # Expected
good_creds = [
{
'username': 'user1',
'auth_url': 'http://localhost/redirect/',
'password': 'pass',
'strategy': 'keystone',
'region': 'RegionOne'
}
]
for creds in good_creds:
plugin = auth.KeystoneStrategy(creds)
self.assertIsNone(plugin.authenticate())
self.assertEqual("example.com", plugin.management_url)
# Assert it does not update management_url via auth response
for creds in good_creds:
plugin = auth.KeystoneStrategy(creds, configure_via_auth=False)
self.assertIsNone(plugin.authenticate())
self.assertIsNone(plugin.management_url)
def test_v2_auth(self):
"""Test v2 auth code paths"""
mock_token = None
def fake_do_request(cls, url, method, headers=None, body=None):
if (not url.rstrip('/').endswith('v2.0/tokens') or
url.count("2.0") != 1):
self.fail("Invalid v2.0 token path (%s)" % url)
creds = jsonutils.loads(body)['auth']
username = creds['passwordCredentials']['username']
password = creds['passwordCredentials']['password']
tenant = creds['tenantName']
resp = webob.Response()
if (username != 'user1' or password != 'pass' or
tenant != 'tenant-ok'):
resp.status = http.UNAUTHORIZED
else:
resp.status = http.OK
body = mock_token.token
return FakeResponse(resp), jsonutils.dumps(body)
mock_token = V2Token()
mock_token.add_service('image', ['RegionOne'])
self.stubs.Set(auth.KeystoneStrategy, '_do_request', fake_do_request)
unauthorized_creds = [
{
'username': 'wronguser',
'auth_url': 'http://localhost/v2.0',
'password': 'pass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionOne'
}, # wrong username
{
'username': 'user1',
'auth_url': 'http://localhost/v2.0',
'password': 'badpass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionOne'
}, # bad password...
{
'username': 'user1',
'auth_url': 'http://localhost/v2.0',
'password': 'pass',
'tenant': 'carterhayes',
'strategy': 'keystone',
'region': 'RegionOne'
}, # bad tenant...
]
for creds in unauthorized_creds:
try:
plugin = auth.KeystoneStrategy(creds)
plugin.authenticate()
self.fail("Failed to raise NotAuthenticated when supplying "
"bad credentials: %r" % creds)
except exception.NotAuthenticated:
continue # Expected
no_region_creds = {
'username': 'user1',
'tenant': 'tenant-ok',
'auth_url': 'http://localhost/redirect/v2.0/',
'password': 'pass',
'strategy': 'keystone'
}
plugin = auth.KeystoneStrategy(no_region_creds)
self.assertIsNone(plugin.authenticate())
self.assertEqual('http://localhost:9292', plugin.management_url)
# Add another image service, with a different region
mock_token.add_service('image', ['RegionTwo'])
try:
plugin = auth.KeystoneStrategy(no_region_creds)
plugin.authenticate()
self.fail("Failed to raise RegionAmbiguity when no region present "
"and multiple regions exist: %r" % no_region_creds)
except exception.RegionAmbiguity:
pass # Expected
wrong_region_creds = {
'username': 'user1',
'tenant': 'tenant-ok',
'auth_url': 'http://localhost/redirect/v2.0/',
'password': 'pass',
'strategy': 'keystone',
'region': 'NonExistentRegion'
}
try:
plugin = auth.KeystoneStrategy(wrong_region_creds)
plugin.authenticate()
self.fail("Failed to raise NoServiceEndpoint when supplying "
"wrong region: %r" % wrong_region_creds)
except exception.NoServiceEndpoint:
pass # Expected
no_strategy_creds = {
'username': 'user1',
'tenant': 'tenant-ok',
'auth_url': 'http://localhost/redirect/v2.0/',
'password': 'pass',
'region': 'RegionOne'
}
try:
plugin = auth.KeystoneStrategy(no_strategy_creds)
plugin.authenticate()
self.fail("Failed to raise MissingCredentialError when "
"supplying no strategy: %r" % no_strategy_creds)
except exception.MissingCredentialError:
pass # Expected
bad_strategy_creds = {
'username': 'user1',
'tenant': 'tenant-ok',
'auth_url': 'http://localhost/redirect/v2.0/',
'password': 'pass',
'region': 'RegionOne',
'strategy': 'keypebble'
}
try:
plugin = auth.KeystoneStrategy(bad_strategy_creds)
plugin.authenticate()
self.fail("Failed to raise BadAuthStrategy when supplying "
"bad auth strategy: %r" % bad_strategy_creds)
except exception.BadAuthStrategy:
pass # Expected
mock_token = V2Token()
mock_token.add_service('image', ['RegionOne', 'RegionTwo'])
good_creds = [
{
'username': 'user1',
'auth_url': 'http://localhost/v2.0/',
'password': 'pass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionOne'
}, # auth_url with trailing '/'
{
'username': 'user1',
'auth_url': 'http://localhost/v2.0',
'password': 'pass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionOne'
}, # auth_url without trailing '/'
{
'username': 'user1',
'auth_url': 'http://localhost/v2.0',
'password': 'pass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionTwo'
} # Second region
]
for creds in good_creds:
plugin = auth.KeystoneStrategy(creds)
self.assertIsNone(plugin.authenticate())
self.assertEqual('http://localhost:9292', plugin.management_url)
ambiguous_region_creds = {
'username': 'user1',
'auth_url': 'http://localhost/v2.0/',
'password': 'pass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionOne'
}
mock_token = V2Token()
# Add two identical services
mock_token.add_service('image', ['RegionOne'])
mock_token.add_service('image', ['RegionOne'])
try:
plugin = auth.KeystoneStrategy(ambiguous_region_creds)
plugin.authenticate()
self.fail("Failed to raise RegionAmbiguity when "
"non-unique regions exist: %r" % ambiguous_region_creds)
except exception.RegionAmbiguity:
pass
mock_token = V2Token()
mock_token.add_service('bad-image', ['RegionOne'])
good_creds = {
'username': 'user1',
'auth_url': 'http://localhost/v2.0/',
'password': 'pass',
'tenant': 'tenant-ok',
'strategy': 'keystone',
'region': 'RegionOne'
}
try:
plugin = auth.KeystoneStrategy(good_creds)
plugin.authenticate()
self.fail("Failed to raise NoServiceEndpoint when bad service "
"type encountered")
except exception.NoServiceEndpoint:
pass
mock_token = V2Token()
mock_token.add_service_no_type()
try:
plugin = auth.KeystoneStrategy(good_creds)
plugin.authenticate()
self.fail("Failed to raise NoServiceEndpoint when bad service "
"type encountered")
except exception.NoServiceEndpoint:
pass
try:
plugin = auth.KeystoneStrategy(good_creds,
configure_via_auth=False)
plugin.authenticate()
except exception.NoServiceEndpoint:
self.fail("NoServiceEndpoint was raised when authenticate "
"should not check for endpoint.")
class TestEndpoints(utils.BaseTestCase):
def setUp(self):
super(TestEndpoints, self).setUp()
self.service_catalog = [
{
'endpoint_links': [],
'endpoints': [
{
'adminURL': 'http://localhost:8080/',
'region': 'RegionOne',
'internalURL': 'http://internalURL/',
'publicURL': 'http://publicURL/',
},
],
'type': 'object-store',
'name': 'Object Storage Service',
}
]
def test_get_endpoint_with_custom_server_type(self):
endpoint = auth.get_endpoint(self.service_catalog,
service_type='object-store')
self.assertEqual('http://publicURL/', endpoint)
def test_get_endpoint_with_custom_endpoint_type(self):
endpoint = auth.get_endpoint(self.service_catalog,
service_type='object-store',
endpoint_type='internalURL')
self.assertEqual('http://internalURL/', endpoint)
def test_get_endpoint_raises_with_invalid_service_type(self):
self.assertRaises(exception.NoServiceEndpoint,
auth.get_endpoint,
self.service_catalog,
service_type='foo')
def test_get_endpoint_raises_with_invalid_endpoint_type(self):
self.assertRaises(exception.NoServiceEndpoint,
auth.get_endpoint,
self.service_catalog,
service_type='object-store',
endpoint_type='foo')
def test_get_endpoint_raises_with_invalid_endpoint_region(self):
self.assertRaises(exception.NoServiceEndpoint,
auth.get_endpoint,
self.service_catalog,
service_type='object-store',
endpoint_region='foo',
endpoint_type='internalURL')
class TestImageMutability(utils.BaseTestCase):
def setUp(self):
super(TestImageMutability, self).setUp()
self.image_factory = glance.domain.ImageFactory()
def _is_mutable(self, tenant, owner, is_admin=False):
context = glance.context.RequestContext(tenant=tenant,
is_admin=is_admin)
image = self.image_factory.new_image(owner=owner)
return authorization.is_image_mutable(context, image)
def test_admin_everything_mutable(self):
self.assertTrue(self._is_mutable(None, None, is_admin=True))
self.assertTrue(self._is_mutable(None, TENANT1, is_admin=True))
self.assertTrue(self._is_mutable(TENANT1, None, is_admin=True))
self.assertTrue(self._is_mutable(TENANT1, TENANT1, is_admin=True))
self.assertTrue(self._is_mutable(TENANT1, TENANT2, is_admin=True))
def test_no_tenant_nothing_mutable(self):
self.assertFalse(self._is_mutable(None, None))
self.assertFalse(self._is_mutable(None, TENANT1))
def test_regular_user(self):
self.assertFalse(self._is_mutable(TENANT1, None))
self.assertFalse(self._is_mutable(TENANT1, TENANT2))
self.assertTrue(self._is_mutable(TENANT1, TENANT1))
class TestImmutableImage(utils.BaseTestCase):
def setUp(self):
super(TestImmutableImage, self).setUp()
image_factory = glance.domain.ImageFactory()
self.context = glance.context.RequestContext(tenant=TENANT1)
image = image_factory.new_image(
image_id=UUID1,
name='Marvin',
owner=TENANT1,
disk_format='raw',
container_format='bare',
extra_properties={'foo': 'bar'},
tags=['ping', 'pong'],
)
self.image = authorization.ImmutableImageProxy(image, self.context)
def _test_change(self, attr, value):
self.assertRaises(exception.Forbidden,
setattr, self.image, attr, value)
self.assertRaises(exception.Forbidden,
delattr, self.image, attr)
def test_change_id(self):
self._test_change('image_id', UUID2)
def test_change_name(self):
self._test_change('name', 'Freddie')
def test_change_owner(self):
self._test_change('owner', TENANT2)
def test_change_min_disk(self):
self._test_change('min_disk', 100)
def test_change_min_ram(self):
self._test_change('min_ram', 1024)
def test_change_disk_format(self):
self._test_change('disk_format', 'vhd')
def test_change_container_format(self):
self._test_change('container_format', 'ova')
def test_change_visibility(self):
self._test_change('visibility', 'public')
def test_change_status(self):
self._test_change('status', 'active')
def test_change_created_at(self):
self._test_change('created_at', timeutils.utcnow())
def test_change_updated_at(self):
self._test_change('updated_at', timeutils.utcnow())
def test_change_locations(self):
self._test_change('locations', ['http://a/b/c'])
self.assertRaises(exception.Forbidden,
self.image.locations.append, 'http://a/b/c')
self.assertRaises(exception.Forbidden,
self.image.locations.extend, ['http://a/b/c'])
self.assertRaises(exception.Forbidden,
self.image.locations.insert, 'foo')
self.assertRaises(exception.Forbidden,
self.image.locations.pop)
self.assertRaises(exception.Forbidden,
self.image.locations.remove, 'foo')
self.assertRaises(exception.Forbidden,
self.image.locations.reverse)
self.assertRaises(exception.Forbidden,
self.image.locations.sort)
self.assertRaises(exception.Forbidden,
self.image.locations.__delitem__, 0)
self.assertRaises(exception.Forbidden,
self.image.locations.__delslice__, 0, 2)
self.assertRaises(exception.Forbidden,
self.image.locations.__setitem__, 0, 'foo')
self.assertRaises(exception.Forbidden,
self.image.locations.__setslice__,
0, 2, ['foo', 'bar'])
self.assertRaises(exception.Forbidden,
self.image.locations.__iadd__, 'foo')
self.assertRaises(exception.Forbidden,
self.image.locations.__imul__, 2)
def test_change_size(self):
self._test_change('size', 32)
def test_change_tags(self):
self.assertRaises(exception.Forbidden,
delattr, self.image, 'tags')
self.assertRaises(exception.Forbidden,
setattr, self.image, 'tags', ['king', 'kong'])
self.assertRaises(exception.Forbidden, self.image.tags.pop)
self.assertRaises(exception.Forbidden, self.image.tags.clear)
self.assertRaises(exception.Forbidden, self.image.tags.add, 'king')
self.assertRaises(exception.Forbidden, self.image.tags.remove, 'ping')
self.assertRaises(exception.Forbidden,
self.image.tags.update, set(['king', 'kong']))
self.assertRaises(exception.Forbidden,
self.image.tags.intersection_update, set([]))
self.assertRaises(exception.Forbidden,
self.image.tags.difference_update, set([]))
self.assertRaises(exception.Forbidden,
self.image.tags.symmetric_difference_update,
set([]))
def test_change_properties(self):
self.assertRaises(exception.Forbidden,
delattr, self.image, 'extra_properties')
self.assertRaises(exception.Forbidden,
setattr, self.image, 'extra_properties', {})
self.assertRaises(exception.Forbidden,
self.image.extra_properties.__delitem__, 'foo')
self.assertRaises(exception.Forbidden,
self.image.extra_properties.__setitem__, 'foo', 'b')
self.assertRaises(exception.Forbidden,
self.image.extra_properties.__setitem__, 'z', 'j')
self.assertRaises(exception.Forbidden,
self.image.extra_properties.pop)
self.assertRaises(exception.Forbidden,
self.image.extra_properties.popitem)
self.assertRaises(exception.Forbidden,
self.image.extra_properties.setdefault, 'p', 'j')
self.assertRaises(exception.Forbidden,
self.image.extra_properties.update, {})
def test_delete(self):
self.assertRaises(exception.Forbidden, self.image.delete)
def test_set_data(self):
self.assertRaises(exception.Forbidden,
self.image.set_data, 'blah', 4)
def test_deactivate_image(self):
self.assertRaises(exception.Forbidden, self.image.deactivate)
def test_reactivate_image(self):
self.assertRaises(exception.Forbidden, self.image.reactivate)
def test_get_data(self):
class FakeImage(object):
def get_data(self):
return 'tiddlywinks'
image = glance.api.authorization.ImmutableImageProxy(
FakeImage(), self.context)
self.assertEqual('tiddlywinks', image.get_data())
class TestImageFactoryProxy(utils.BaseTestCase):
def setUp(self):
super(TestImageFactoryProxy, self).setUp()
factory = glance.domain.ImageFactory()
self.context = glance.context.RequestContext(tenant=TENANT1)
self.image_factory = authorization.ImageFactoryProxy(factory,
self.context)
def test_default_owner_is_set(self):
image = self.image_factory.new_image()
self.assertEqual(TENANT1, image.owner)
def test_wrong_owner_cannot_be_set(self):
self.assertRaises(exception.Forbidden,
self.image_factory.new_image, owner=TENANT2)
def test_cannot_set_owner_to_none(self):
self.assertRaises(exception.Forbidden,
self.image_factory.new_image, owner=None)
def test_admin_can_set_any_owner(self):
self.context.is_admin = True
image = self.image_factory.new_image(owner=TENANT2)
self.assertEqual(TENANT2, image.owner)
def test_admin_can_set_owner_to_none(self):
self.context.is_admin = True
image = self.image_factory.new_image(owner=None)
self.assertIsNone(image.owner)
def test_admin_still_gets_default_tenant(self):
self.context.is_admin = True
image = self.image_factory.new_image()
self.assertEqual(TENANT1, image.owner)
class TestImageRepoProxy(utils.BaseTestCase):
class ImageRepoStub(object):
def __init__(self, fixtures):
self.fixtures = fixtures
def get(self, image_id):
for f in self.fixtures:
if f.image_id == image_id:
return f
else:
raise ValueError(image_id)
def list(self, *args, **kwargs):
return self.fixtures
def setUp(self):
super(TestImageRepoProxy, self).setUp()
image_factory = glance.domain.ImageFactory()
self.fixtures = [
image_factory.new_image(owner=TENANT1),
image_factory.new_image(owner=TENANT2, visibility='public'),
image_factory.new_image(owner=TENANT2),
]
self.context = glance.context.RequestContext(tenant=TENANT1)
image_repo = self.ImageRepoStub(self.fixtures)
self.image_repo = authorization.ImageRepoProxy(image_repo,
self.context)
def test_get_mutable_image(self):
image = self.image_repo.get(self.fixtures[0].image_id)
self.assertEqual(image.image_id, self.fixtures[0].image_id)
def test_get_immutable_image(self):
image = self.image_repo.get(self.fixtures[1].image_id)
self.assertRaises(exception.Forbidden,
setattr, image, 'name', 'Vince')
def test_list(self):
images = self.image_repo.list()
self.assertEqual(images[0].image_id, self.fixtures[0].image_id)
self.assertRaises(exception.Forbidden,
setattr, images[1], 'name', 'Wally')
self.assertRaises(exception.Forbidden,
setattr, images[2], 'name', 'Calvin')
class TestImmutableTask(utils.BaseTestCase):
def setUp(self):
super(TestImmutableTask, self).setUp()
task_factory = glance.domain.TaskFactory()
self.context = glance.context.RequestContext(tenant=TENANT2)
task_type = 'import'
owner = TENANT2
task = task_factory.new_task(task_type, owner)
self.task = authorization.ImmutableTaskProxy(task)
def _test_change(self, attr, value):
self.assertRaises(
exception.Forbidden,
setattr,
self.task,
attr,
value
)
self.assertRaises(
exception.Forbidden,
delattr,
self.task,
attr
)
def test_change_id(self):
self._test_change('task_id', UUID2)
def test_change_type(self):
self._test_change('type', 'fake')
def test_change_status(self):
self._test_change('status', 'success')
def test_change_owner(self):
self._test_change('owner', 'fake')
def test_change_expires_at(self):
self._test_change('expires_at', 'fake')
def test_change_created_at(self):
self._test_change('created_at', 'fake')
def test_change_updated_at(self):
self._test_change('updated_at', 'fake')
def test_begin_processing(self):
self.assertRaises(
exception.Forbidden,
self.task.begin_processing
)
def test_succeed(self):
self.assertRaises(
exception.Forbidden,
self.task.succeed,
'result'
)
def test_fail(self):
self.assertRaises(
exception.Forbidden,
self.task.fail,
'message'
)
class TestImmutableTaskStub(utils.BaseTestCase):
def setUp(self):
super(TestImmutableTaskStub, self).setUp()
task_factory = glance.domain.TaskFactory()
self.context = glance.context.RequestContext(tenant=TENANT2)
task_type = 'import'
owner = TENANT2
task = task_factory.new_task(task_type, owner)
self.task = authorization.ImmutableTaskStubProxy(task)
def _test_change(self, attr, value):
self.assertRaises(
exception.Forbidden,
setattr,
self.task,
attr,
value
)
self.assertRaises(
exception.Forbidden,
delattr,
self.task,
attr
)
def test_change_id(self):
self._test_change('task_id', UUID2)
def test_change_type(self):
self._test_change('type', 'fake')
def test_change_status(self):
self._test_change('status', 'success')
def test_change_owner(self):
self._test_change('owner', 'fake')
def test_change_expires_at(self):
self._test_change('expires_at', 'fake')
def test_change_created_at(self):
self._test_change('created_at', 'fake')
def test_change_updated_at(self):
self._test_change('updated_at', 'fake')
class TestTaskFactoryProxy(utils.BaseTestCase):
def setUp(self):
super(TestTaskFactoryProxy, self).setUp()
factory = glance.domain.TaskFactory()
self.context = glance.context.RequestContext(tenant=TENANT1)
self.context_owner_is_none = glance.context.RequestContext()
self.task_factory = authorization.TaskFactoryProxy(
factory,
self.context
)
self.task_type = 'import'
self.task_input = '{"loc": "fake"}'
self.owner = 'foo'
self.request1 = unittest_utils.get_fake_request(tenant=TENANT1)
self.request2 = unittest_utils.get_fake_request(tenant=TENANT2)
def test_task_create_default_owner(self):
owner = self.request1.context.owner
task = self.task_factory.new_task(task_type=self.task_type,
owner=owner)
self.assertEqual(TENANT1, task.owner)
def test_task_create_wrong_owner(self):
self.assertRaises(exception.Forbidden,
self.task_factory.new_task,
task_type=self.task_type,
task_input=self.task_input,
owner=self.owner)
def test_task_create_owner_as_None(self):
self.assertRaises(exception.Forbidden,
self.task_factory.new_task,
task_type=self.task_type,
task_input=self.task_input,
owner=None)
def test_task_create_admin_context_owner_as_None(self):
self.context.is_admin = True
self.assertRaises(exception.Forbidden,
self.task_factory.new_task,
task_type=self.task_type,
task_input=self.task_input,
owner=None)
class TestTaskRepoProxy(utils.BaseTestCase):
class TaskRepoStub(object):
def __init__(self, fixtures):
self.fixtures = fixtures
def get(self, task_id):
for f in self.fixtures:
if f.task_id == task_id:
return f
else:
raise ValueError(task_id)
class TaskStubRepoStub(object):
def __init__(self, fixtures):
self.fixtures = fixtures
def list(self, *args, **kwargs):
return self.fixtures
def setUp(self):
super(TestTaskRepoProxy, self).setUp()
task_factory = glance.domain.TaskFactory()
task_type = 'import'
owner = None
self.fixtures = [
task_factory.new_task(task_type, owner),
task_factory.new_task(task_type, owner),
task_factory.new_task(task_type, owner),
]
self.context = glance.context.RequestContext(tenant=TENANT1)
task_repo = self.TaskRepoStub(self.fixtures)
task_stub_repo = self.TaskStubRepoStub(self.fixtures)
self.task_repo = authorization.TaskRepoProxy(
task_repo,
self.context
)
self.task_stub_repo = authorization.TaskStubRepoProxy(
task_stub_repo,
self.context
)
def test_get_mutable_task(self):
task = self.task_repo.get(self.fixtures[0].task_id)
self.assertEqual(task.task_id, self.fixtures[0].task_id)
def test_get_immutable_task(self):
task_id = self.fixtures[1].task_id
task = self.task_repo.get(task_id)
self.assertRaises(exception.Forbidden,
setattr, task, 'input', 'foo')
def test_list(self):
tasks = self.task_stub_repo.list()
self.assertEqual(tasks[0].task_id, self.fixtures[0].task_id)
self.assertRaises(exception.Forbidden,
setattr,
tasks[1],
'owner',
'foo')
self.assertRaises(exception.Forbidden,
setattr,
tasks[2],
'owner',
'foo')
glance-16.0.1/glance/tests/unit/test_scrubber.py 0000666 0001750 0001750 00000015175 13267672254 021675 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import glance_store
import mock
from mock import patch
from mox3 import mox
from oslo_config import cfg
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.common import exception
from glance.db.sqlalchemy import api as db_api
from glance import scrubber
from glance.tests import utils as test_utils
CONF = cfg.CONF
class TestScrubber(test_utils.BaseTestCase):
def setUp(self):
super(TestScrubber, self).setUp()
glance_store.register_opts(CONF)
self.config(group='glance_store', default_store='file',
filesystem_store_datadir=self.test_dir)
glance_store.create_stores()
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
# These globals impact state outside of this test class, kill them.
scrubber._file_queue = None
scrubber._db_queue = None
super(TestScrubber, self).tearDown()
def _scrubber_cleanup_with_store_delete_exception(self, ex):
uri = 'file://some/path/%s' % uuid.uuid4()
id = 'helloworldid'
scrub = scrubber.Scrubber(glance_store)
self.mox.StubOutWithMock(glance_store, "delete_from_backend")
glance_store.delete_from_backend(
uri,
mox.IgnoreArg()).AndRaise(ex)
self.mox.ReplayAll()
scrub._scrub_image(id, [(id, '-', uri)])
self.mox.VerifyAll()
@mock.patch.object(db_api, "image_get")
def test_store_delete_successful(self, mock_image_get):
uri = 'file://some/path/%s' % uuid.uuid4()
id = 'helloworldid'
scrub = scrubber.Scrubber(glance_store)
self.mox.StubOutWithMock(glance_store, "delete_from_backend")
glance_store.delete_from_backend(uri, mox.IgnoreArg()).AndReturn('')
self.mox.ReplayAll()
scrub._scrub_image(id, [(id, '-', uri)])
self.mox.VerifyAll()
@mock.patch.object(db_api, "image_get")
def test_store_delete_store_exceptions(self, mock_image_get):
# While scrubbing image data, all store exceptions, other than
# NotFound, cause image scrubbing to fail. Essentially, no attempt
# would be made to update the status of image.
uri = 'file://some/path/%s' % uuid.uuid4()
id = 'helloworldid'
ex = glance_store.GlanceStoreException()
scrub = scrubber.Scrubber(glance_store)
self.mox.StubOutWithMock(glance_store, "delete_from_backend")
glance_store.delete_from_backend(
uri,
mox.IgnoreArg()).AndRaise(ex)
self.mox.ReplayAll()
scrub._scrub_image(id, [(id, '-', uri)])
self.mox.VerifyAll()
@mock.patch.object(db_api, "image_get")
def test_store_delete_notfound_exception(self, mock_image_get):
# While scrubbing image data, NotFound exception is ignored and image
# scrubbing succeeds
uri = 'file://some/path/%s' % uuid.uuid4()
id = 'helloworldid'
ex = glance_store.NotFound(message='random')
scrub = scrubber.Scrubber(glance_store)
self.mox.StubOutWithMock(glance_store, "delete_from_backend")
glance_store.delete_from_backend(uri, mox.IgnoreArg()).AndRaise(ex)
self.mox.ReplayAll()
scrub._scrub_image(id, [(id, '-', uri)])
self.mox.VerifyAll()
def test_scrubber_exits(self):
# Checks for Scrubber exits when it is not able to fetch jobs from
# the queue
scrub_jobs = scrubber.ScrubDBQueue.get_all_locations
scrub_jobs = mock.MagicMock()
scrub_jobs.side_effect = exception.NotFound
scrub = scrubber.Scrubber(glance_store)
self.assertRaises(exception.FailedToGetScrubberJobs,
scrub._get_delete_jobs)
class TestScrubDBQueue(test_utils.BaseTestCase):
def setUp(self):
super(TestScrubDBQueue, self).setUp()
def _create_image_list(self, count):
images = []
for x in range(count):
images.append({'id': x})
return images
def test_get_all_images(self):
scrub_queue = scrubber.ScrubDBQueue()
images = self._create_image_list(15)
image_pager = ImagePager(images)
def make_get_images_detailed(pager):
def mock_get_images_detailed(ctx, filters, marker=None,
limit=None):
return pager()
return mock_get_images_detailed
with patch.object(db_api, 'image_get_all') as (
_mock_get_images_detailed):
_mock_get_images_detailed.side_effect = (
make_get_images_detailed(image_pager))
actual = list(scrub_queue._get_all_images())
self.assertEqual(images, actual)
def test_get_all_images_paged(self):
scrub_queue = scrubber.ScrubDBQueue()
images = self._create_image_list(15)
image_pager = ImagePager(images, page_size=4)
def make_get_images_detailed(pager):
def mock_get_images_detailed(ctx, filters, marker=None,
limit=None):
return pager()
return mock_get_images_detailed
with patch.object(db_api, 'image_get_all') as (
_mock_get_images_detailed):
_mock_get_images_detailed.side_effect = (
make_get_images_detailed(image_pager))
actual = list(scrub_queue._get_all_images())
self.assertEqual(images, actual)
class ImagePager(object):
def __init__(self, images, page_size=0):
image_count = len(images)
if page_size == 0 or page_size > image_count:
page_size = image_count
self.image_batches = []
start = 0
l = len(images)
while start < l:
self.image_batches.append(images[start: start + page_size])
start += page_size
if (l - start) < page_size:
page_size = l - start
def __call__(self):
if len(self.image_batches) == 0:
return []
else:
return self.image_batches.pop(0)
glance-16.0.1/glance/tests/unit/base.py 0000666 0001750 0001750 00000005247 13267672245 017740 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import glance_store as store
from glance_store import location
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_db import options
from oslo_serialization import jsonutils
from glance.tests import stubs
from glance.tests import utils as test_utils
CONF = cfg.CONF
class StoreClearingUnitTest(test_utils.BaseTestCase):
def setUp(self):
super(StoreClearingUnitTest, self).setUp()
# Ensure stores + locations cleared
location.SCHEME_TO_CLS_MAP = {}
self._create_stores()
self.addCleanup(setattr, location, 'SCHEME_TO_CLS_MAP', dict())
def _create_stores(self, passing_config=True):
"""Create known stores. Mock out sheepdog's subprocess dependency
on collie.
:param passing_config: making store driver passes basic configurations.
:returns: the number of how many store drivers been loaded.
"""
store.register_opts(CONF)
self.config(default_store='filesystem',
filesystem_store_datadir=self.test_dir,
group="glance_store")
store.create_stores(CONF)
class IsolatedUnitTest(StoreClearingUnitTest):
"""
Unit test case that establishes a mock environment within
a testing directory (in isolation)
"""
registry = None
def setUp(self):
super(IsolatedUnitTest, self).setUp()
options.set_defaults(CONF, connection='sqlite://')
lockutils.set_defaults(os.path.join(self.test_dir))
self.config(debug=False)
self.config(default_store='filesystem',
filesystem_store_datadir=self.test_dir,
group="glance_store")
store.create_stores()
stubs.stub_out_registry_and_store_server(self.stubs,
self.test_dir,
registry=self.registry)
def set_policy_rules(self, rules):
fap = open(CONF.oslo_policy.policy_file, 'w')
fap.write(jsonutils.dumps(rules))
fap.close()
glance-16.0.1/glance/tests/unit/test_notifier.py 0000666 0001750 0001750 00000072175 13267672245 021710 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import glance_store
import mock
from oslo_config import cfg
import oslo_messaging
import webob
import glance.async
from glance.common import exception
from glance.common import timeutils
import glance.context
from glance import notifier
import glance.tests.unit.utils as unit_test_utils
from glance.tests import utils
DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
class ImageStub(glance.domain.Image):
def get_data(self, offset=0, chunk_size=None):
return ['01234', '56789']
def set_data(self, data, size=None):
for chunk in data:
pass
class ImageRepoStub(object):
def remove(self, *args, **kwargs):
return 'image_from_get'
def save(self, *args, **kwargs):
return 'image_from_save'
def add(self, *args, **kwargs):
return 'image_from_add'
def get(self, *args, **kwargs):
return 'image_from_get'
def list(self, *args, **kwargs):
return ['images_from_list']
class ImageMemberRepoStub(object):
def remove(self, *args, **kwargs):
return 'image_member_from_remove'
def save(self, *args, **kwargs):
return 'image_member_from_save'
def add(self, *args, **kwargs):
return 'image_member_from_add'
def get(self, *args, **kwargs):
return 'image_member_from_get'
def list(self, *args, **kwargs):
return ['image_members_from_list']
class TaskStub(glance.domain.TaskStub):
def run(self, executor):
pass
class Task(glance.domain.Task):
def succeed(self, result):
pass
def fail(self, message):
pass
class TaskRepoStub(object):
def remove(self, *args, **kwargs):
return 'task_from_remove'
def save(self, *args, **kwargs):
return 'task_from_save'
def add(self, *args, **kwargs):
return 'task_from_add'
def get_task(self, *args, **kwargs):
return 'task_from_get'
def list(self, *args, **kwargs):
return ['tasks_from_list']
class TestNotifier(utils.BaseTestCase):
@mock.patch.object(oslo_messaging, 'Notifier')
@mock.patch.object(oslo_messaging, 'get_notification_transport')
def _test_load_strategy(self,
mock_get_transport, mock_notifier,
url, driver):
nfier = notifier.Notifier()
mock_get_transport.assert_called_with(cfg.CONF)
self.assertIsNotNone(nfier._transport)
mock_notifier.assert_called_with(nfier._transport,
publisher_id='image.localhost')
self.assertIsNotNone(nfier._notifier)
def test_notifier_load(self):
self._test_load_strategy(url=None, driver=None)
@mock.patch.object(oslo_messaging, 'set_transport_defaults')
def test_set_defaults(self, mock_set_trans_defaults):
notifier.set_defaults(control_exchange='foo')
mock_set_trans_defaults.assert_called_with('foo')
notifier.set_defaults()
mock_set_trans_defaults.assert_called_with('glance')
class TestImageNotifications(utils.BaseTestCase):
"""Test Image Notifications work"""
def setUp(self):
super(TestImageNotifications, self).setUp()
self.image = ImageStub(
image_id=UUID1, name='image-1', status='active', size=1024,
created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
visibility='public', container_format='ami', virtual_size=2048,
tags=['one', 'two'], disk_format='ami', min_ram=128,
min_disk=10, checksum='ca425b88f047ce8ec45ee90e813ada91',
locations=['http://127.0.0.1'])
self.context = glance.context.RequestContext(tenant=TENANT2,
user=USER1)
self.image_repo_stub = ImageRepoStub()
self.notifier = unit_test_utils.FakeNotifier()
self.image_repo_proxy = glance.notifier.ImageRepoProxy(
self.image_repo_stub, self.context, self.notifier)
self.image_proxy = glance.notifier.ImageProxy(
self.image, self.context, self.notifier)
def test_image_save_notification(self):
self.image_repo_proxy.save(self.image_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.update', output_log['event_type'])
self.assertEqual(self.image.image_id, output_log['payload']['id'])
if 'location' in output_log['payload']:
self.fail('Notification contained location field.')
def test_image_save_notification_disabled(self):
self.config(disabled_notifications=["image.update"])
self.image_repo_proxy.save(self.image_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_add_notification(self):
self.image_repo_proxy.add(self.image_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.create', output_log['event_type'])
self.assertEqual(self.image.image_id, output_log['payload']['id'])
if 'location' in output_log['payload']:
self.fail('Notification contained location field.')
def test_image_add_notification_disabled(self):
self.config(disabled_notifications=["image.create"])
self.image_repo_proxy.add(self.image_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_delete_notification(self):
self.image_repo_proxy.remove(self.image_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.delete', output_log['event_type'])
self.assertEqual(self.image.image_id, output_log['payload']['id'])
self.assertTrue(output_log['payload']['deleted'])
if 'location' in output_log['payload']:
self.fail('Notification contained location field.')
def test_image_delete_notification_disabled(self):
self.config(disabled_notifications=['image.delete'])
self.image_repo_proxy.remove(self.image_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_get(self):
image = self.image_repo_proxy.get(UUID1)
self.assertIsInstance(image, glance.notifier.ImageProxy)
self.assertEqual('image_from_get', image.repo)
def test_image_list(self):
images = self.image_repo_proxy.list()
self.assertIsInstance(images[0], glance.notifier.ImageProxy)
self.assertEqual('images_from_list', images[0].repo)
def test_image_get_data_should_call_next_image_get_data(self):
with mock.patch.object(self.image, 'get_data') as get_data_mock:
self.image_proxy.get_data()
self.assertTrue(get_data_mock.called)
def test_image_get_data_notification(self):
self.image_proxy.size = 10
data = ''.join(self.image_proxy.get_data())
self.assertEqual('0123456789', data)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.send', output_log['event_type'])
self.assertEqual(self.image.image_id,
output_log['payload']['image_id'])
self.assertEqual(TENANT2, output_log['payload']['receiver_tenant_id'])
self.assertEqual(USER1, output_log['payload']['receiver_user_id'])
self.assertEqual(10, output_log['payload']['bytes_sent'])
self.assertEqual(TENANT1, output_log['payload']['owner_id'])
def test_image_get_data_notification_disabled(self):
self.config(disabled_notifications=['image.send'])
self.image_proxy.size = 10
data = ''.join(self.image_proxy.get_data())
self.assertEqual('0123456789', data)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_get_data_size_mismatch(self):
self.image_proxy.size = 11
list(self.image_proxy.get_data())
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.send', output_log['event_type'])
self.assertEqual(self.image.image_id,
output_log['payload']['image_id'])
def test_image_set_data_prepare_notification(self):
insurance = {'called': False}
def data_iterator():
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.prepare', output_log['event_type'])
self.assertEqual(self.image.image_id, output_log['payload']['id'])
yield 'abcd'
yield 'efgh'
insurance['called'] = True
self.image_proxy.set_data(data_iterator(), 8)
self.assertTrue(insurance['called'])
def test_image_set_data_prepare_notification_disabled(self):
insurance = {'called': False}
def data_iterator():
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
yield 'abcd'
yield 'efgh'
insurance['called'] = True
self.config(disabled_notifications=['image.prepare'])
self.image_proxy.set_data(data_iterator(), 8)
self.assertTrue(insurance['called'])
def test_image_set_data_upload_and_activate_notification(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
yield 'fghij'
self.image_proxy.set_data(data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(2, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertEqual(self.image.image_id, output_log['payload']['id'])
output_log = output_logs[1]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.activate', output_log['event_type'])
self.assertEqual(self.image.image_id, output_log['payload']['id'])
def test_image_set_data_upload_and_activate_notification_disabled(self):
insurance = {'called': False}
def data_iterator():
self.notifier.log = []
yield 'abcde'
yield 'fghij'
insurance['called'] = True
self.config(disabled_notifications=['image.activate', 'image.upload'])
self.image_proxy.set_data(data_iterator(), 10)
self.assertTrue(insurance['called'])
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_set_data_storage_full(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise glance_store.StorageFull(message='Modern Major General')
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('Modern Major General', output_log['payload'])
def test_image_set_data_value_error(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise ValueError('value wrong')
self.assertRaises(webob.exc.HTTPBadRequest,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('value wrong', output_log['payload'])
def test_image_set_data_duplicate(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise exception.Duplicate('Cant have duplicates')
self.assertRaises(webob.exc.HTTPConflict,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('Cant have duplicates', output_log['payload'])
def test_image_set_data_storage_write_denied(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise glance_store.StorageWriteDenied(message='The Very Model')
self.assertRaises(webob.exc.HTTPServiceUnavailable,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('The Very Model', output_log['payload'])
def test_image_set_data_forbidden(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise exception.Forbidden('Not allowed')
self.assertRaises(webob.exc.HTTPForbidden,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('Not allowed', output_log['payload'])
def test_image_set_data_not_found(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise exception.NotFound('Not found')
self.assertRaises(webob.exc.HTTPNotFound,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('Not found', output_log['payload'])
def test_image_set_data_HTTP_error(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise webob.exc.HTTPError('Http issue')
self.assertRaises(webob.exc.HTTPError,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('Http issue', output_log['payload'])
def test_image_set_data_error(self):
def data_iterator():
self.notifier.log = []
yield 'abcde'
raise exception.GlanceException('Failed')
self.assertRaises(exception.GlanceException,
self.image_proxy.set_data, data_iterator(), 10)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('ERROR', output_log['notification_type'])
self.assertEqual('image.upload', output_log['event_type'])
self.assertIn('Failed', output_log['payload'])
class TestImageMemberNotifications(utils.BaseTestCase):
"""Test Image Member Notifications work"""
def setUp(self):
super(TestImageMemberNotifications, self).setUp()
self.context = glance.context.RequestContext(tenant=TENANT2,
user=USER1)
self.notifier = unit_test_utils.FakeNotifier()
self.image = ImageStub(
image_id=UUID1, name='image-1', status='active', size=1024,
created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
visibility='public', container_format='ami',
tags=['one', 'two'], disk_format='ami', min_ram=128,
min_disk=10, checksum='ca425b88f047ce8ec45ee90e813ada91',
locations=['http://127.0.0.1'])
self.image_member = glance.domain.ImageMembership(
id=1, image_id=UUID1, member_id=TENANT1, created_at=DATETIME,
updated_at=DATETIME, status='accepted')
self.image_member_repo_stub = ImageMemberRepoStub()
self.image_member_repo_proxy = glance.notifier.ImageMemberRepoProxy(
self.image_member_repo_stub, self.image,
self.context, self.notifier)
self.image_member_proxy = glance.notifier.ImageMemberProxy(
self.image_member, self.context, self.notifier)
def _assert_image_member_with_notifier(self, output_log, deleted=False):
self.assertEqual(self.image_member.member_id,
output_log['payload']['member_id'])
self.assertEqual(self.image_member.image_id,
output_log['payload']['image_id'])
self.assertEqual(self.image_member.status,
output_log['payload']['status'])
self.assertEqual(timeutils.isotime(self.image_member.created_at),
output_log['payload']['created_at'])
self.assertEqual(timeutils.isotime(self.image_member.updated_at),
output_log['payload']['updated_at'])
if deleted:
self.assertTrue(output_log['payload']['deleted'])
self.assertIsNotNone(output_log['payload']['deleted_at'])
else:
self.assertFalse(output_log['payload']['deleted'])
self.assertIsNone(output_log['payload']['deleted_at'])
def test_image_member_add_notification(self):
self.image_member_repo_proxy.add(self.image_member_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.member.create', output_log['event_type'])
self._assert_image_member_with_notifier(output_log)
def test_image_member_add_notification_disabled(self):
self.config(disabled_notifications=['image.member.create'])
self.image_member_repo_proxy.add(self.image_member_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_member_save_notification(self):
self.image_member_repo_proxy.save(self.image_member_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.member.update', output_log['event_type'])
self._assert_image_member_with_notifier(output_log)
def test_image_member_save_notification_disabled(self):
self.config(disabled_notifications=['image.member.update'])
self.image_member_repo_proxy.save(self.image_member_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_member_delete_notification(self):
self.image_member_repo_proxy.remove(self.image_member_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('image.member.delete', output_log['event_type'])
self._assert_image_member_with_notifier(output_log, deleted=True)
def test_image_member_delete_notification_disabled(self):
self.config(disabled_notifications=['image.member.delete'])
self.image_member_repo_proxy.remove(self.image_member_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_image_member_get(self):
image_member = self.image_member_repo_proxy.get(TENANT1)
self.assertIsInstance(image_member, glance.notifier.ImageMemberProxy)
self.assertEqual('image_member_from_get', image_member.repo)
def test_image_member_list(self):
image_members = self.image_member_repo_proxy.list()
self.assertIsInstance(image_members[0],
glance.notifier.ImageMemberProxy)
self.assertEqual('image_members_from_list', image_members[0].repo)
class TestTaskNotifications(utils.BaseTestCase):
"""Test Task Notifications work"""
def setUp(self):
super(TestTaskNotifications, self).setUp()
task_input = {"loc": "fake"}
self.task_stub = TaskStub(
task_id='aaa',
task_type='import',
status='pending',
owner=TENANT2,
expires_at=None,
created_at=DATETIME,
updated_at=DATETIME,
)
self.task = Task(
task_id='aaa',
task_type='import',
status='pending',
owner=TENANT2,
expires_at=None,
created_at=DATETIME,
updated_at=DATETIME,
task_input=task_input,
result='res',
message='blah'
)
self.context = glance.context.RequestContext(
tenant=TENANT2,
user=USER1
)
self.task_repo_stub = TaskRepoStub()
self.notifier = unit_test_utils.FakeNotifier()
self.task_repo_proxy = glance.notifier.TaskRepoProxy(
self.task_repo_stub,
self.context,
self.notifier
)
self.task_proxy = glance.notifier.TaskProxy(
self.task,
self.context,
self.notifier
)
self.task_stub_proxy = glance.notifier.TaskStubProxy(
self.task_stub,
self.context,
self.notifier
)
self.patcher = mock.patch.object(timeutils, 'utcnow')
mock_utcnow = self.patcher.start()
mock_utcnow.return_value = datetime.datetime.utcnow()
def tearDown(self):
super(TestTaskNotifications, self).tearDown()
self.patcher.stop()
def test_task_create_notification(self):
self.task_repo_proxy.add(self.task_stub_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.create', output_log['event_type'])
self.assertEqual(self.task.task_id, output_log['payload']['id'])
self.assertEqual(
timeutils.isotime(self.task.updated_at),
output_log['payload']['updated_at']
)
self.assertEqual(
timeutils.isotime(self.task.created_at),
output_log['payload']['created_at']
)
if 'location' in output_log['payload']:
self.fail('Notification contained location field.')
def test_task_create_notification_disabled(self):
self.config(disabled_notifications=['task.create'])
self.task_repo_proxy.add(self.task_stub_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_task_delete_notification(self):
now = timeutils.isotime()
self.task_repo_proxy.remove(self.task_stub_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.delete', output_log['event_type'])
self.assertEqual(self.task.task_id, output_log['payload']['id'])
self.assertEqual(
timeutils.isotime(self.task.updated_at),
output_log['payload']['updated_at']
)
self.assertEqual(
timeutils.isotime(self.task.created_at),
output_log['payload']['created_at']
)
self.assertEqual(
now,
output_log['payload']['deleted_at']
)
if 'location' in output_log['payload']:
self.fail('Notification contained location field.')
def test_task_delete_notification_disabled(self):
self.config(disabled_notifications=['task.delete'])
self.task_repo_proxy.remove(self.task_stub_proxy)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_task_run_notification(self):
with mock.patch('glance.async.TaskExecutor') as mock_executor:
executor = mock_executor.return_value
executor._run.return_value = mock.Mock()
self.task_proxy.run(executor=mock_executor)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.run', output_log['event_type'])
self.assertEqual(self.task.task_id, output_log['payload']['id'])
def test_task_run_notification_disabled(self):
self.config(disabled_notifications=['task.run'])
with mock.patch('glance.async.TaskExecutor') as mock_executor:
executor = mock_executor.return_value
executor._run.return_value = mock.Mock()
self.task_proxy.run(executor=mock_executor)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_task_processing_notification(self):
self.task_proxy.begin_processing()
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.processing', output_log['event_type'])
self.assertEqual(self.task.task_id, output_log['payload']['id'])
def test_task_processing_notification_disabled(self):
self.config(disabled_notifications=['task.processing'])
self.task_proxy.begin_processing()
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_task_success_notification(self):
self.task_proxy.begin_processing()
self.task_proxy.succeed(result=None)
output_logs = self.notifier.get_logs()
self.assertEqual(2, len(output_logs))
output_log = output_logs[1]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.success', output_log['event_type'])
self.assertEqual(self.task.task_id, output_log['payload']['id'])
def test_task_success_notification_disabled(self):
self.config(disabled_notifications=['task.processing', 'task.success'])
self.task_proxy.begin_processing()
self.task_proxy.succeed(result=None)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_task_failure_notification(self):
self.task_proxy.fail(message=None)
output_logs = self.notifier.get_logs()
self.assertEqual(1, len(output_logs))
output_log = output_logs[0]
self.assertEqual('INFO', output_log['notification_type'])
self.assertEqual('task.failure', output_log['event_type'])
self.assertEqual(self.task.task_id, output_log['payload']['id'])
def test_task_failure_notification_disabled(self):
self.config(disabled_notifications=['task.failure'])
self.task_proxy.fail(message=None)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
glance-16.0.1/glance/tests/unit/test_misc.py 0000666 0001750 0001750 00000005601 13267672245 021012 0 ustar zuul zuul 0000000 0000000 # Copyright 2010-2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import six
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.common import crypt
from glance.common import utils
from glance.tests import utils as test_utils
class UtilsTestCase(test_utils.BaseTestCase):
def test_encryption(self):
# Check that original plaintext and unencrypted ciphertext match
# Check keys of the three allowed lengths
key_list = ["1234567890abcdef",
"12345678901234567890abcd",
"1234567890abcdef1234567890ABCDEF"]
plaintext_list = ['']
blocksize = 64
for i in range(3 * blocksize):
text = os.urandom(i)
if six.PY3:
text = text.decode('latin1')
plaintext_list.append(text)
for key in key_list:
for plaintext in plaintext_list:
ciphertext = crypt.urlsafe_encrypt(key, plaintext, blocksize)
self.assertIsInstance(ciphertext, str)
self.assertNotEqual(ciphertext, plaintext)
text = crypt.urlsafe_decrypt(key, ciphertext)
self.assertIsInstance(text, str)
self.assertEqual(plaintext, text)
def test_empty_metadata_headers(self):
"""Ensure unset metadata is not encoded in HTTP headers"""
metadata = {
'foo': 'bar',
'snafu': None,
'bells': 'whistles',
'unset': None,
'empty': '',
'properties': {
'distro': '',
'arch': None,
'user': 'nobody',
},
}
headers = utils.image_meta_to_http_headers(metadata)
self.assertNotIn('x-image-meta-snafu', headers)
self.assertNotIn('x-image-meta-uset', headers)
self.assertNotIn('x-image-meta-snafu', headers)
self.assertNotIn('x-image-meta-property-arch', headers)
self.assertEqual('bar', headers.get('x-image-meta-foo'))
self.assertEqual('whistles', headers.get('x-image-meta-bells'))
self.assertEqual('', headers.get('x-image-meta-empty'))
self.assertEqual('', headers.get('x-image-meta-property-distro'))
self.assertEqual('nobody', headers.get('x-image-meta-property-user'))
glance-16.0.1/glance/tests/unit/test_data_migration_framework.py 0000666 0001750 0001750 00000020702 13267672245 025115 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from glance.db.sqlalchemy.alembic_migrations import data_migrations
from glance.tests import utils as test_utils
class TestDataMigrationFramework(test_utils.BaseTestCase):
@mock.patch('glance.db.sqlalchemy.alembic_migrations.data_migrations'
'._find_migration_modules')
def test_has_pending_migrations_no_migrations(self, mock_find):
mock_find.return_value = None
self.assertFalse(data_migrations.has_pending_migrations(mock.Mock()))
@mock.patch('glance.db.sqlalchemy.alembic_migrations.data_migrations'
'._find_migration_modules')
def test_has_pending_migrations_one_migration_no_pending(self, mock_find):
mock_migration1 = mock.Mock()
mock_migration1.has_migrations.return_value = False
mock_find.return_value = [mock_migration1]
self.assertFalse(data_migrations.has_pending_migrations(mock.Mock()))
@mock.patch('glance.db.sqlalchemy.alembic_migrations.data_migrations'
'._find_migration_modules')
def test_has_pending_migrations_one_migration_with_pending(self,
mock_find):
mock_migration1 = mock.Mock()
mock_migration1.has_migrations.return_value = True
mock_find.return_value = [mock_migration1]
self.assertTrue(data_migrations.has_pending_migrations(mock.Mock()))
@mock.patch('glance.db.sqlalchemy.alembic_migrations.data_migrations'
'._find_migration_modules')
def test_has_pending_migrations_mult_migration_no_pending(self, mock_find):
mock_migration1 = mock.Mock()
mock_migration1.has_migrations.return_value = False
mock_migration2 = mock.Mock()
mock_migration2.has_migrations.return_value = False
mock_migration3 = mock.Mock()
mock_migration3.has_migrations.return_value = False
mock_find.return_value = [mock_migration1, mock_migration2,
mock_migration3]
self.assertFalse(data_migrations.has_pending_migrations(mock.Mock()))
@mock.patch('glance.db.sqlalchemy.alembic_migrations.data_migrations'
'._find_migration_modules')
def test_has_pending_migrations_mult_migration_one_pending(self,
mock_find):
mock_migration1 = mock.Mock()
mock_migration1.has_migrations.return_value = False
mock_migration2 = mock.Mock()
mock_migration2.has_migrations.return_value = True
mock_migration3 = mock.Mock()
mock_migration3.has_migrations.return_value = False
mock_find.return_value = [mock_migration1, mock_migration2,
mock_migration3]
self.assertTrue(data_migrations.has_pending_migrations(mock.Mock()))
@mock.patch('glance.db.sqlalchemy.alembic_migrations.data_migrations'
'._find_migration_modules')
def test_has_pending_migrations_mult_migration_some_pending(self,
mock_find):
mock_migration1 = mock.Mock()
mock_migration1.has_migrations.return_value = False
mock_migration2 = mock.Mock()
mock_migration2.has_migrations.return_value = True
mock_migration3 = mock.Mock()
mock_migration3.has_migrations.return_value = False
mock_migration4 = mock.Mock()
mock_migration4.has_migrations.return_value = True
mock_find.return_value = [mock_migration1, mock_migration2,
mock_migration3, mock_migration4]
self.assertTrue(data_migrations.has_pending_migrations(mock.Mock()))
@mock.patch('importlib.import_module')
@mock.patch('pkgutil.iter_modules')
def test_find_migrations(self, mock_iter, mock_import):
def fake_iter_modules(blah):
yield 'blah', 'zebra01', 'blah'
yield 'blah', 'zebra02', 'blah'
yield 'blah', 'yellow01', 'blah'
yield 'blah', 'xray01', 'blah'
yield 'blah', 'wrinkle01', 'blah'
mock_iter.side_effect = fake_iter_modules
zebra1 = mock.Mock()
zebra1.has_migrations.return_value = mock.Mock()
zebra1.migrate.return_value = mock.Mock()
zebra2 = mock.Mock()
zebra2.has_migrations.return_value = mock.Mock()
zebra2.migrate.return_value = mock.Mock()
fake_imported_modules = [zebra1, zebra2]
mock_import.side_effect = fake_imported_modules
actual = data_migrations._find_migration_modules('zebra')
self.assertEqual(2, len(actual))
self.assertEqual(fake_imported_modules, actual)
@mock.patch('pkgutil.iter_modules')
def test_find_migrations_no_migrations(self, mock_iter):
def fake_iter_modules(blah):
yield 'blah', 'zebra01', 'blah'
yield 'blah', 'yellow01', 'blah'
yield 'blah', 'xray01', 'blah'
yield 'blah', 'wrinkle01', 'blah'
yield 'blah', 'victor01', 'blah'
mock_iter.side_effect = fake_iter_modules
actual = data_migrations._find_migration_modules('umbrella')
self.assertEqual(0, len(actual))
self.assertEqual([], actual)
def test_run_migrations(self):
zebra1 = mock.Mock()
zebra1.has_migrations.return_value = True
zebra1.migrate.return_value = 100
zebra2 = mock.Mock()
zebra2.has_migrations.return_value = True
zebra2.migrate.return_value = 50
migrations = [zebra1, zebra2]
engine = mock.Mock()
actual = data_migrations._run_migrations(engine, migrations)
self.assertEqual(150, actual)
zebra1.has_migrations.assert_called_once_with(engine)
zebra1.migrate.assert_called_once_with(engine)
zebra2.has_migrations.assert_called_once_with(engine)
zebra2.migrate.assert_called_once_with(engine)
def test_run_migrations_with_one_pending_migration(self):
zebra1 = mock.Mock()
zebra1.has_migrations.return_value = False
zebra1.migrate.return_value = 0
zebra2 = mock.Mock()
zebra2.has_migrations.return_value = True
zebra2.migrate.return_value = 50
migrations = [zebra1, zebra2]
engine = mock.Mock()
actual = data_migrations._run_migrations(engine, migrations)
self.assertEqual(50, actual)
zebra1.has_migrations.assert_called_once_with(engine)
zebra1.migrate.assert_not_called()
zebra2.has_migrations.assert_called_once_with(engine)
zebra2.migrate.assert_called_once_with(engine)
def test_run_migrations_with_no_migrations(self):
migrations = []
actual = data_migrations._run_migrations(mock.Mock(), migrations)
self.assertEqual(0, actual)
@mock.patch('glance.db.migration.CURRENT_RELEASE', 'zebra')
@mock.patch('importlib.import_module')
@mock.patch('pkgutil.iter_modules')
def test_migrate(self, mock_iter, mock_import):
def fake_iter_modules(blah):
yield 'blah', 'zebra01', 'blah'
yield 'blah', 'zebra02', 'blah'
yield 'blah', 'yellow01', 'blah'
yield 'blah', 'xray01', 'blah'
yield 'blah', 'xray02', 'blah'
mock_iter.side_effect = fake_iter_modules
zebra1 = mock.Mock()
zebra1.has_migrations.return_value = True
zebra1.migrate.return_value = 100
zebra2 = mock.Mock()
zebra2.has_migrations.return_value = True
zebra2.migrate.return_value = 50
fake_imported_modules = [zebra1, zebra2]
mock_import.side_effect = fake_imported_modules
engine = mock.Mock()
actual = data_migrations.migrate(engine, 'zebra')
self.assertEqual(150, actual)
zebra1.has_migrations.assert_called_once_with(engine)
zebra1.migrate.assert_called_once_with(engine)
zebra2.has_migrations.assert_called_once_with(engine)
zebra2.migrate.assert_called_once_with(engine)
glance-16.0.1/glance/tests/unit/test_context_middleware.py 0000666 0001750 0001750 00000015116 13267672245 023742 0 ustar zuul zuul 0000000 0000000 # All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import webob
from glance.api.middleware import context
import glance.context
from glance.tests.unit import base
class TestContextMiddleware(base.IsolatedUnitTest):
def _build_request(self, roles=None, identity_status='Confirmed',
service_catalog=None):
req = webob.Request.blank('/')
req.headers['x-auth-token'] = 'token1'
req.headers['x-identity-status'] = identity_status
req.headers['x-user-id'] = 'user1'
req.headers['x-tenant-id'] = 'tenant1'
_roles = roles or ['role1', 'role2']
req.headers['x-roles'] = ','.join(_roles)
if service_catalog:
req.headers['x-service-catalog'] = service_catalog
return req
def _build_middleware(self):
return context.ContextMiddleware(None)
def test_header_parsing(self):
req = self._build_request()
self._build_middleware().process_request(req)
self.assertEqual('token1', req.context.auth_token)
self.assertEqual('user1', req.context.user)
self.assertEqual('tenant1', req.context.tenant)
self.assertEqual(['role1', 'role2'], req.context.roles)
def test_is_admin_flag(self):
# is_admin check should look for 'admin' role by default
req = self._build_request(roles=['admin', 'role2'])
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
# without the 'admin' role, is_admin should be False
req = self._build_request()
self._build_middleware().process_request(req)
self.assertFalse(req.context.is_admin)
# if we change the admin_role attribute, we should be able to use it
req = self._build_request()
self.config(admin_role='role1')
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
def test_roles_case_insensitive(self):
# accept role from request
req = self._build_request(roles=['Admin', 'role2'])
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
# accept role from config
req = self._build_request(roles=['role1'])
self.config(admin_role='rOLe1')
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
def test_roles_stripping(self):
# stripping extra spaces in request
req = self._build_request(roles=['\trole1'])
self.config(admin_role='role1')
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
# stripping extra spaces in config
req = self._build_request(roles=['\trole1\n'])
self.config(admin_role=' role1\t')
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
def test_anonymous_access_enabled(self):
req = self._build_request(identity_status='Nope')
self.config(allow_anonymous_access=True)
middleware = self._build_middleware()
middleware.process_request(req)
self.assertIsNone(req.context.auth_token)
self.assertIsNone(req.context.user)
self.assertIsNone(req.context.tenant)
self.assertEqual([], req.context.roles)
self.assertFalse(req.context.is_admin)
self.assertTrue(req.context.read_only)
def test_anonymous_access_defaults_to_disabled(self):
req = self._build_request(identity_status='Nope')
middleware = self._build_middleware()
self.assertRaises(webob.exc.HTTPUnauthorized,
middleware.process_request, req)
def test_service_catalog(self):
catalog_json = "[{}]"
req = self._build_request(service_catalog=catalog_json)
self._build_middleware().process_request(req)
self.assertEqual([{}], req.context.service_catalog)
def test_invalid_service_catalog(self):
catalog_json = "bad json"
req = self._build_request(service_catalog=catalog_json)
middleware = self._build_middleware()
self.assertRaises(webob.exc.HTTPInternalServerError,
middleware.process_request, req)
def test_response(self):
req = self._build_request()
req.context = glance.context.RequestContext()
request_id = req.context.request_id
resp = webob.Response()
resp.request = req
self._build_middleware().process_response(resp)
self.assertEqual(request_id, resp.headers['x-openstack-request-id'])
resp_req_id = resp.headers['x-openstack-request-id']
# Validate that request-id do not starts with 'req-req-'
if isinstance(resp_req_id, bytes):
resp_req_id = resp_req_id.decode('utf-8')
self.assertFalse(resp_req_id.startswith('req-req-'))
self.assertTrue(resp_req_id.startswith('req-'))
class TestUnauthenticatedContextMiddleware(base.IsolatedUnitTest):
def test_request(self):
middleware = context.UnauthenticatedContextMiddleware(None)
req = webob.Request.blank('/')
middleware.process_request(req)
self.assertIsNone(req.context.auth_token)
self.assertIsNone(req.context.user)
self.assertIsNone(req.context.tenant)
self.assertEqual([], req.context.roles)
self.assertTrue(req.context.is_admin)
def test_response(self):
middleware = context.UnauthenticatedContextMiddleware(None)
req = webob.Request.blank('/')
req.context = glance.context.RequestContext()
request_id = req.context.request_id
resp = webob.Response()
resp.request = req
middleware.process_response(resp)
self.assertEqual(request_id, resp.headers['x-openstack-request-id'])
resp_req_id = resp.headers['x-openstack-request-id']
if isinstance(resp_req_id, bytes):
resp_req_id = resp_req_id.decode('utf-8')
# Validate that request-id do not starts with 'req-req-'
self.assertFalse(resp_req_id.startswith('req-req-'))
self.assertTrue(resp_req_id.startswith('req-'))
glance-16.0.1/glance/tests/unit/test_domain.py 0000666 0001750 0001750 00000053662 13267672245 021340 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
import mock
from oslo_config import cfg
import oslo_utils.importutils
import glance.async
from glance.async import taskflow_executor
from glance.common import exception
from glance.common import timeutils
from glance import domain
import glance.tests.utils as test_utils
CONF = cfg.CONF
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class TestImageFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestImageFactory, self).setUp()
self.image_factory = domain.ImageFactory()
def test_minimal_new_image(self):
image = self.image_factory.new_image()
self.assertIsNotNone(image.image_id)
self.assertIsNotNone(image.created_at)
self.assertEqual(image.created_at, image.updated_at)
self.assertEqual('queued', image.status)
self.assertEqual('shared', image.visibility)
self.assertIsNone(image.owner)
self.assertIsNone(image.name)
self.assertIsNone(image.size)
self.assertEqual(0, image.min_disk)
self.assertEqual(0, image.min_ram)
self.assertFalse(image.protected)
self.assertIsNone(image.disk_format)
self.assertIsNone(image.container_format)
self.assertEqual({}, image.extra_properties)
self.assertEqual(set([]), image.tags)
def test_new_image(self):
image = self.image_factory.new_image(
image_id=UUID1, name='image-1', min_disk=256,
owner=TENANT1)
self.assertEqual(UUID1, image.image_id)
self.assertIsNotNone(image.created_at)
self.assertEqual(image.created_at, image.updated_at)
self.assertEqual('queued', image.status)
self.assertEqual('shared', image.visibility)
self.assertEqual(TENANT1, image.owner)
self.assertEqual('image-1', image.name)
self.assertIsNone(image.size)
self.assertEqual(256, image.min_disk)
self.assertEqual(0, image.min_ram)
self.assertFalse(image.protected)
self.assertIsNone(image.disk_format)
self.assertIsNone(image.container_format)
self.assertEqual({}, image.extra_properties)
self.assertEqual(set([]), image.tags)
def test_new_image_with_extra_properties_and_tags(self):
extra_properties = {'foo': 'bar'}
tags = ['one', 'two']
image = self.image_factory.new_image(
image_id=UUID1, name='image-1',
extra_properties=extra_properties, tags=tags)
self.assertEqual(UUID1, image.image_id, UUID1)
self.assertIsNotNone(image.created_at)
self.assertEqual(image.created_at, image.updated_at)
self.assertEqual('queued', image.status)
self.assertEqual('shared', image.visibility)
self.assertIsNone(image.owner)
self.assertEqual('image-1', image.name)
self.assertIsNone(image.size)
self.assertEqual(0, image.min_disk)
self.assertEqual(0, image.min_ram)
self.assertFalse(image.protected)
self.assertIsNone(image.disk_format)
self.assertIsNone(image.container_format)
self.assertEqual({'foo': 'bar'}, image.extra_properties)
self.assertEqual(set(['one', 'two']), image.tags)
def test_new_image_read_only_property(self):
self.assertRaises(exception.ReadonlyProperty,
self.image_factory.new_image, image_id=UUID1,
name='image-1', size=256)
def test_new_image_unexpected_property(self):
self.assertRaises(TypeError,
self.image_factory.new_image, image_id=UUID1,
image_name='name-1')
def test_new_image_reserved_property(self):
extra_properties = {'deleted': True}
self.assertRaises(exception.ReservedProperty,
self.image_factory.new_image, image_id=UUID1,
extra_properties=extra_properties)
def test_new_image_for_is_public(self):
extra_prop = {'is_public': True}
new_image = self.image_factory.new_image(image_id=UUID1,
extra_properties=extra_prop)
self.assertEqual(True, new_image.extra_properties['is_public'])
class TestImage(test_utils.BaseTestCase):
def setUp(self):
super(TestImage, self).setUp()
self.image_factory = domain.ImageFactory()
self.image = self.image_factory.new_image(
container_format='bear', disk_format='rawr')
def test_extra_properties(self):
self.image.extra_properties = {'foo': 'bar'}
self.assertEqual({'foo': 'bar'}, self.image.extra_properties)
def test_extra_properties_assign(self):
self.image.extra_properties['foo'] = 'bar'
self.assertEqual({'foo': 'bar'}, self.image.extra_properties)
def test_delete_extra_properties(self):
self.image.extra_properties = {'foo': 'bar'}
self.assertEqual({'foo': 'bar'}, self.image.extra_properties)
del self.image.extra_properties['foo']
self.assertEqual({}, self.image.extra_properties)
def test_visibility_enumerated(self):
self.image.visibility = 'public'
self.image.visibility = 'private'
self.image.visibility = 'shared'
self.image.visibility = 'community'
self.assertRaises(ValueError, setattr,
self.image, 'visibility', 'ellison')
def test_tags_always_a_set(self):
self.image.tags = ['a', 'b', 'c']
self.assertEqual(set(['a', 'b', 'c']), self.image.tags)
def test_delete_protected_image(self):
self.image.protected = True
self.assertRaises(exception.ProtectedImageDelete, self.image.delete)
def test_status_saving(self):
self.image.status = 'saving'
self.assertEqual('saving', self.image.status)
def test_set_incorrect_status(self):
self.image.status = 'saving'
self.image.status = 'killed'
self.assertRaises(
exception.InvalidImageStatusTransition,
setattr, self.image, 'status', 'delet')
def test_status_saving_without_disk_format(self):
self.image.disk_format = None
self.assertRaises(ValueError, setattr,
self.image, 'status', 'saving')
def test_status_saving_without_container_format(self):
self.image.container_format = None
self.assertRaises(ValueError, setattr,
self.image, 'status', 'saving')
def test_status_active_without_disk_format(self):
self.image.disk_format = None
self.assertRaises(ValueError, setattr,
self.image, 'status', 'active')
def test_status_active_without_container_format(self):
self.image.container_format = None
self.assertRaises(ValueError, setattr,
self.image, 'status', 'active')
def test_delayed_delete(self):
self.config(delayed_delete=True)
self.image.status = 'active'
self.image.locations = [{'url': 'http://foo.bar/not.exists',
'metadata': {}}]
self.assertEqual('active', self.image.status)
self.image.delete()
self.assertEqual('pending_delete', self.image.status)
class TestImageMember(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMember, self).setUp()
self.image_member_factory = domain.ImageMemberFactory()
self.image_factory = domain.ImageFactory()
self.image = self.image_factory.new_image()
self.image_member = self.image_member_factory.new_image_member(
image=self.image,
member_id=TENANT1)
def test_status_enumerated(self):
self.image_member.status = 'pending'
self.image_member.status = 'accepted'
self.image_member.status = 'rejected'
self.assertRaises(ValueError, setattr,
self.image_member, 'status', 'ellison')
class TestImageMemberFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMemberFactory, self).setUp()
self.image_member_factory = domain.ImageMemberFactory()
self.image_factory = domain.ImageFactory()
def test_minimal_new_image_member(self):
member_id = 'fake-member-id'
image = self.image_factory.new_image(
image_id=UUID1, name='image-1', min_disk=256,
owner=TENANT1)
image_member = self.image_member_factory.new_image_member(image,
member_id)
self.assertEqual(image_member.image_id, image.image_id)
self.assertIsNotNone(image_member.created_at)
self.assertEqual(image_member.created_at, image_member.updated_at)
self.assertEqual('pending', image_member.status)
self.assertIsNotNone(image_member.member_id)
class TestExtraProperties(test_utils.BaseTestCase):
def test_getitem(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
self.assertEqual('bar', extra_properties['foo'])
self.assertEqual('golden', extra_properties['snitch'])
def test_getitem_with_no_items(self):
extra_properties = domain.ExtraProperties()
self.assertRaises(KeyError, extra_properties.__getitem__, 'foo')
def test_setitem(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
extra_properties['foo'] = 'baz'
self.assertEqual('baz', extra_properties['foo'])
def test_delitem(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
del extra_properties['foo']
self.assertRaises(KeyError, extra_properties.__getitem__, 'foo')
self.assertEqual('golden', extra_properties['snitch'])
def test_len_with_zero_items(self):
extra_properties = domain.ExtraProperties()
self.assertEqual(0, len(extra_properties))
def test_len_with_non_zero_items(self):
extra_properties = domain.ExtraProperties()
extra_properties['foo'] = 'bar'
extra_properties['snitch'] = 'golden'
self.assertEqual(2, len(extra_properties))
def test_eq_with_a_dict(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
ref_extra_properties = {'foo': 'bar', 'snitch': 'golden'}
self.assertEqual(ref_extra_properties, extra_properties)
def test_eq_with_an_object_of_ExtraProperties(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
ref_extra_properties = domain.ExtraProperties()
ref_extra_properties['snitch'] = 'golden'
ref_extra_properties['foo'] = 'bar'
self.assertEqual(ref_extra_properties, extra_properties)
def test_eq_with_uneqal_dict(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
ref_extra_properties = {'boo': 'far', 'gnitch': 'solden'}
self.assertNotEqual(ref_extra_properties, extra_properties)
def test_eq_with_unequal_ExtraProperties_object(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
ref_extra_properties = domain.ExtraProperties()
ref_extra_properties['gnitch'] = 'solden'
ref_extra_properties['boo'] = 'far'
self.assertNotEqual(ref_extra_properties, extra_properties)
def test_eq_with_incompatible_object(self):
a_dict = {'foo': 'bar', 'snitch': 'golden'}
extra_properties = domain.ExtraProperties(a_dict)
random_list = ['foo', 'bar']
self.assertNotEqual(random_list, extra_properties)
class TestTaskFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskFactory, self).setUp()
self.task_factory = domain.TaskFactory()
def test_new_task(self):
task_type = 'import'
owner = TENANT1
task_input = 'input'
task = self.task_factory.new_task(task_type, owner,
task_input=task_input,
result='test_result',
message='test_message')
self.assertIsNotNone(task.task_id)
self.assertIsNotNone(task.created_at)
self.assertEqual(task_type, task.type)
self.assertEqual(task.created_at, task.updated_at)
self.assertEqual('pending', task.status)
self.assertIsNone(task.expires_at)
self.assertEqual(owner, task.owner)
self.assertEqual(task_input, task.task_input)
self.assertEqual('test_message', task.message)
self.assertEqual('test_result', task.result)
def test_new_task_invalid_type(self):
task_type = 'blah'
owner = TENANT1
self.assertRaises(
exception.InvalidTaskType,
self.task_factory.new_task,
task_type,
owner,
)
class TestTask(test_utils.BaseTestCase):
def setUp(self):
super(TestTask, self).setUp()
self.task_factory = domain.TaskFactory()
task_type = 'import'
owner = TENANT1
task_ttl = CONF.task.task_time_to_live
self.task = self.task_factory.new_task(task_type,
owner,
task_time_to_live=task_ttl)
def test_task_invalid_status(self):
task_id = str(uuid.uuid4())
status = 'blah'
self.assertRaises(
exception.InvalidTaskStatus,
domain.Task,
task_id,
task_type='import',
status=status,
owner=None,
expires_at=None,
created_at=timeutils.utcnow(),
updated_at=timeutils.utcnow(),
task_input=None,
message=None,
result=None
)
def test_validate_status_transition_from_pending(self):
self.task.begin_processing()
self.assertEqual('processing', self.task.status)
def test_validate_status_transition_from_processing_to_success(self):
self.task.begin_processing()
self.task.succeed('')
self.assertEqual('success', self.task.status)
def test_validate_status_transition_from_processing_to_failure(self):
self.task.begin_processing()
self.task.fail('')
self.assertEqual('failure', self.task.status)
def test_invalid_status_transitions_from_pending(self):
# test do not allow transition from pending to success
self.assertRaises(
exception.InvalidTaskStatusTransition,
self.task.succeed,
''
)
def test_invalid_status_transitions_from_success(self):
# test do not allow transition from success to processing
self.task.begin_processing()
self.task.succeed('')
self.assertRaises(
exception.InvalidTaskStatusTransition,
self.task.begin_processing
)
# test do not allow transition from success to failure
self.assertRaises(
exception.InvalidTaskStatusTransition,
self.task.fail,
''
)
def test_invalid_status_transitions_from_failure(self):
# test do not allow transition from failure to processing
self.task.begin_processing()
self.task.fail('')
self.assertRaises(
exception.InvalidTaskStatusTransition,
self.task.begin_processing
)
# test do not allow transition from failure to success
self.assertRaises(
exception.InvalidTaskStatusTransition,
self.task.succeed,
''
)
def test_begin_processing(self):
self.task.begin_processing()
self.assertEqual('processing', self.task.status)
@mock.patch.object(timeutils, 'utcnow')
def test_succeed(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime.utcnow()
self.task.begin_processing()
self.task.succeed('{"location": "file://home"}')
self.assertEqual('success', self.task.status)
self.assertEqual('{"location": "file://home"}', self.task.result)
self.assertEqual(u'', self.task.message)
expected = (timeutils.utcnow() +
datetime.timedelta(hours=CONF.task.task_time_to_live))
self.assertEqual(
expected,
self.task.expires_at
)
@mock.patch.object(timeutils, 'utcnow')
def test_fail(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime.utcnow()
self.task.begin_processing()
self.task.fail('{"message": "connection failed"}')
self.assertEqual('failure', self.task.status)
self.assertEqual('{"message": "connection failed"}', self.task.message)
self.assertIsNone(self.task.result)
expected = (timeutils.utcnow() +
datetime.timedelta(hours=CONF.task.task_time_to_live))
self.assertEqual(
expected,
self.task.expires_at
)
@mock.patch.object(glance.async.TaskExecutor, 'begin_processing')
def test_run(self, mock_begin_processing):
executor = glance.async.TaskExecutor(context=mock.ANY,
task_repo=mock.ANY,
image_repo=mock.ANY,
image_factory=mock.ANY)
self.task.run(executor)
mock_begin_processing.assert_called_once_with(self.task.task_id)
class TestTaskStub(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskStub, self).setUp()
self.task_id = str(uuid.uuid4())
self.task_type = 'import'
self.owner = TENANT1
self.task_ttl = CONF.task.task_time_to_live
def test_task_stub_init(self):
self.task_factory = domain.TaskFactory()
task = domain.TaskStub(
self.task_id,
self.task_type,
'status',
self.owner,
'expires_at',
'created_at',
'updated_at'
)
self.assertEqual(self.task_id, task.task_id)
self.assertEqual(self.task_type, task.type)
self.assertEqual(self.owner, task.owner)
self.assertEqual('status', task.status)
self.assertEqual('expires_at', task.expires_at)
self.assertEqual('created_at', task.created_at)
self.assertEqual('updated_at', task.updated_at)
def test_task_stub_get_status(self):
status = 'pending'
task = domain.TaskStub(
self.task_id,
self.task_type,
status,
self.owner,
'expires_at',
'created_at',
'updated_at'
)
self.assertEqual(status, task.status)
class TestTaskExecutorFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskExecutorFactory, self).setUp()
self.task_repo = mock.Mock()
self.image_repo = mock.Mock()
self.image_factory = mock.Mock()
def test_init(self):
task_executor_factory = domain.TaskExecutorFactory(self.task_repo,
self.image_repo,
self.image_factory)
self.assertEqual(self.task_repo, task_executor_factory.task_repo)
def test_new_task_executor(self):
task_executor_factory = domain.TaskExecutorFactory(self.task_repo,
self.image_repo,
self.image_factory)
context = mock.Mock()
with mock.patch.object(oslo_utils.importutils,
'import_class') as mock_import_class:
mock_executor = mock.Mock()
mock_import_class.return_value = mock_executor
task_executor_factory.new_task_executor(context)
mock_executor.assert_called_once_with(context,
self.task_repo,
self.image_repo,
self.image_factory)
def test_new_task_executor_error(self):
task_executor_factory = domain.TaskExecutorFactory(self.task_repo,
self.image_repo,
self.image_factory)
context = mock.Mock()
with mock.patch.object(oslo_utils.importutils,
'import_class') as mock_import_class:
mock_import_class.side_effect = ImportError
self.assertRaises(ImportError,
task_executor_factory.new_task_executor,
context)
def test_new_task_eventlet_backwards_compatibility(self):
context = mock.MagicMock()
self.config(task_executor='eventlet', group='task')
task_executor_factory = domain.TaskExecutorFactory(self.task_repo,
self.image_repo,
self.image_factory)
# NOTE(flaper87): "eventlet" executor. short name to avoid > 79.
te_evnt = task_executor_factory.new_task_executor(context)
self.assertIsInstance(te_evnt, taskflow_executor.TaskExecutor)
glance-16.0.1/glance/tests/unit/v1/ 0000775 0001750 0001750 00000000000 13267672475 016775 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/v1/__init__.py 0000666 0001750 0001750 00000000000 13267672254 021071 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/v1/test_registry_client.py 0000666 0001750 0001750 00000112246 13267672254 023617 0 ustar zuul zuul 0000000 0000000 # Copyright 2010-2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import datetime
import os
import uuid
from mock import patch
from six.moves import http_client as http
from six.moves import reload_module
import testtools
from glance.api.v1.images import Controller as acontroller
from glance.common import client as test_client
from glance.common import config
from glance.common import exception
from glance.common import timeutils
from glance import context
from glance.db.sqlalchemy import api as db_api
from glance.registry.api.v1.images import Controller as rcontroller
import glance.registry.client.v1.api as rapi
from glance.registry.client.v1.api import client as rclient
from glance.tests.unit import base
from glance.tests import utils as test_utils
import webob
_gen_uuid = lambda: str(uuid.uuid4())
UUID1 = _gen_uuid()
UUID2 = _gen_uuid()
# NOTE(bcwaldon): needed to init config_dir cli opt
config.parse_args(args=[])
class TestRegistryV1Client(base.IsolatedUnitTest, test_utils.RegistryAPIMixIn):
"""
Test proper actions made for both valid and invalid requests
against a Registry service
"""
def setUp(self):
"""Establish a clean test environment"""
super(TestRegistryV1Client, self).setUp()
db_api.get_engine()
self.context = context.RequestContext(is_admin=True)
self.FIXTURES = [
self.get_fixture(
id=UUID1, name='fake image #1', is_public=False,
disk_format='ami', container_format='ami', size=13,
location="swift://user:passwd@acct/container/obj.tar.0",
properties={'type': 'kernel'}),
self.get_fixture(id=UUID2, name='fake image #2', properties={},
size=19, location="file:///tmp/glance-tests/2")]
self.destroy_fixtures()
self.create_fixtures()
self.client = rclient.RegistryClient("0.0.0.0")
def tearDown(self):
"""Clear the test environment"""
super(TestRegistryV1Client, self).tearDown()
self.destroy_fixtures()
def test_get_image_index(self):
"""Test correct set of public image returned"""
fixture = {
'id': UUID2,
'name': 'fake image #2'
}
images = self.client.get_images()
self.assertEqualImages(images, (UUID2,), unjsonify=False)
for k, v in fixture.items():
self.assertEqual(v, images[0][k])
def test_create_image_with_null_min_disk_min_ram(self):
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, min_disk=None, min_ram=None)
db_api.image_create(self.context, extra_fixture)
image = self.client.get_image(UUID3)
self.assertEqual(0, image["min_ram"])
self.assertEqual(0, image["min_disk"])
def test_get_index_sort_name_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by name in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='name', sort_dir='asc')
self.assertEqualImages(images, (UUID3, UUID2, UUID4), unjsonify=False)
def test_get_index_sort_status_desc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by status in
descending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
status='queued')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='status', sort_dir='desc')
self.assertEqualImages(images, (UUID3, UUID4, UUID2), unjsonify=False)
def test_get_index_sort_disk_format_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by disk_format in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
disk_format='vdi')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='disk_format',
sort_dir='asc')
self.assertEqualImages(images, (UUID3, UUID4, UUID2), unjsonify=False)
def test_get_index_sort_container_format_desc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by container_format in
descending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
disk_format='iso',
container_format='bare')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='container_format',
sort_dir='desc')
self.assertEqualImages(images, (UUID2, UUID4, UUID3), unjsonify=False)
def test_get_index_sort_size_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted by size in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami', size=100)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='asdf',
disk_format='iso',
container_format='bare', size=2)
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='size', sort_dir='asc')
self.assertEqualImages(images, (UUID4, UUID2, UUID3), unjsonify=False)
def test_get_index_sort_created_at_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted by created_at in ascending order.
"""
now = timeutils.utcnow()
time1 = now + datetime.timedelta(seconds=5)
time2 = now
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, created_at=time1)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=time2)
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='created_at', sort_dir='asc')
self.assertEqualImages(images, (UUID2, UUID4, UUID3), unjsonify=False)
def test_get_index_sort_updated_at_desc(self):
"""
Tests that the /images registry API returns list of
public images sorted by updated_at in descending order.
"""
now = timeutils.utcnow()
time1 = now + datetime.timedelta(seconds=5)
time2 = now
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, created_at=None,
updated_at=time1)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=None,
updated_at=time2)
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(sort_key='updated_at', sort_dir='desc')
self.assertEqualImages(images, (UUID3, UUID4, UUID2), unjsonify=False)
def test_get_image_index_marker(self):
"""Test correct set of images returned with marker param."""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='new name! #123',
status='saving')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='new name! #125',
status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(marker=UUID4)
self.assertEqualImages(images, (UUID3, UUID2), unjsonify=False)
def test_get_image_index_invalid_marker(self):
"""Test exception is raised when marker is invalid"""
self.assertRaises(exception.Invalid,
self.client.get_images,
marker=_gen_uuid())
def test_get_image_index_forbidden_marker(self):
"""Test exception is raised when marker is forbidden"""
UUID5 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID5, owner='0123',
status='saving', is_public=False)
db_api.image_create(self.context, extra_fixture)
def non_admin_get_images(self, context, *args, **kwargs):
"""Convert to non-admin context"""
context.is_admin = False
rcontroller.__get_images(self, context, *args, **kwargs)
rcontroller.__get_images = rcontroller._get_images
self.stubs.Set(rcontroller, '_get_images', non_admin_get_images)
self.assertRaises(exception.Invalid,
self.client.get_images,
marker=UUID5)
def test_get_image_index_private_marker(self):
"""Test exception is not raised if private non-owned marker is used"""
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, owner='1234',
status='saving', is_public=False)
db_api.image_create(self.context, extra_fixture)
try:
self.client.get_images(marker=UUID4)
except Exception as e:
self.fail("Unexpected exception '%s'" % e)
def test_get_image_index_limit(self):
"""Test correct number of images returned with limit param."""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(limit=2)
self.assertEqual(2, len(images))
def test_get_image_index_marker_limit(self):
"""Test correct set of images returned with marker/limit params."""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='new name! #123',
status='saving')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='new name! #125',
status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(marker=UUID3, limit=1)
self.assertEqualImages(images, (UUID2,), unjsonify=False)
def test_get_image_index_limit_None(self):
"""Test correct set of images returned with limit param == None."""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(limit=None)
self.assertEqual(3, len(images))
def test_get_image_index_by_name(self):
"""
Test correct set of public, name-filtered image returned. This
is just a sanity check, we test the details call more in-depth.
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), name='new name! #123')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images(filters={'name': 'new name! #123'})
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_get_image_details(self):
"""Tests that the detailed info about public images returned"""
fixture = self.get_fixture(id=UUID2, name='fake image #2',
properties={}, size=19, is_public=True)
images = self.client.get_images_detailed()
self.assertEqual(1, len(images))
for k, v in fixture.items():
self.assertEqual(v, images[0][k])
def test_get_image_details_marker_limit(self):
"""Test correct set of images returned with marker/limit params."""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, status='saving')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images_detailed(marker=UUID3, limit=1)
self.assertEqualImages(images, (UUID2,), unjsonify=False)
def test_get_image_details_invalid_marker(self):
"""Test exception is raised when marker is invalid"""
self.assertRaises(exception.Invalid,
self.client.get_images_detailed,
marker=_gen_uuid())
def test_get_image_details_forbidden_marker(self):
"""Test exception is raised when marker is forbidden"""
UUID5 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID5, is_public=False,
owner='0123', status='saving')
db_api.image_create(self.context, extra_fixture)
def non_admin_get_images(self, context, *args, **kwargs):
"""Convert to non-admin context"""
context.is_admin = False
rcontroller.__get_images(self, context, *args, **kwargs)
rcontroller.__get_images = rcontroller._get_images
self.stubs.Set(rcontroller, '_get_images', non_admin_get_images)
self.assertRaises(exception.Invalid,
self.client.get_images_detailed,
marker=UUID5)
def test_get_image_details_private_marker(self):
"""Test exception is not raised if private non-owned marker is used"""
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, is_public=False,
owner='1234', status='saving')
db_api.image_create(self.context, extra_fixture)
try:
self.client.get_images_detailed(marker=UUID4)
except Exception as e:
self.fail("Unexpected exception '%s'" % e)
def test_get_image_details_by_name(self):
"""Tests that a detailed call can be filtered by name"""
extra_fixture = self.get_fixture(id=_gen_uuid(), name='new name! #123')
db_api.image_create(self.context, extra_fixture)
filters = {'name': 'new name! #123'}
images = self.client.get_images_detailed(filters=filters)
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_get_image_details_by_status(self):
"""Tests that a detailed call can be filtered by status"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images_detailed(filters={'status': 'saving'})
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('saving', image['status'])
def test_get_image_details_by_container_format(self):
"""Tests that a detailed call can be filtered by container_format"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
filters = {'container_format': 'ovf'}
images = self.client.get_images_detailed(filters=filters)
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('ovf', image['container_format'])
def test_get_image_details_by_disk_format(self):
"""Tests that a detailed call can be filtered by disk_format"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
filters = {'disk_format': 'vhd'}
images = self.client.get_images_detailed(filters=filters)
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('vhd', image['disk_format'])
def test_get_image_details_with_maximum_size(self):
"""Tests that a detailed call can be filtered by size_max"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving',
size=21)
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images_detailed(filters={'size_max': 20})
self.assertEqual(1, len(images))
for image in images:
self.assertLessEqual(image['size'], 20)
def test_get_image_details_with_minimum_size(self):
"""Tests that a detailed call can be filtered by size_min"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images_detailed(filters={'size_min': 20})
self.assertEqual(1, len(images))
for image in images:
self.assertGreaterEqual(image['size'], 20)
def test_get_image_details_with_changes_since(self):
"""Tests that a detailed call can be filtered by changes-since"""
dt1 = timeutils.utcnow() - datetime.timedelta(1)
iso1 = timeutils.isotime(dt1)
dt2 = timeutils.utcnow() + datetime.timedelta(1)
iso2 = timeutils.isotime(dt2)
dt3 = timeutils.utcnow() + datetime.timedelta(2)
dt4 = timeutils.utcnow() + datetime.timedelta(3)
iso4 = timeutils.isotime(dt4)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='fake image #3')
db_api.image_create(self.context, extra_fixture)
db_api.image_destroy(self.context, UUID3)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='fake image #4',
created_at=dt3, updated_at=dt3)
db_api.image_create(self.context, extra_fixture)
# Check a standard list, 4 images in db (2 deleted)
images = self.client.get_images_detailed(filters={})
self.assertEqualImages(images, (UUID4, UUID2), unjsonify=False)
# Expect 3 images (1 deleted)
filters = {'changes-since': iso1}
images = self.client.get_images(filters=filters)
self.assertEqualImages(images, (UUID4, UUID3, UUID2), unjsonify=False)
# Expect 1 images (0 deleted)
filters = {'changes-since': iso2}
images = self.client.get_images_detailed(filters=filters)
self.assertEqualImages(images, (UUID4,), unjsonify=False)
# Expect 0 images (0 deleted)
filters = {'changes-since': iso4}
images = self.client.get_images(filters=filters)
self.assertEqualImages(images, (), unjsonify=False)
def test_get_image_details_with_size_min(self):
"""Tests that a detailed call can be filtered by size_min"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images_detailed(filters={'size_min': 20})
self.assertEqual(1, len(images))
for image in images:
self.assertGreaterEqual(image['size'], 20)
def test_get_image_details_by_property(self):
"""Tests that a detailed call can be filtered by a property"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving',
properties={'p a': 'v a'})
db_api.image_create(self.context, extra_fixture)
filters = {'property-p a': 'v a'}
images = self.client.get_images_detailed(filters=filters)
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('v a', image['properties']['p a'])
def test_get_image_is_public_v1(self):
"""Tests that a detailed call can be filtered by a property"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving',
properties={'is_public': 'avalue'})
context = copy.copy(self.context)
db_api.image_create(context, extra_fixture)
filters = {'property-is_public': 'avalue'}
images = self.client.get_images_detailed(filters=filters)
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('avalue', image['properties']['is_public'])
def test_get_image_details_sort_disk_format_asc(self):
"""
Tests that a detailed call returns list of
public images sorted alphabetically by disk_format in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf',
disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz',
disk_format='vdi')
db_api.image_create(self.context, extra_fixture)
images = self.client.get_images_detailed(sort_key='disk_format',
sort_dir='asc')
self.assertEqualImages(images, (UUID3, UUID4, UUID2), unjsonify=False)
def test_get_image(self):
"""Tests that the detailed info about an image returned"""
fixture = self.get_fixture(id=UUID1, name='fake image #1',
disk_format='ami', container_format='ami',
is_public=False, size=13,
properties={'type': 'kernel'})
data = self.client.get_image(UUID1)
for k, v in fixture.items():
el = data[k]
self.assertEqual(v, data[k],
"Failed v != data[k] where v = %(v)s and "
"k = %(k)s and data[k] = %(el)s" % {'v': v,
'k': k,
'el': el})
def test_get_image_non_existing(self):
"""Tests that NotFound is raised when getting a non-existing image"""
self.assertRaises(exception.NotFound,
self.client.get_image,
_gen_uuid())
def test_add_image_basic(self):
"""Tests that we can add image metadata and returns the new id"""
fixture = self.get_fixture(is_public=True)
new_image = self.client.add_image(fixture)
# Test all other attributes set
data = self.client.get_image(new_image['id'])
for k, v in fixture.items():
self.assertEqual(v, data[k])
# Test status was updated properly
self.assertIn('status', data.keys())
self.assertEqual('active', data['status'])
def test_add_image_with_properties(self):
"""Tests that we can add image metadata with properties"""
fixture = self.get_fixture(location="file:///tmp/glance-tests/2",
properties={'distro': 'Ubuntu 10.04 LTS'},
is_public=True)
new_image = self.client.add_image(fixture)
del fixture['location']
for k, v in fixture.items():
self.assertEqual(v, new_image[k])
# Test status was updated properly
self.assertIn('status', new_image.keys())
self.assertEqual('active', new_image['status'])
def test_add_image_with_location_data(self):
"""Tests that we can add image metadata with properties"""
location = "file:///tmp/glance-tests/2"
loc_meta = {'key': 'value'}
fixture = self.get_fixture(location_data=[{'url': location,
'metadata': loc_meta,
'status': 'active'}],
properties={'distro': 'Ubuntu 10.04 LTS'})
new_image = self.client.add_image(fixture)
self.assertEqual(location, new_image['location'])
self.assertEqual(location, new_image['location_data'][0]['url'])
self.assertEqual(loc_meta, new_image['location_data'][0]['metadata'])
def test_add_image_with_location_data_with_encryption(self):
"""Tests that we can add image metadata with properties and
enable encryption.
"""
self.client.metadata_encryption_key = '1234567890123456'
location = "file:///tmp/glance-tests/%d"
loc_meta = {'key': 'value'}
fixture = {'name': 'fake public image',
'is_public': True,
'disk_format': 'vmdk',
'container_format': 'ovf',
'size': 19,
'location_data': [{'url': location % 1,
'metadata': loc_meta,
'status': 'active'},
{'url': location % 2,
'metadata': {},
'status': 'active'}],
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
new_image = self.client.add_image(fixture)
self.assertEqual(location % 1, new_image['location'])
self.assertEqual(2, len(new_image['location_data']))
self.assertEqual(location % 1, new_image['location_data'][0]['url'])
self.assertEqual(loc_meta, new_image['location_data'][0]['metadata'])
self.assertEqual(location % 2, new_image['location_data'][1]['url'])
self.assertEqual({}, new_image['location_data'][1]['metadata'])
self.client.metadata_encryption_key = None
def test_add_image_already_exists(self):
"""Tests proper exception is raised if image with ID already exists"""
fixture = self.get_fixture(id=UUID2,
location="file:///tmp/glance-tests/2")
self.assertRaises(exception.Duplicate,
self.client.add_image,
fixture)
def test_add_image_with_bad_status(self):
"""Tests proper exception is raised if a bad status is set"""
fixture = self.get_fixture(status='bad status',
location="file:///tmp/glance-tests/2")
self.assertRaises(exception.Invalid,
self.client.add_image,
fixture)
def test_update_image(self):
"""Tests that the /images PUT registry API updates the image"""
fixture = {'name': 'fake public image #2',
'disk_format': 'vmdk'}
self.assertTrue(self.client.update_image(UUID2, fixture))
# Test all other attributes set
data = self.client.get_image(UUID2)
for k, v in fixture.items():
self.assertEqual(v, data[k])
def test_update_image_public(self):
"""Tests that the /images PUT registry API updates the image"""
fixture = {'name': 'fake public image #2',
'is_public': True,
'disk_format': 'vmdk'}
self.assertTrue(self.client.update_image(UUID2, fixture))
# Test all other attributes set
data = self.client.get_image(UUID2)
for k, v in fixture.items():
self.assertEqual(v, data[k])
def test_update_image_private(self):
"""Tests that the /images PUT registry API updates the image"""
fixture = {'name': 'fake public image #2',
'is_public': False,
'disk_format': 'vmdk'}
self.assertTrue(self.client.update_image(UUID2, fixture))
# Test all other attributes set
data = self.client.get_image(UUID2)
for k, v in fixture.items():
self.assertEqual(v, data[k])
def test_update_image_not_existing(self):
"""Tests non existing image update doesn't work"""
fixture = self.get_fixture(status='bad status')
self.assertRaises(exception.NotFound,
self.client.update_image,
_gen_uuid(),
fixture)
def test_delete_image(self):
"""Tests that image metadata is deleted properly"""
# Grab the original number of images
orig_num_images = len(self.client.get_images())
# Delete image #2
image = self.FIXTURES[1]
deleted_image = self.client.delete_image(image['id'])
self.assertTrue(deleted_image)
self.assertEqual(image['id'], deleted_image['id'])
self.assertTrue(deleted_image['deleted'])
self.assertTrue(deleted_image['deleted_at'])
# Verify one less image
new_num_images = len(self.client.get_images())
self.assertEqual(orig_num_images - 1, new_num_images)
def test_delete_image_not_existing(self):
"""Check that one cannot delete non-existing image."""
self.assertRaises(exception.NotFound,
self.client.delete_image,
_gen_uuid())
def test_get_image_members(self):
"""Test getting image members."""
memb_list = self.client.get_image_members(UUID2)
num_members = len(memb_list)
self.assertEqual(0, num_members)
def test_get_image_members_not_existing(self):
"""Test getting non-existent image members."""
self.assertRaises(exception.NotFound,
self.client.get_image_members,
_gen_uuid())
def test_get_member_images(self):
"""Test getting member images."""
memb_list = self.client.get_member_images('pattieblack')
num_members = len(memb_list)
self.assertEqual(0, num_members)
def test_add_replace_members(self):
"""Test replacing image members."""
self.assertTrue(self.client.add_member(UUID2, 'pattieblack'))
self.assertTrue(self.client.replace_members(UUID2,
dict(member_id='pattie'
'black2')))
def test_add_delete_member(self):
"""Tests deleting image members"""
self.client.add_member(UUID2, 'pattieblack')
self.assertTrue(self.client.delete_member(UUID2, 'pattieblack'))
class TestBaseClient(testtools.TestCase):
"""
Test proper actions made for both valid and invalid requests
against a Registry service
"""
def test_connect_kwargs_default_values(self):
actual = test_client.BaseClient('127.0.0.1').get_connect_kwargs()
self.assertEqual({'timeout': None}, actual)
def test_connect_kwargs(self):
base_client = test_client.BaseClient(
host='127.0.0.1', port=80, timeout=1, use_ssl=True)
actual = base_client.get_connect_kwargs()
expected = {'insecure': False,
'key_file': None,
'cert_file': None,
'timeout': 1}
for k in expected.keys():
self.assertEqual(expected[k], actual[k])
class TestRegistryV1ClientApi(base.IsolatedUnitTest):
def setUp(self):
"""Establish a clean test environment."""
super(TestRegistryV1ClientApi, self).setUp()
self.context = context.RequestContext()
reload_module(rapi)
def test_get_registry_client(self):
actual_client = rapi.get_registry_client(self.context)
self.assertIsNone(actual_client.identity_headers)
def test_get_registry_client_with_identity_headers(self):
self.config(send_identity_headers=True)
expected_identity_headers = {
'X-User-Id': '',
'X-Tenant-Id': '',
'X-Roles': ','.join(self.context.roles),
'X-Identity-Status': 'Confirmed',
'X-Service-Catalog': 'null',
}
actual_client = rapi.get_registry_client(self.context)
self.assertEqual(expected_identity_headers,
actual_client.identity_headers)
def test_configure_registry_client_not_using_use_user_token(self):
self.config(use_user_token=False)
with patch.object(rapi, 'configure_registry_admin_creds') as mock_rapi:
rapi.configure_registry_client()
mock_rapi.assert_called_once_with()
def _get_fake_config_creds(self, auth_url='auth_url', strategy='keystone'):
return {
'user': 'user',
'password': 'password',
'username': 'user',
'tenant': 'tenant',
'auth_url': auth_url,
'strategy': strategy,
'region': 'region'
}
def test_configure_registry_admin_creds(self):
expected = self._get_fake_config_creds(auth_url=None,
strategy='configured_strategy')
self.config(admin_user=expected['user'])
self.config(admin_password=expected['password'])
self.config(admin_tenant_name=expected['tenant'])
self.config(auth_strategy=expected['strategy'])
self.config(auth_region=expected['region'])
self.stubs.Set(os, 'getenv', lambda x: None)
self.assertIsNone(rapi._CLIENT_CREDS)
rapi.configure_registry_admin_creds()
self.assertEqual(expected, rapi._CLIENT_CREDS)
def test_configure_registry_admin_creds_with_auth_url(self):
expected = self._get_fake_config_creds()
self.config(admin_user=expected['user'])
self.config(admin_password=expected['password'])
self.config(admin_tenant_name=expected['tenant'])
self.config(auth_url=expected['auth_url'])
self.config(auth_strategy='test_strategy')
self.config(auth_region=expected['region'])
self.assertIsNone(rapi._CLIENT_CREDS)
rapi.configure_registry_admin_creds()
self.assertEqual(expected, rapi._CLIENT_CREDS)
class FakeResponse(object):
status = http.ACCEPTED
def getheader(*args, **kwargs):
return None
class TestRegistryV1ClientRequests(base.IsolatedUnitTest):
def setUp(self):
super(TestRegistryV1ClientRequests, self).setUp()
def test_do_request_with_identity_headers(self):
identity_headers = {'foo': 'bar'}
self.client = rclient.RegistryClient("0.0.0.0",
identity_headers=identity_headers)
with patch.object(test_client.BaseClient, 'do_request',
return_value=FakeResponse()) as mock_do_request:
self.client.do_request("GET", "/images")
mock_do_request.assert_called_once_with("GET", "/images",
headers=identity_headers)
def test_do_request(self):
self.client = rclient.RegistryClient("0.0.0.0")
with patch.object(test_client.BaseClient, 'do_request',
return_value=FakeResponse()) as mock_do_request:
self.client.do_request("GET", "/images")
mock_do_request.assert_called_once_with("GET", "/images",
headers={})
def test_registry_invalid_token_exception_handling(self):
self.image_controller = acontroller()
request = webob.Request.blank('/images')
request.method = 'GET'
request.context = context.RequestContext()
with patch.object(rapi, 'get_images_detail') as mock_detail:
mock_detail.side_effect = exception.NotAuthenticated()
self.assertRaises(webob.exc.HTTPUnauthorized,
self.image_controller.detail, request)
glance-16.0.1/glance/tests/unit/v1/test_registry_api.py 0000666 0001750 0001750 00000254617 13267672254 023123 0 ustar zuul zuul 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
import mock
from oslo_serialization import jsonutils
import routes
import six
from six.moves import http_client as http
import webob
import glance.api.common
import glance.common.config
from glance.common import crypt
from glance.common import timeutils
from glance import context
from glance.db.sqlalchemy import api as db_api
from glance.db.sqlalchemy import models as db_models
from glance.registry.api import v1 as rserver
from glance.tests.unit import base
from glance.tests import utils as test_utils
_gen_uuid = lambda: str(uuid.uuid4())
UUID1 = _gen_uuid()
UUID2 = _gen_uuid()
class TestRegistryAPI(base.IsolatedUnitTest, test_utils.RegistryAPIMixIn):
def setUp(self):
"""Establish a clean test environment"""
super(TestRegistryAPI, self).setUp()
self.mapper = routes.Mapper()
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=True)
def _get_extra_fixture(id, name, **kwargs):
return self.get_extra_fixture(
id, name,
locations=[{'url': "file:///%s/%s" % (self.test_dir, id),
'metadata': {}, 'status': 'active'}], **kwargs)
self.FIXTURES = [
_get_extra_fixture(UUID1, 'fake image #1', is_public=False,
disk_format='ami', container_format='ami',
min_disk=0, min_ram=0, owner=123,
size=13, properties={'type': 'kernel'}),
_get_extra_fixture(UUID2, 'fake image #2',
min_disk=5, min_ram=256,
size=19, properties={})]
self.context = context.RequestContext(is_admin=True)
db_api.get_engine()
self.destroy_fixtures()
self.create_fixtures()
def tearDown(self):
"""Clear the test environment"""
super(TestRegistryAPI, self).tearDown()
self.destroy_fixtures()
def test_show(self):
"""
Tests that the /images/ registry API endpoint
returns the expected image
"""
fixture = {'id': UUID2,
'name': 'fake image #2',
'size': 19,
'min_ram': 256,
'min_disk': 5,
'checksum': None}
res = self.get_api_response_ext(http.OK, '/images/%s' % UUID2)
res_dict = jsonutils.loads(res.body)
image = res_dict['image']
for k, v in six.iteritems(fixture):
self.assertEqual(v, image[k])
def test_show_unknown(self):
"""
Tests that the /images/ registry API endpoint
returns a 404 for an unknown image id
"""
self.get_api_response_ext(http.NOT_FOUND, '/images/%s' % _gen_uuid())
def test_show_invalid(self):
"""
Tests that the /images/ registry API endpoint
returns a 404 for an invalid (therefore unknown) image id
"""
self.get_api_response_ext(http.NOT_FOUND, '/images/%s' % _gen_uuid())
def test_show_deleted_image_as_admin(self):
"""
Tests that the /images/ registry API endpoint
returns a 200 for deleted image to admin user.
"""
# Delete image #2
self.get_api_response_ext(http.OK, '/images/%s' % UUID2,
method='DELETE')
self.get_api_response_ext(http.OK, '/images/%s' % UUID2)
def test_show_deleted_image_as_nonadmin(self):
"""
Tests that the /images/ registry API endpoint
returns a 404 for deleted image to non-admin user.
"""
# Delete image #2
self.get_api_response_ext(http.OK, '/images/%s' % UUID2,
method='DELETE')
api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=False)
self.get_api_response_ext(http.NOT_FOUND, '/images/%s' % UUID2,
api=api)
def test_show_private_image_with_no_admin_user(self):
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, size=18, owner='test user',
is_public=False)
db_api.image_create(self.context, extra_fixture)
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
self.get_api_response_ext(http.NOT_FOUND, '/images/%s' % UUID4,
api=api)
def test_get_root(self):
"""
Tests that the root registry API returns "index",
which is a list of public images
"""
fixture = {'id': UUID2, 'size': 19, 'checksum': None}
res = self.get_api_response_ext(http.OK, url='/')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for k, v in six.iteritems(fixture):
self.assertEqual(v, images[0][k])
def test_get_index(self):
"""
Tests that the /images registry API returns list of
public images
"""
fixture = {'id': UUID2, 'size': 19, 'checksum': None}
res = self.get_api_response_ext(http.OK)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for k, v in six.iteritems(fixture):
self.assertEqual(v, images[0][k])
def test_get_index_marker(self):
"""
Tests that the /images registry API returns list of
public images that conforms to a marker query param
"""
time1 = timeutils.utcnow() + datetime.timedelta(seconds=5)
time2 = timeutils.utcnow() + datetime.timedelta(seconds=4)
time3 = timeutils.utcnow()
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=19, created_at=time1)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=time2)
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID5, created_at=time3)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images?marker=%s' % UUID4)
self.assertEqualImages(res, (UUID5, UUID2))
def test_get_index_unknown_marker(self):
"""
Tests that the /images registry API returns a 400
when an unknown marker is provided
"""
self.get_api_response_ext(http.BAD_REQUEST,
url='/images?marker=%s' % _gen_uuid())
def test_get_index_malformed_marker(self):
"""
Tests that the /images registry API returns a 400
when a malformed marker is provided
"""
res = self.get_api_response_ext(http.BAD_REQUEST,
url='/images?marker=4')
self.assertIn(b'marker', res.body)
def test_get_index_forbidden_marker(self):
"""
Tests that the /images registry API returns a 400
when a forbidden marker is provided
"""
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
self.get_api_response_ext(http.BAD_REQUEST,
url='/images?marker=%s' % UUID1, api=api)
def test_get_index_limit(self):
"""
Tests that the /images registry API returns list of
public images that conforms to a limit query param
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url='/images?limit=1')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
# expect list to be sorted by created_at desc
self.assertEqual(UUID4, images[0]['id'])
def test_get_index_limit_negative(self):
"""
Tests that the /images registry API returns list of
public images that conforms to a limit query param
"""
self.get_api_response_ext(http.BAD_REQUEST, url='/images?limit=-1')
def test_get_index_limit_non_int(self):
"""
Tests that the /images registry API returns list of
public images that conforms to a limit query param
"""
self.get_api_response_ext(http.BAD_REQUEST, url='/images?limit=a')
def test_get_index_limit_marker(self):
"""
Tests that the /images registry API returns list of
public images that conforms to limit and marker query params
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=19)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid())
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(
http.OK, url='/images?marker=%s&limit=1' % UUID3)
self.assertEqualImages(res, (UUID2,))
def test_get_index_filter_on_user_defined_properties(self):
"""
Tests that /images registry API returns list of public images based
a filter on user-defined properties.
"""
image1_id = _gen_uuid()
properties = {'distro': 'ubuntu', 'arch': 'i386'}
extra_fixture = self.get_fixture(id=image1_id, name='image-extra-1',
properties=properties)
db_api.image_create(self.context, extra_fixture)
image2_id = _gen_uuid()
properties = {'distro': 'ubuntu', 'arch': 'x86_64', 'foo': 'bar'}
extra_fixture = self.get_fixture(id=image2_id, name='image-extra-2',
properties=properties)
db_api.image_create(self.context, extra_fixture)
# Test index with filter containing one user-defined property.
# Filter is 'property-distro=ubuntu'.
# Verify both image1 and image2 are returned
res = self.get_api_response_ext(http.OK, url='/images?'
'property-distro=ubuntu')
images = jsonutils.loads(res.body)['images']
self.assertEqual(2, len(images))
self.assertEqual(image2_id, images[0]['id'])
self.assertEqual(image1_id, images[1]['id'])
# Test index with filter containing one user-defined property but
# non-existent value. Filter is 'property-distro=fedora'.
# Verify neither images are returned
res = self.get_api_response_ext(http.OK, url='/images?'
'property-distro=fedora')
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing one user-defined property but
# unique value. Filter is 'property-arch=i386'.
# Verify only image1 is returned.
res = self.get_api_response_ext(http.OK, url='/images?'
'property-arch=i386')
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image1_id, images[0]['id'])
# Test index with filter containing one user-defined property but
# unique value. Filter is 'property-arch=x86_64'.
# Verify only image1 is returned.
res = self.get_api_response_ext(http.OK, url='/images?'
'property-arch=x86_64')
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Test index with filter containing unique user-defined property.
# Filter is 'property-foo=bar'.
# Verify only image2 is returned.
res = self.get_api_response_ext(http.OK,
url='/images?property-foo=bar')
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Test index with filter containing unique user-defined property but
# .value is non-existent. Filter is 'property-foo=baz'.
# Verify neither images are returned.
res = self.get_api_response_ext(http.OK,
url='/images?property-foo=baz')
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties
# Filter is 'property-arch=x86_64&property-distro=ubuntu'.
# Verify only image2 is returned.
res = self.get_api_response_ext(http.OK, url='/images?'
'property-arch=x86_64&'
'property-distro=ubuntu')
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Test index with filter containing multiple user-defined properties
# Filter is 'property-arch=i386&property-distro=ubuntu'.
# Verify only image1 is returned.
res = self.get_api_response_ext(http.OK,
url='/images?property-arch=i386&'
'property-distro=ubuntu')
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image1_id, images[0]['id'])
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-arch=random&property-distro=ubuntu'.
# Verify neither images are returned.
res = self.get_api_response_ext(http.OK, url='/images?'
'property-arch=random&'
'property-distro=ubuntu')
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-arch=random&property-distro=random'.
# Verify neither images are returned.
res = self.get_api_response_ext(http.OK, url='/images?'
'property-arch=random&'
'property-distro=random')
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-boo=far&property-poo=far'.
# Verify neither images are returned.
res = self.get_api_response_ext(http.OK,
url='/images?property-boo=far&'
'property-poo=far')
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-foo=bar&property-poo=far'.
# Verify neither images are returned.
res = self.get_api_response_ext(http.OK,
url='/images?property-foo=bar&'
'property-poo=far')
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
def test_get_index_filter_name(self):
"""
Tests that the /images registry API returns list of
public images that have a specific name. This is really a sanity
check, filtering is tested more in-depth using /images/detail
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #123', size=19)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), name='new name! #123')
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images?name=new name! #123')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_get_index_sort_default_created_at_desc(self):
"""
Tests that the /images registry API returns list of
public images that conforms to a default sort key/dir
"""
time1 = timeutils.utcnow() + datetime.timedelta(seconds=5)
time2 = timeutils.utcnow() + datetime.timedelta(seconds=4)
time3 = timeutils.utcnow()
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=19, created_at=time1)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=time2)
db_api.image_create(self.context, extra_fixture)
UUID5 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID5, created_at=time3)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url='/images')
self.assertEqualImages(res, (UUID3, UUID4, UUID5, UUID2))
def test_get_index_bad_sort_key(self):
"""Ensure a 400 is returned when a bad sort_key is provided."""
self.get_api_response_ext(http.BAD_REQUEST,
url='/images?sort_key=asdf')
def test_get_index_bad_sort_dir(self):
"""Ensure a 400 is returned when a bad sort_dir is provided."""
self.get_api_response_ext(http.BAD_REQUEST,
url='/images?sort_dir=asdf')
def test_get_index_null_name(self):
"""Check 200 is returned when sort_key is null name
Check 200 is returned when sort_key is name and name is null
for specified marker
"""
UUID6 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID6, name=None)
db_api.image_create(self.context, extra_fixture)
self.get_api_response_ext(
http.OK, url='/images?sort_key=name&marker=%s' % UUID6)
def test_get_index_null_disk_format(self):
"""Check 200 is returned when sort_key is null disk_format
Check 200 is returned when sort_key is disk_format and
disk_format is null for specified marker
"""
UUID6 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID6, disk_format=None, size=19)
db_api.image_create(self.context, extra_fixture)
self.get_api_response_ext(
http.OK, url='/images?sort_key=disk_format&marker=%s' % UUID6)
def test_get_index_null_container_format(self):
"""Check 200 is returned when sort_key is null container_format
Check 200 is returned when sort_key is container_format and
container_format is null for specified marker
"""
UUID6 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID6, container_format=None)
db_api.image_create(self.context, extra_fixture)
self.get_api_response_ext(
http.OK, url='/images?sort_key=container_format&marker=%s' % UUID6)
def test_get_index_sort_name_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by name in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf', size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz')
db_api.image_create(self.context, extra_fixture)
url = '/images?sort_key=name&sort_dir=asc'
res = self.get_api_response_ext(http.OK, url=url)
self.assertEqualImages(res, (UUID3, UUID2, UUID4))
def test_get_index_sort_status_desc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by status in
descending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, status='queued', size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url=(
'/images?sort_key=status&sort_dir=desc'))
self.assertEqualImages(res, (UUID3, UUID4, UUID2))
def test_get_index_sort_disk_format_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by disk_format in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, disk_format='ami',
container_format='ami', size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, disk_format='vdi')
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url=(
'/images?sort_key=disk_format&sort_dir=asc'))
self.assertEqualImages(res, (UUID3, UUID4, UUID2))
def test_get_index_sort_container_format_desc(self):
"""
Tests that the /images registry API returns list of
public images sorted alphabetically by container_format in
descending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=19, disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, disk_format='iso',
container_format='bare')
db_api.image_create(self.context, extra_fixture)
url = '/images?sort_key=container_format&sort_dir=desc'
res = self.get_api_response_ext(http.OK, url=url)
self.assertEqualImages(res, (UUID2, UUID4, UUID3))
def test_get_index_sort_size_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted by size in ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, disk_format='ami',
container_format='ami', size=100)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, disk_format='iso',
container_format='bare', size=2)
db_api.image_create(self.context, extra_fixture)
url = '/images?sort_key=size&sort_dir=asc'
res = self.get_api_response_ext(http.OK, url=url)
self.assertEqualImages(res, (UUID4, UUID2, UUID3))
def test_get_index_sort_created_at_asc(self):
"""
Tests that the /images registry API returns list of
public images sorted by created_at in ascending order.
"""
now = timeutils.utcnow()
time1 = now + datetime.timedelta(seconds=5)
time2 = now
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, created_at=time1, size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=time2)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url=(
'/images?sort_key=created_at&sort_dir=asc'))
self.assertEqualImages(res, (UUID2, UUID4, UUID3))
def test_get_index_sort_updated_at_desc(self):
"""
Tests that the /images registry API returns list of
public images sorted by updated_at in descending order.
"""
now = timeutils.utcnow()
time1 = now + datetime.timedelta(seconds=5)
time2 = now
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=19, created_at=None,
updated_at=time1)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, created_at=None,
updated_at=time2)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url=(
'/images?sort_key=updated_at&sort_dir=desc'))
self.assertEqualImages(res, (UUID3, UUID4, UUID2))
def test_get_details(self):
"""
Tests that the /images/detail registry API returns
a mapping containing a list of detailed image information
"""
fixture = {'id': UUID2,
'name': 'fake image #2',
'is_public': True,
'size': 19,
'min_disk': 5,
'min_ram': 256,
'checksum': None,
'disk_format': 'vhd',
'container_format': 'ovf',
'status': 'active'}
res = self.get_api_response_ext(http.OK, url='/images/detail')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for k, v in six.iteritems(fixture):
self.assertEqual(v, images[0][k])
def test_get_details_limit_marker(self):
"""
Tests that the /images/details registry API returns list of
public images that conforms to limit and marker query params.
This functionality is tested more thoroughly on /images, this is
just a sanity check
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=20)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid())
db_api.image_create(self.context, extra_fixture)
url = '/images/detail?marker=%s&limit=1' % UUID3
res = self.get_api_response_ext(http.OK, url=url)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
# expect list to be sorted by created_at desc
self.assertEqual(UUID2, images[0]['id'])
def test_get_details_invalid_marker(self):
"""
Tests that the /images/detail registry API returns a 400
when an invalid marker is provided
"""
url = '/images/detail?marker=%s' % _gen_uuid()
self.get_api_response_ext(http.BAD_REQUEST, url=url)
def test_get_details_malformed_marker(self):
"""
Tests that the /images/detail registry API returns a 400
when a malformed marker is provided
"""
res = self.get_api_response_ext(http.BAD_REQUEST,
url='/images/detail?marker=4')
self.assertIn(b'marker', res.body)
def test_get_details_forbidden_marker(self):
"""
Tests that the /images/detail registry API returns a 400
when a forbidden marker is provided
"""
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
self.get_api_response_ext(http.BAD_REQUEST, api=api,
url='/images/detail?marker=%s' % UUID1)
def test_get_details_filter_name(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific name
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #123', size=20)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(),
name='new name! #123')
db_api.image_create(self.context, extra_fixture)
url = '/images/detail?name=new name! #123'
res = self.get_api_response_ext(http.OK, url=url)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_get_details_filter_status(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific status
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), status='saving')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), size=19,
status='active')
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?status=saving')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('saving', image['status'])
def test_get_details_filter_container_format(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific container_format
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='vdi',
size=19)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami', size=19)
db_api.image_create(self.context, extra_fixture)
url = '/images/detail?container_format=ovf'
res = self.get_api_response_ext(http.OK, url=url)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('ovf', image['container_format'])
def test_get_details_filter_min_disk(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific min_disk
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), min_disk=7, size=19)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami', size=19)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?min_disk=7')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for image in images:
self.assertEqual(7, image['min_disk'])
def test_get_details_filter_min_ram(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific min_ram
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), min_ram=514, size=19)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami', size=19)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?min_ram=514')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for image in images:
self.assertEqual(514, image['min_ram'])
def test_get_details_filter_disk_format(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific disk_format
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), size=19)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami', size=19)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?disk_format=vhd')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertEqual('vhd', image['disk_format'])
def test_get_details_filter_size_min(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a size greater than or equal to size_min
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), size=18)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?size_min=19')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertGreaterEqual(image['size'], 19)
def test_get_details_filter_size_max(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a size less than or equal to size_max
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), size=18)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?size_max=19')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertLessEqual(image['size'], 19)
def test_get_details_filter_size_min_max(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a size less than or equal to size_max
and greater than or equal to size_min
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), size=18)
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), disk_format='ami',
container_format='ami')
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), size=6)
db_api.image_create(self.context, extra_fixture)
url = '/images/detail?size_min=18&size_max=19'
res = self.get_api_response_ext(http.OK, url=url)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertTrue(18 <= image['size'] <= 19)
def test_get_details_filter_changes_since(self):
"""
Tests that the /images/detail registry API returns list of
images that changed since the time defined by changes-since
"""
dt1 = timeutils.utcnow() - datetime.timedelta(1)
iso1 = timeutils.isotime(dt1)
date_only1 = dt1.strftime('%Y-%m-%d')
date_only2 = dt1.strftime('%Y%m%d')
date_only3 = dt1.strftime('%Y-%m%d')
dt2 = timeutils.utcnow() + datetime.timedelta(1)
iso2 = timeutils.isotime(dt2)
image_ts = timeutils.utcnow() + datetime.timedelta(2)
hour_before = image_ts.strftime('%Y-%m-%dT%H:%M:%S%%2B01:00')
hour_after = image_ts.strftime('%Y-%m-%dT%H:%M:%S-01:00')
dt4 = timeutils.utcnow() + datetime.timedelta(3)
iso4 = timeutils.isotime(dt4)
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, size=18)
db_api.image_create(self.context, extra_fixture)
db_api.image_destroy(self.context, UUID3)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4,
disk_format='ami',
container_format='ami',
created_at=image_ts,
updated_at=image_ts)
db_api.image_create(self.context, extra_fixture)
# Check a standard list, 4 images in db (2 deleted)
res = self.get_api_response_ext(http.OK, url='/images/detail')
self.assertEqualImages(res, (UUID4, UUID2))
# Expect 3 images (1 deleted)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?changes-since=%s' % iso1))
self.assertEqualImages(res, (UUID4, UUID3, UUID2))
# Expect 1 images (0 deleted)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?changes-since=%s' % iso2))
self.assertEqualImages(res, (UUID4,))
# Expect 1 images (0 deleted)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?changes-since=%s' % hour_before))
self.assertEqualImages(res, (UUID4,))
# Expect 0 images (0 deleted)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?changes-since=%s' % hour_after))
self.assertEqualImages(res, ())
# Expect 0 images (0 deleted)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?changes-since=%s' % iso4))
self.assertEqualImages(res, ())
for param in [date_only1, date_only2, date_only3]:
# Expect 3 images (1 deleted)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?changes-since=%s' % param))
self.assertEqualImages(res, (UUID4, UUID3, UUID2))
# Bad request (empty changes-since param)
self.get_api_response_ext(http.BAD_REQUEST,
url='/images/detail?changes-since=')
def test_get_details_filter_property(self):
"""
Tests that the /images/detail registry API returns list of
public images that have a specific custom property
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), size=19,
properties={'prop_123': 'v a'})
db_api.image_create(self.context, extra_fixture)
extra_fixture = self.get_fixture(id=_gen_uuid(), size=19,
disk_format='ami',
container_format='ami',
properties={'prop_123': 'v b'})
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?property-prop_123=v%20a'))
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for image in images:
self.assertEqual('v a', image['properties']['prop_123'])
def test_get_details_filter_public_none(self):
"""
Tests that the /images/detail registry API returns list of
all images if is_public none is passed
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
is_public=False, size=18)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?is_public=None')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(3, len(images))
def test_get_details_filter_public_false(self):
"""
Tests that the /images/detail registry API returns list of
private images if is_public false is passed
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
is_public=False, size=18)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?is_public=False')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
for image in images:
self.assertEqual(False, image['is_public'])
def test_get_details_filter_public_true(self):
"""
Tests that the /images/detail registry API returns list of
public images if is_public true is passed (same as default)
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
is_public=False, size=18)
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?is_public=True')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
for image in images:
self.assertTrue(image['is_public'])
def test_get_details_filter_public_string_format(self):
"""
Tests that the /images/detail registry
API returns 400 Bad error for filter is_public with wrong format
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
is_public='true', size=18)
db_api.image_create(self.context, extra_fixture)
self.get_api_response_ext(http.BAD_REQUEST,
url='/images/detail?is_public=public')
def test_get_details_filter_deleted_false(self):
"""
Test that the /images/detail registry
API return list of images with deleted filter = false
"""
extra_fixture = {'id': _gen_uuid(),
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'test deleted filter 1',
'size': 18,
'deleted': False,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK,
url='/images/detail?deleted=False')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
for image in images:
self.assertFalse(image['deleted'])
def test_get_filter_no_public_with_no_admin(self):
"""
Tests that the /images/detail registry API returns list of
public images if is_public true is passed (same as default)
"""
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4,
is_public=False, size=18)
db_api.image_create(self.context, extra_fixture)
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
res = self.get_api_response_ext(http.OK, api=api,
url='/images/detail?is_public=False')
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
# Check that for non admin user only is_public = True images returns
for image in images:
self.assertTrue(image['is_public'])
def test_get_filter_protected_with_None_value(self):
"""
Tests that the /images/detail registry API returns 400 error
"""
extra_fixture = self.get_fixture(id=_gen_uuid(), size=18,
protected="False")
db_api.image_create(self.context, extra_fixture)
self.get_api_response_ext(http.BAD_REQUEST,
url='/images/detail?protected=')
def test_get_filter_protected_with_True_value(self):
"""
Tests that the /images/detail registry API returns 400 error
"""
extra_fixture = self.get_fixture(id=_gen_uuid(),
size=18, protected="True")
db_api.image_create(self.context, extra_fixture)
self.get_api_response_ext(http.OK, url='/images/detail?protected=True')
def test_get_details_sort_name_asc(self):
"""
Tests that the /images/details registry API returns list of
public images sorted alphabetically by name in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID3, name='asdf', size=19)
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID4, name='xyz')
db_api.image_create(self.context, extra_fixture)
res = self.get_api_response_ext(http.OK, url=(
'/images/detail?sort_key=name&sort_dir=asc'))
self.assertEqualImages(res, (UUID3, UUID2, UUID4))
def test_create_image(self):
"""Tests that the /images POST registry API creates the image"""
fixture = self.get_minimal_fixture(is_public=True)
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.OK, body=body,
method='POST', content_type='json')
res_dict = jsonutils.loads(res.body)
for k, v in six.iteritems(fixture):
self.assertEqual(v, res_dict['image'][k])
# Test status was updated properly
self.assertEqual('active', res_dict['image']['status'])
def test_create_image_with_min_disk(self):
"""Tests that the /images POST registry API creates the image"""
fixture = self.get_minimal_fixture(min_disk=5)
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.OK, body=body,
method='POST', content_type='json')
res_dict = jsonutils.loads(res.body)
self.assertEqual(5, res_dict['image']['min_disk'])
def test_create_image_with_min_ram(self):
"""Tests that the /images POST registry API creates the image"""
fixture = self.get_minimal_fixture(min_ram=256)
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.OK, body=body,
method='POST', content_type='json')
res_dict = jsonutils.loads(res.body)
self.assertEqual(256, res_dict['image']['min_ram'])
def test_create_image_with_min_ram_default(self):
"""Tests that the /images POST registry API creates the image"""
fixture = self.get_minimal_fixture()
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.OK, body=body,
method='POST', content_type='json')
res_dict = jsonutils.loads(res.body)
self.assertEqual(0, res_dict['image']['min_ram'])
def test_create_image_with_min_disk_default(self):
"""Tests that the /images POST registry API creates the image"""
fixture = self.get_minimal_fixture()
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.OK, body=body,
method='POST', content_type='json')
res_dict = jsonutils.loads(res.body)
self.assertEqual(0, res_dict['image']['min_disk'])
def test_create_image_with_bad_status(self):
"""Tests proper exception is raised if a bad status is set"""
fixture = self.get_minimal_fixture(id=_gen_uuid(), status='bad status')
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.BAD_REQUEST, body=body,
method='POST', content_type='json')
self.assertIn(b'Invalid image status', res.body)
def test_create_image_with_bad_id(self):
"""Tests proper exception is raised if a bad disk_format is set"""
fixture = self.get_minimal_fixture(id='asdf')
body = jsonutils.dump_as_bytes(dict(image=fixture))
self.get_api_response_ext(http.BAD_REQUEST, content_type='json',
method='POST', body=body)
def test_create_image_with_image_id_in_log(self):
"""Tests correct image id in log message when creating image"""
fixture = self.get_minimal_fixture(
id='0564c64c-3545-4e34-abfb-9d18e5f2f2f9')
self.log_image_id = False
def fake_log_info(msg, image_data):
if ('0564c64c-3545-4e34-abfb-9d18e5f2f2f9' == image_data['id'] and
'Successfully created image' in msg):
self.log_image_id = True
self.stubs.Set(rserver.images.LOG, 'info', fake_log_info)
body = jsonutils.dump_as_bytes(dict(image=fixture))
self.get_api_response_ext(http.OK, content_type='json', method='POST',
body=body)
self.assertTrue(self.log_image_id)
def test_update_image(self):
"""Tests that the /images PUT registry API updates the image"""
fixture = {'name': 'fake public image #2',
'min_disk': 5,
'min_ram': 256,
'disk_format': 'raw'}
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.OK, url='/images/%s' % UUID2,
body=body, method='PUT',
content_type='json')
res_dict = jsonutils.loads(res.body)
self.assertNotEqual(res_dict['image']['created_at'],
res_dict['image']['updated_at'])
for k, v in six.iteritems(fixture):
self.assertEqual(v, res_dict['image'][k])
@mock.patch.object(rserver.images.LOG, 'debug')
def test_update_image_not_log_sensitive_info(self, log_debug):
"""
Tests that there is no any sensitive info of image location
was logged in glance during the image update operation.
"""
def fake_log_debug(fmt_str, image_meta):
self.assertNotIn("'locations'", fmt_str % image_meta)
fixture = {'name': 'fake public image #2',
'min_disk': 5,
'min_ram': 256,
'disk_format': 'raw',
'location': 'fake://image'}
body = jsonutils.dump_as_bytes(dict(image=fixture))
log_debug.side_effect = fake_log_debug
res = self.get_api_response_ext(http.OK, url='/images/%s' % UUID2,
body=body, method='PUT',
content_type='json')
res_dict = jsonutils.loads(res.body)
self.assertNotEqual(res_dict['image']['created_at'],
res_dict['image']['updated_at'])
for k, v in six.iteritems(fixture):
self.assertEqual(v, res_dict['image'][k])
def test_update_image_not_existing(self):
"""
Tests proper exception is raised if attempt to update
non-existing image
"""
fixture = {'status': 'killed'}
body = jsonutils.dump_as_bytes(dict(image=fixture))
self.get_api_response_ext(http.NOT_FOUND,
url='/images/%s' % _gen_uuid(),
method='PUT', body=body, content_type='json')
def test_update_image_with_bad_status(self):
"""Tests that exception raised trying to set a bad status"""
fixture = {'status': 'invalid'}
body = jsonutils.dump_as_bytes(dict(image=fixture))
res = self.get_api_response_ext(http.BAD_REQUEST, method='PUT',
body=body,
url='/images/%s' % UUID2,
content_type='json')
self.assertIn(b'Invalid image status', res.body)
def test_update_private_image_no_admin(self):
"""
Tests proper exception is raised if attempt to update
private image with non admin user, that not belongs to it
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, is_public=False,
protected=True, owner='test user')
db_api.image_create(self.context, extra_fixture)
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
body = jsonutils.dump_as_bytes(dict(image=extra_fixture))
self.get_api_response_ext(http.NOT_FOUND, body=body, api=api,
url='/images/%s' % UUID8, method='PUT',
content_type='json')
def test_delete_image(self):
"""Tests that the /images DELETE registry API deletes the image"""
# Grab the original number of images
res = self.get_api_response_ext(http.OK)
res_dict = jsonutils.loads(res.body)
orig_num_images = len(res_dict['images'])
# Delete image #2
self.get_api_response_ext(http.OK, url='/images/%s' % UUID2,
method='DELETE')
# Verify one less image
res = self.get_api_response_ext(http.OK)
res_dict = jsonutils.loads(res.body)
new_num_images = len(res_dict['images'])
self.assertEqual(orig_num_images - 1, new_num_images)
def test_delete_image_response(self):
"""Tests that the registry API delete returns the image metadata"""
image = self.FIXTURES[0]
res = self.get_api_response_ext(http.OK,
url='/images/%s' % image['id'],
method='DELETE')
deleted_image = jsonutils.loads(res.body)['image']
self.assertEqual(image['id'], deleted_image['id'])
self.assertTrue(deleted_image['deleted'])
self.assertTrue(deleted_image['deleted_at'])
def test_delete_image_not_existing(self):
"""
Tests proper exception is raised if attempt to delete
non-existing image
"""
self.get_api_response_ext(http.NOT_FOUND,
url='/images/%s' % _gen_uuid(),
method='DELETE')
def test_delete_public_image_no_admin(self):
"""
Tests proper exception is raised if attempt to delete
public image with non admin user
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=True,
owner='test user')
db_api.image_create(self.context, extra_fixture)
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
self.get_api_response_ext(http.FORBIDDEN, url='/images/%s' % UUID8,
method='DELETE', api=api)
def test_delete_private_image_no_admin(self):
"""
Tests proper exception is raised if attempt to delete
private image with non admin user, that not belongs to it
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, is_public=False, size=19,
protected=True, owner='test user')
db_api.image_create(self.context, extra_fixture)
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
self.get_api_response_ext(http.NOT_FOUND, url='/images/%s' % UUID8,
method='DELETE', api=api)
def test_get_image_members(self):
"""
Tests members listing for existing images
"""
res = self.get_api_response_ext(http.OK,
url='/images/%s/members' % UUID2,
method='GET')
memb_list = jsonutils.loads(res.body)
num_members = len(memb_list['members'])
self.assertEqual(0, num_members)
def test_get_image_members_not_existing(self):
"""
Tests proper exception is raised if attempt to get members of
non-existing image
"""
self.get_api_response_ext(http.NOT_FOUND, method='GET',
url='/images/%s/members' % _gen_uuid())
def test_get_image_members_forbidden(self):
"""
Tests proper exception is raised if attempt to get members of
non-existing image
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, is_public=False, size=19,
protected=True, owner='test user')
db_api.image_create(self.context, extra_fixture)
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
self.get_api_response_ext(http.NOT_FOUND,
url='/images/%s/members' % UUID8,
method='GET', api=api)
def test_get_member_images(self):
"""
Tests image listing for members
"""
res = self.get_api_response_ext(http.OK,
url='/shared-images/pattieblack',
method='GET')
memb_list = jsonutils.loads(res.body)
num_members = len(memb_list['shared_images'])
self.assertEqual(0, num_members)
def test_replace_members(self):
"""
Tests replacing image members raises right exception
"""
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=False)
fixture = dict(member_id='pattieblack')
body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
self.get_api_response_ext(http.UNAUTHORIZED, method='PUT', body=body,
url='/images/%s/members' % UUID2,
content_type='json')
def test_update_all_image_members_non_existing_image_id(self):
"""
Test update image members raises right exception
"""
# Update all image members
fixture = dict(member_id='test1')
req = webob.Request.blank('/images/%s/members' % _gen_uuid())
req.method = 'PUT'
self.context.tenant = 'test2'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http.NOT_FOUND, res.status_int)
def test_update_all_image_members_invalid_membership_association(self):
"""
Test update image members raises right exception
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
# Add several members to image
req = webob.Request.blank('/images/%s/members/test1' % UUID8)
req.method = 'PUT'
res = req.get_response(self.api)
# Get all image members:
res = self.get_api_response_ext(http.OK,
url='/images/%s/members' % UUID8,
method='GET')
memb_list = jsonutils.loads(res.body)
num_members = len(memb_list['members'])
self.assertEqual(1, num_members)
fixture = dict(member_id='test1')
body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
self.get_api_response_ext(http.BAD_REQUEST,
url='/images/%s/members' % UUID8,
method='PUT', body=body,
content_type='json')
def test_update_all_image_members_non_shared_image_forbidden(self):
"""
Test update image members raises right exception
"""
test_rserv = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_rserv, is_admin=False)
UUID9 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID9, size=19, protected=False)
db_api.image_create(self.context, extra_fixture)
fixture = dict(member_id='test1')
req = webob.Request.blank('/images/%s/members' % UUID9)
req.headers['X-Auth-Token'] = 'test1:test1:'
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
res = req.get_response(api)
self.assertEqual(http.FORBIDDEN, res.status_int)
def test_update_all_image_members(self):
"""
Test update non existing image members
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
# Add several members to image
req = webob.Request.blank('/images/%s/members/test1' % UUID8)
req.method = 'PUT'
req.get_response(self.api)
fixture = [dict(member_id='test2', can_share=True)]
body = jsonutils.dump_as_bytes(dict(memberships=fixture))
self.get_api_response_ext(http.NO_CONTENT,
url='/images/%s/members' % UUID8,
method='PUT', body=body,
content_type='json')
def test_update_all_image_members_bad_request(self):
"""
Test that right exception is raises
in case if wrong memberships association is supplied
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
# Add several members to image
req = webob.Request.blank('/images/%s/members/test1' % UUID8)
req.method = 'PUT'
req.get_response(self.api)
fixture = dict(member_id='test3')
body = jsonutils.dump_as_bytes(dict(memberships=fixture))
self.get_api_response_ext(http.BAD_REQUEST,
url='/images/%s/members' % UUID8,
method='PUT', body=body,
content_type='json')
def test_update_all_image_existing_members(self):
"""
Test update existing image members
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
# Add several members to image
req = webob.Request.blank('/images/%s/members/test1' % UUID8)
req.method = 'PUT'
req.get_response(self.api)
fixture = [dict(member_id='test1', can_share=False)]
body = jsonutils.dump_as_bytes(dict(memberships=fixture))
self.get_api_response_ext(http.NO_CONTENT,
url='/images/%s/members' % UUID8,
method='PUT', body=body,
content_type='json')
def test_update_all_image_existing_deleted_members(self):
"""
Test update existing image members
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
# Add a new member to an image
req = webob.Request.blank('/images/%s/members/test1' % UUID8)
req.method = 'PUT'
req.get_response(self.api)
# Delete the existing member
self.get_api_response_ext(http.NO_CONTENT, method='DELETE',
url='/images/%s/members/test1' % UUID8)
# Re-add the deleted member by replacing membership list
fixture = [dict(member_id='test1', can_share=False)]
body = jsonutils.dump_as_bytes(dict(memberships=fixture))
self.get_api_response_ext(http.NO_CONTENT,
url='/images/%s/members' % UUID8,
method='PUT', body=body,
content_type='json')
memb_list = db_api.image_member_find(self.context, image_id=UUID8)
self.assertEqual(1, len(memb_list))
def test_add_member(self):
"""
Tests adding image members raises right exception
"""
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=False)
self.get_api_response_ext(http.UNAUTHORIZED, method='PUT',
url=('/images/%s/members/pattieblack' %
UUID2))
def test_add_member_to_image_positive(self):
"""
Test check that member can be successfully added
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
fixture = dict(can_share=True)
test_uri = '/images/%s/members/test_add_member_positive'
body = jsonutils.dump_as_bytes(dict(member=fixture))
self.get_api_response_ext(http.NO_CONTENT, url=test_uri % UUID8,
method='PUT', body=body,
content_type='json')
def test_add_member_to_non_exist_image(self):
"""
Test check that member can't be added for
non exist image
"""
fixture = dict(can_share=True)
test_uri = '/images/%s/members/test_add_member_positive'
body = jsonutils.dump_as_bytes(dict(member=fixture))
self.get_api_response_ext(http.NOT_FOUND, url=test_uri % _gen_uuid(),
method='PUT', body=body,
content_type='json')
def test_add_image_member_non_shared_image_forbidden(self):
"""
Test update image members raises right exception
"""
test_rserver_api = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(
test_rserver_api, is_admin=False)
UUID9 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID9, size=19, protected=False)
db_api.image_create(self.context, extra_fixture)
fixture = dict(can_share=True)
test_uri = '/images/%s/members/test_add_member_to_non_share_image'
req = webob.Request.blank(test_uri % UUID9)
req.headers['X-Auth-Token'] = 'test1:test1:'
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(member=fixture))
res = req.get_response(api)
self.assertEqual(http.FORBIDDEN, res.status_int)
def test_add_member_to_image_bad_request(self):
"""
Test check right status code is returned
"""
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
fixture = [dict(can_share=True)]
test_uri = '/images/%s/members/test_add_member_bad_request'
body = jsonutils.dump_as_bytes(dict(member=fixture))
self.get_api_response_ext(http.BAD_REQUEST, url=test_uri % UUID8,
method='PUT', body=body,
content_type='json')
def test_delete_member(self):
"""
Tests deleting image members raises right exception
"""
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=False)
self.get_api_response_ext(http.UNAUTHORIZED, method='DELETE',
url=('/images/%s/members/pattieblack' %
UUID2))
def test_delete_member_invalid(self):
"""
Tests deleting a invalid/non existing member raises right exception
"""
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=True)
res = self.get_api_response_ext(
http.NOT_FOUND, method='DELETE',
url=('/images/%s/members/pattieblack' % UUID2))
self.assertIn(b'Membership could not be found', res.body)
def test_delete_member_from_non_exist_image(self):
"""
Tests deleting image members raises right exception
"""
test_rserver_api = rserver.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_rserver_api, is_admin=True)
test_uri = '/images/%s/members/pattieblack'
self.get_api_response_ext(http.NOT_FOUND, method='DELETE',
url=test_uri % _gen_uuid())
def test_delete_image_member_non_shared_image_forbidden(self):
"""
Test delete image members raises right exception
"""
test_rserver_api = rserver.API(self.mapper)
api = test_utils.FakeAuthMiddleware(
test_rserver_api, is_admin=False)
UUID9 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID9, size=19, protected=False)
db_api.image_create(self.context, extra_fixture)
test_uri = '/images/%s/members/test_add_member_to_non_share_image'
req = webob.Request.blank(test_uri % UUID9)
req.headers['X-Auth-Token'] = 'test1:test1:'
req.method = 'DELETE'
req.content_type = 'application/json'
res = req.get_response(api)
self.assertEqual(http.FORBIDDEN, res.status_int)
def test_add_member_delete_create(self):
"""
Test check that the same member can be successfully added after delete
it, and the same record will be reused for the same membership.
"""
# add a member
UUID8 = _gen_uuid()
extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False,
owner='test user')
db_api.image_create(self.context, extra_fixture)
fixture = dict(can_share=True)
test_uri = '/images/%s/members/test_add_member_delete_create'
body = jsonutils.dump_as_bytes(dict(member=fixture))
self.get_api_response_ext(http.NO_CONTENT, url=test_uri % UUID8,
method='PUT', body=body,
content_type='json')
memb_list = db_api.image_member_find(self.context, image_id=UUID8)
self.assertEqual(1, len(memb_list))
memb_list2 = db_api.image_member_find(self.context,
image_id=UUID8,
include_deleted=True)
self.assertEqual(1, len(memb_list2))
# delete the member
self.get_api_response_ext(http.NO_CONTENT, method='DELETE',
url=test_uri % UUID8)
memb_list = db_api.image_member_find(self.context, image_id=UUID8)
self.assertEqual(0, len(memb_list))
memb_list2 = db_api.image_member_find(self.context,
image_id=UUID8,
include_deleted=True)
self.assertEqual(1, len(memb_list2))
# create it again
self.get_api_response_ext(http.NO_CONTENT, url=test_uri % UUID8,
method='PUT', body=body,
content_type='json')
memb_list = db_api.image_member_find(self.context, image_id=UUID8)
self.assertEqual(1, len(memb_list))
memb_list2 = db_api.image_member_find(self.context,
image_id=UUID8,
include_deleted=True)
self.assertEqual(1, len(memb_list2))
def test_get_on_image_member(self):
"""
Test GET on image members raises 404 and produces correct Allow headers
"""
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=False)
uri = '/images/%s/members/123' % UUID1
req = webob.Request.blank(uri)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
self.assertIn(('Allow', 'PUT, DELETE'), res.headerlist)
def test_get_images_bad_urls(self):
"""Check that routes collections are not on (LP bug 1185828)"""
self.get_api_response_ext(http.NOT_FOUND, url='/images/detail.xxx')
self.get_api_response_ext(http.NOT_FOUND, url='/images.xxx')
self.get_api_response_ext(http.NOT_FOUND, url='/images/new')
self.get_api_response_ext(http.OK, url='/images/%s/members' % UUID1)
self.get_api_response_ext(http.NOT_FOUND,
url='/images/%s/members.xxx' % UUID1)
class TestRegistryAPILocations(base.IsolatedUnitTest,
test_utils.RegistryAPIMixIn):
def setUp(self):
"""Establish a clean test environment"""
super(TestRegistryAPILocations, self).setUp()
self.mapper = routes.Mapper()
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=True)
def _get_extra_fixture(id, name, **kwargs):
return self.get_extra_fixture(
id, name,
locations=[{'url': "file:///%s/%s" % (self.test_dir, id),
'metadata': {}, 'status': 'active'}], **kwargs)
self.FIXTURES = [
_get_extra_fixture(UUID1, 'fake image #1', is_public=False,
disk_format='ami', container_format='ami',
min_disk=0, min_ram=0, owner=123,
size=13, properties={'type': 'kernel'}),
_get_extra_fixture(UUID2, 'fake image #2',
min_disk=5, min_ram=256,
size=19, properties={})]
self.context = context.RequestContext(is_admin=True)
db_api.get_engine()
self.destroy_fixtures()
self.create_fixtures()
def tearDown(self):
"""Clear the test environment"""
super(TestRegistryAPILocations, self).tearDown()
self.destroy_fixtures()
def test_show_from_locations(self):
req = webob.Request.blank('/images/%s' % UUID1)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
image = res_dict['image']
self.assertIn('id', image['location_data'][0])
image['location_data'][0].pop('id')
self.assertEqual(self.FIXTURES[0]['locations'][0],
image['location_data'][0])
self.assertEqual(self.FIXTURES[0]['locations'][0]['url'],
image['location_data'][0]['url'])
self.assertEqual(self.FIXTURES[0]['locations'][0]['metadata'],
image['location_data'][0]['metadata'])
def test_show_from_location_data(self):
req = webob.Request.blank('/images/%s' % UUID2)
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
image = res_dict['image']
self.assertIn('id', image['location_data'][0])
image['location_data'][0].pop('id')
self.assertEqual(self.FIXTURES[1]['locations'][0],
image['location_data'][0])
self.assertEqual(self.FIXTURES[1]['locations'][0]['url'],
image['location_data'][0]['url'])
self.assertEqual(self.FIXTURES[1]['locations'][0]['metadata'],
image['location_data'][0]['metadata'])
def test_create_from_location_data_with_encryption(self):
encryption_key = '1234567890123456'
location_url1 = "file:///%s/%s" % (self.test_dir, _gen_uuid())
location_url2 = "file:///%s/%s" % (self.test_dir, _gen_uuid())
encrypted_location_url1 = crypt.urlsafe_encrypt(encryption_key,
location_url1, 64)
encrypted_location_url2 = crypt.urlsafe_encrypt(encryption_key,
location_url2, 64)
fixture = {'name': 'fake image #3',
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'is_public': True,
'checksum': None,
'min_disk': 5,
'min_ram': 256,
'size': 19,
'location': encrypted_location_url1,
'location_data': [{'url': encrypted_location_url1,
'metadata': {'key': 'value'},
'status': 'active'},
{'url': encrypted_location_url2,
'metadata': {'key': 'value'},
'status': 'active'}]}
self.config(metadata_encryption_key=encryption_key)
req = webob.Request.blank('/images')
req.method = 'POST'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image=fixture))
res = req.get_response(self.api)
self.assertEqual(http.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
image = res_dict['image']
# NOTE(zhiyan) _normalize_image_location_for_db() function will
# not re-encrypted the url within location.
self.assertEqual(fixture['location'], image['location'])
self.assertEqual(2, len(image['location_data']))
self.assertEqual(fixture['location_data'][0]['url'],
image['location_data'][0]['url'])
self.assertEqual(fixture['location_data'][0]['metadata'],
image['location_data'][0]['metadata'])
self.assertEqual(fixture['location_data'][1]['url'],
image['location_data'][1]['url'])
self.assertEqual(fixture['location_data'][1]['metadata'],
image['location_data'][1]['metadata'])
image_entry = db_api.image_get(self.context, image['id'])
self.assertEqual(encrypted_location_url1,
image_entry['locations'][0]['url'])
self.assertEqual(encrypted_location_url2,
image_entry['locations'][1]['url'])
decrypted_location_url1 = crypt.urlsafe_decrypt(
encryption_key, image_entry['locations'][0]['url'])
decrypted_location_url2 = crypt.urlsafe_decrypt(
encryption_key, image_entry['locations'][1]['url'])
self.assertEqual(location_url1, decrypted_location_url1)
self.assertEqual(location_url2, decrypted_location_url2)
class TestSharability(test_utils.BaseTestCase):
def setUp(self):
super(TestSharability, self).setUp()
self.setup_db()
self.controller = glance.registry.api.v1.members.Controller()
def setup_db(self):
db_api.get_engine()
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def test_is_image_sharable_as_admin(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=True, user=TENANT2,
auth_token='user:%s:admin' % TENANT2,
owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need private image and context.owner should not match image
# owner
image = db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
result = self.controller.is_image_sharable(ctxt2, image)
self.assertTrue(result)
def test_is_image_sharable_owner_can_share(self):
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
UUIDX = str(uuid.uuid4())
# We need private image and context.owner should not match image
# owner
image = db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
result = self.controller.is_image_sharable(ctxt1, image)
self.assertTrue(result)
def test_is_image_sharable_non_owner_cannot_share(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=False, user=TENANT2,
auth_token='user:%s:user' % TENANT2,
owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need private image and context.owner should not match image
# owner
image = db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
result = self.controller.is_image_sharable(ctxt2, image)
self.assertFalse(result)
def test_is_image_sharable_non_owner_can_share_as_image_member(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=False, user=TENANT2,
auth_token='user:%s:user' % TENANT2,
owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need private image and context.owner should not match image
# owner
image = db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
membership = {'can_share': True,
'member': TENANT2,
'image_id': UUIDX}
db_api.image_member_create(ctxt1, membership)
result = self.controller.is_image_sharable(ctxt2, image)
self.assertTrue(result)
def test_is_image_sharable_non_owner_as_image_member_without_sharing(self):
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=False, user=TENANT2,
auth_token='user:%s:user' % TENANT2,
owner_is_tenant=False)
UUIDX = str(uuid.uuid4())
# We need private image and context.owner should not match image
# owner
image = db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
membership = {'can_share': False,
'member': TENANT2,
'image_id': UUIDX}
db_api.image_member_create(ctxt1, membership)
result = self.controller.is_image_sharable(ctxt2, image)
self.assertFalse(result)
def test_is_image_sharable_owner_is_none(self):
TENANT1 = str(uuid.uuid4())
ctxt1 = context.RequestContext(is_admin=False, tenant=TENANT1,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
ctxt2 = context.RequestContext(is_admin=False, tenant=None,
auth_token='user:%s:user' % TENANT1,
owner_is_tenant=True)
UUIDX = str(uuid.uuid4())
# We need private image and context.owner should not match image
# owner
image = db_api.image_create(ctxt1, {'id': UUIDX,
'status': 'queued',
'is_public': False,
'owner': TENANT1})
result = self.controller.is_image_sharable(ctxt2, image)
self.assertFalse(result)
glance-16.0.1/glance/tests/unit/v1/test_upload_utils.py 0000666 0001750 0001750 00000037547 13267672254 023127 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from contextlib import contextmanager
import glance_store
import mock
from mock import patch
import webob.exc
from glance.api.v1 import upload_utils
from glance.common import exception
from glance.common import store_utils
from glance.common import utils
import glance.registry.client.v1.api as registry
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
class TestUploadUtils(base.StoreClearingUnitTest):
def setUp(self):
super(TestUploadUtils, self).setUp()
self.config(debug=True)
def test_initiate_delete(self):
req = unit_test_utils.get_fake_request()
location = {"url": "file://foo/bar",
"metadata": {},
"status": "active"}
id = unit_test_utils.UUID1
with patch.object(store_utils,
"safe_delete_from_backend") as mock_store_utils:
upload_utils.initiate_deletion(req, location, id)
mock_store_utils.assert_called_once_with(req.context,
id,
location)
def test_initiate_delete_with_delayed_delete(self):
self.config(delayed_delete=True)
req = unit_test_utils.get_fake_request()
location = {"url": "file://foo/bar",
"metadata": {},
"status": "active"}
id = unit_test_utils.UUID1
with patch.object(store_utils, "schedule_delayed_delete_from_backend",
return_value=True) as mock_store_utils:
upload_utils.initiate_deletion(req, location, id)
mock_store_utils.assert_called_once_with(req.context,
id,
location)
def test_safe_kill(self):
req = unit_test_utils.get_fake_request()
id = unit_test_utils.UUID1
with patch.object(registry, "update_image_metadata") as mock_registry:
upload_utils.safe_kill(req, id, 'saving')
mock_registry.assert_called_once_with(req.context, id,
{'status': 'killed'},
from_state='saving')
def test_safe_kill_with_error(self):
req = unit_test_utils.get_fake_request()
id = unit_test_utils.UUID1
with patch.object(registry, "update_image_metadata",
side_effect=Exception()) as mock_registry:
upload_utils.safe_kill(req, id, 'saving')
mock_registry.assert_called_once_with(req.context, id,
{'status': 'killed'},
from_state='saving')
@contextmanager
def _get_store_and_notifier(self, image_size=10, ext_update_data=None,
ret_checksum="checksum", exc_class=None):
location = "file://foo/bar"
checksum = "checksum"
size = 10
update_data = {'checksum': checksum}
if ext_update_data is not None:
update_data.update(ext_update_data)
image_meta = {'id': unit_test_utils.UUID1,
'size': image_size}
image_data = "blah"
store = mock.MagicMock()
notifier = mock.MagicMock()
if exc_class is not None:
store.add.side_effect = exc_class
else:
store.add.return_value = (location, size, ret_checksum, {})
yield (location, checksum, image_meta, image_data, store, notifier,
update_data)
def test_upload_data_to_store(self):
# 'user_storage_quota' is not set
def store_add(image_id, data, size, **kwargs):
# Check if 'data' is instance of 'CooperativeReader' when
# 'user_storage_quota' is disabled.
self.assertIsInstance(data, utils.CooperativeReader)
return location, 10, "checksum", {}
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
ext_update_data={'size': 10},
exc_class=store_add) as (location, checksum, image_meta,
image_data, store, notifier,
update_data):
ret = image_meta.update(update_data)
with patch.object(registry, 'update_image_metadata',
return_value=ret) as mock_update_image_metadata:
actual_meta, location_data = upload_utils.upload_data_to_store(
req, image_meta, image_data, store, notifier)
self.assertEqual(location, location_data['url'])
self.assertEqual(image_meta.update(update_data), actual_meta)
mock_update_image_metadata.assert_called_once_with(
req.context, image_meta['id'], update_data,
from_state='saving')
def test_upload_data_to_store_user_storage_quota_enabled(self):
# Enable user_storage_quota
self.config(user_storage_quota='100B')
def store_add(image_id, data, size, **kwargs):
# Check if 'data' is instance of 'LimitingReader' when
# 'user_storage_quota' is enabled.
self.assertIsInstance(data, utils.LimitingReader)
return location, 10, "checksum", {}
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
ext_update_data={'size': 10},
exc_class=store_add) as (location, checksum, image_meta,
image_data, store, notifier,
update_data):
ret = image_meta.update(update_data)
# mock 'check_quota'
mock_check_quota = patch('glance.api.common.check_quota',
return_value=100)
mock_check_quota.start()
self.addCleanup(mock_check_quota.stop)
with patch.object(registry, 'update_image_metadata',
return_value=ret) as mock_update_image_metadata:
actual_meta, location_data = upload_utils.upload_data_to_store(
req, image_meta, image_data, store, notifier)
self.assertEqual(location, location_data['url'])
self.assertEqual(image_meta.update(update_data), actual_meta)
mock_update_image_metadata.assert_called_once_with(
req.context, image_meta['id'], update_data,
from_state='saving')
# 'check_quota' is called two times
check_quota_call_count = (
mock_check_quota.target.check_quota.call_count)
self.assertEqual(2, check_quota_call_count)
def test_upload_data_to_store_mismatch_size(self):
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
image_size=11) as (location, checksum, image_meta, image_data,
store, notifier, update_data):
ret = image_meta.update(update_data)
with patch.object(registry, 'update_image_metadata',
return_value=ret) as mock_update_image_metadata:
self.assertRaises(webob.exc.HTTPBadRequest,
upload_utils.upload_data_to_store,
req, image_meta, image_data, store,
notifier)
mock_update_image_metadata.assert_called_with(
req.context, image_meta['id'], {'status': 'killed'},
from_state='saving')
def test_upload_data_to_store_mismatch_checksum(self):
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
ret_checksum='fake') as (location, checksum, image_meta,
image_data, store, notifier, update_data):
ret = image_meta.update(update_data)
with patch.object(registry, "update_image_metadata",
return_value=ret) as mock_update_image_metadata:
self.assertRaises(webob.exc.HTTPBadRequest,
upload_utils.upload_data_to_store,
req, image_meta, image_data, store,
notifier)
mock_update_image_metadata.assert_called_with(
req.context, image_meta['id'], {'status': 'killed'},
from_state='saving')
def _test_upload_data_to_store_exception(self, exc_class, expected_class):
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
exc_class=exc_class) as (location, checksum, image_meta,
image_data, store, notifier, update_data):
with patch.object(upload_utils, 'safe_kill') as mock_safe_kill:
self.assertRaises(expected_class,
upload_utils.upload_data_to_store,
req, image_meta, image_data, store, notifier)
mock_safe_kill.assert_called_once_with(
req, image_meta['id'], 'saving')
def _test_upload_data_to_store_exception_with_notify(self,
exc_class,
expected_class,
image_killed=True):
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
exc_class=exc_class) as (location, checksum, image_meta,
image_data, store, notifier, update_data):
with patch.object(upload_utils, 'safe_kill') as mock_safe_kill:
self.assertRaises(expected_class,
upload_utils.upload_data_to_store,
req, image_meta, image_data, store,
notifier)
if image_killed:
mock_safe_kill.assert_called_with(req, image_meta['id'],
'saving')
def test_upload_data_to_store_raises_store_disabled(self):
"""Test StoreDisabled exception is raised while uploading data"""
self._test_upload_data_to_store_exception_with_notify(
glance_store.StoreAddDisabled,
webob.exc.HTTPGone,
image_killed=True)
def test_upload_data_to_store_duplicate(self):
"""See note in glance.api.v1.upload_utils on why we don't want image to
be deleted in this case.
"""
self._test_upload_data_to_store_exception_with_notify(
exception.Duplicate,
webob.exc.HTTPConflict,
image_killed=False)
def test_upload_data_to_store_forbidden(self):
self._test_upload_data_to_store_exception_with_notify(
exception.Forbidden,
webob.exc.HTTPForbidden)
def test_upload_data_to_store_storage_full(self):
self._test_upload_data_to_store_exception_with_notify(
glance_store.StorageFull,
webob.exc.HTTPRequestEntityTooLarge)
def test_upload_data_to_store_storage_write_denied(self):
self._test_upload_data_to_store_exception_with_notify(
glance_store.StorageWriteDenied,
webob.exc.HTTPServiceUnavailable)
def test_upload_data_to_store_size_limit_exceeded(self):
self._test_upload_data_to_store_exception_with_notify(
exception.ImageSizeLimitExceeded,
webob.exc.HTTPRequestEntityTooLarge)
def test_upload_data_to_store_http_error(self):
self._test_upload_data_to_store_exception_with_notify(
webob.exc.HTTPError,
webob.exc.HTTPError)
def test_upload_data_to_store_client_disconnect(self):
self._test_upload_data_to_store_exception(
ValueError,
webob.exc.HTTPBadRequest)
def test_upload_data_to_store_client_disconnect_ioerror(self):
self._test_upload_data_to_store_exception(
IOError,
webob.exc.HTTPBadRequest)
def test_upload_data_to_store_exception(self):
self._test_upload_data_to_store_exception_with_notify(
Exception,
webob.exc.HTTPInternalServerError)
def test_upload_data_to_store_not_found_after_upload(self):
req = unit_test_utils.get_fake_request()
with self._get_store_and_notifier(
ext_update_data={'size': 10}) as (location, checksum, image_meta,
image_data, store, notifier,
update_data):
exc = exception.ImageNotFound
with patch.object(registry, 'update_image_metadata',
side_effect=exc) as mock_update_image_metadata:
with patch.object(upload_utils,
"initiate_deletion") as mock_initiate_del:
with patch.object(upload_utils,
"safe_kill") as mock_safe_kill:
self.assertRaises(webob.exc.HTTPPreconditionFailed,
upload_utils.upload_data_to_store,
req, image_meta, image_data, store,
notifier)
mock_update_image_metadata.assert_called_once_with(
req.context, image_meta['id'], update_data,
from_state='saving')
mock_initiate_del.assert_called_once_with(
req, {'url': location, 'status': 'active',
'metadata': {}}, image_meta['id'])
mock_safe_kill.assert_called_once_with(
req, image_meta['id'], 'saving')
@mock.patch.object(registry, 'update_image_metadata',
side_effect=exception.NotAuthenticated)
@mock.patch.object(upload_utils, 'initiate_deletion')
def test_activate_image_with_expired_token(
self, mocked_delete, mocked_update):
"""Test token expiration during image upload.
If users token expired before image was uploaded then if auth error
was caught from registry during changing image status from 'saving'
to 'active' then it's required to delete all image data.
"""
context = mock.Mock()
req = mock.Mock()
req.context = context
with self._get_store_and_notifier() as (location, checksum, image_meta,
image_data, store, notifier,
update_data):
self.assertRaises(webob.exc.HTTPUnauthorized,
upload_utils.upload_data_to_store,
req, image_meta, image_data, store, notifier)
self.assertEqual(2, mocked_update.call_count)
mocked_delete.assert_called_once_with(
req,
{'url': 'file://foo/bar', 'status': 'active', 'metadata': {}},
'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d')
glance-16.0.1/glance/tests/unit/v1/test_api.py 0000666 0001750 0001750 00000624325 13267672254 021170 0 ustar zuul zuul 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import datetime
import hashlib
import os
import signal
import uuid
import glance_store as store
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
import routes
import six
from six.moves import http_client
import webob
import glance.api
import glance.api.common
from glance.api.v1 import router
from glance.api.v1 import upload_utils
import glance.common.config
from glance.common import exception
from glance.common import timeutils
import glance.context
from glance.db.sqlalchemy import api as db_api
from glance.db.sqlalchemy import models as db_models
import glance.registry.client.v1.api as registry
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
from glance.tests import utils as test_utils
CONF = cfg.CONF
_gen_uuid = lambda: str(uuid.uuid4())
UUID1 = _gen_uuid()
UUID2 = _gen_uuid()
UUID3 = _gen_uuid()
class TestGlanceAPI(base.IsolatedUnitTest):
def setUp(self):
"""Establish a clean test environment"""
super(TestGlanceAPI, self).setUp()
self.mapper = routes.Mapper()
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper))
self.FIXTURES = [
{'id': UUID1,
'name': 'fake image #1',
'status': 'active',
'disk_format': 'ami',
'container_format': 'ami',
'is_public': False,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': None,
'size': 13,
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}, 'status': 'active'}],
'properties': {'type': 'kernel'}},
{'id': UUID2,
'name': 'fake image #2',
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'is_public': True,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': 'abc123',
'size': 19,
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID2),
'metadata': {}, 'status': 'active'}],
'properties': {}},
{'id': UUID3,
'name': 'fake image #3',
'status': 'deactivated',
'disk_format': 'ami',
'container_format': 'ami',
'is_public': False,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': '13',
'size': 13,
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}, 'status': 'active'}],
'properties': {}}]
self.context = glance.context.RequestContext(is_admin=True)
db_api.get_engine()
self.destroy_fixtures()
self.addCleanup(self.destroy_fixtures)
self.create_fixtures()
# Used to store/track image status changes for post-analysis
self.image_status = []
self.http_server_pid = None
self.addCleanup(self._cleanup_server)
ret = test_utils.start_http_server("foo_image_id", b"foo_image")
self.http_server_pid, self.http_port = ret
def _cleanup_server(self):
if self.http_server_pid is not None:
os.kill(self.http_server_pid, signal.SIGKILL)
def create_fixtures(self):
for fixture in self.FIXTURES:
db_api.image_create(self.context, fixture)
# We write a fake image file to the filesystem
with open("%s/%s" % (self.test_dir, fixture['id']), 'wb') as image:
image.write(b"chunk00000remainder")
image.flush()
def destroy_fixtures(self):
# Easiest to just drop the models and re-create them...
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def _do_test_defaulted_format(self, format_key, format_value):
fixture_headers = {'x-image-meta-name': 'defaulted',
'x-image-meta-location': 'http://localhost:0/image',
format_key: format_value}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual(format_value, res_body['disk_format'])
self.assertEqual(format_value, res_body['container_format'])
def _http_loc_url(self, path):
return 'http://127.0.0.1:%d%s' % (self.http_port, path)
def test_defaulted_amazon_format(self):
for key in ('x-image-meta-disk-format',
'x-image-meta-container-format'):
for value in ('aki', 'ari', 'ami'):
self._do_test_defaulted_format(key, value)
def test_bad_time_create_minus_int(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-created_at': '-42',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_bad_time_create_string(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-created_at': 'foo',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_bad_time_create_low_year(self):
# 'strftime' only allows values after 1900 in glance v1
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-created_at': '1100',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_bad_time_create_string_in_date(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-created_at': '2012-01-01hey12:32:12',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_bad_min_disk_size_create(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-min-disk': '-42',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid value', res.body)
def test_updating_imageid_after_creation(self):
# Test incorrect/illegal id update
req = webob.Request.blank("/images/%s" % UUID1)
req.method = 'PUT'
req.headers['x-image-meta-id'] = '000000-000-0000-0000-000'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
# Test using id of another image
req = webob.Request.blank("/images/%s" % UUID1)
req.method = 'PUT'
req.headers['x-image-meta-id'] = UUID2
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_bad_min_disk_size_update(self):
fixture_headers = {'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-min-disk'] = '-42'
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid value', res.body)
def test_invalid_min_disk_size_update(self):
fixture_headers = {'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-min-disk'] = str(2 ** 31 + 1)
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_bad_min_ram_size_create(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-min-ram': '-42',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid value', res.body)
def test_bad_min_ram_size_update(self):
fixture_headers = {'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-min-ram'] = '-42'
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid value', res.body)
def test_invalid_min_ram_size_update(self):
fixture_headers = {'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-min-ram'] = str(2 ** 31 + 1)
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_bad_disk_format(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'invalid',
'x-image-meta-container-format': 'ami',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid disk format', res.body)
def test_configured_disk_format_good(self):
self.config(disk_formats=['foo'], group="image_format")
fixture_headers = {
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'foo',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
def test_configured_disk_format_bad(self):
self.config(disk_formats=['foo'], group="image_format")
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'bar',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid disk format', res.body)
def test_configured_container_format_good(self):
self.config(container_formats=['foo'], group="image_format")
fixture_headers = {
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'raw',
'x-image-meta-container-format': 'foo',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
def test_configured_container_format_bad(self):
self.config(container_formats=['foo'], group="image_format")
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'raw',
'x-image-meta-container-format': 'bar',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid container format', res.body)
def test_container_and_disk_amazon_format_differs(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'aki',
'x-image-meta-container-format': 'ami'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
expected = (b"Invalid mix of disk and container formats. "
b"When setting a disk or container format to one of "
b"'aki', 'ari', or 'ami', "
b"the container and disk formats must match.")
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(expected, res.body)
def test_create_with_location_no_container_format(self):
fixture_headers = {
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'vhd',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Container format is not specified', res.body)
def test_create_with_location_no_disk_format(self):
fixture_headers = {
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Disk format is not specified', res.body)
def test_create_with_empty_location(self):
fixture_headers = {
'x-image-meta-location': '',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_create_with_empty_copy_from(self):
fixture_headers = {
'x-glance-api-copy-from': '',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_create_delayed_image_with_no_disk_and_container_formats(self):
fixture_headers = {
'x-image-meta-name': 'delayed',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
def test_create_with_bad_store_name(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-disk-format': 'qcow2',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Required store bad is invalid', res.body)
@mock.patch.object(glance.api.v1.images.Controller, '_external_source')
@mock.patch.object(store, 'get_store_from_location')
def test_create_with_location_get_store_or_400_raises_exception(
self, mock_get_store_from_location, mock_external_source):
location = 'bad+scheme://localhost:0/image.qcow2'
scheme = 'bad+scheme'
fixture_headers = {
'x-image-meta-name': 'bogus',
'x-image-meta-location': location,
'x-image-meta-disk-format': 'qcow2',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
mock_external_source.return_value = location
mock_get_store_from_location.return_value = scheme
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertEqual(1, mock_external_source.call_count)
self.assertEqual(1, mock_get_store_from_location.call_count)
self.assertIn('Store for scheme %s not found' % scheme,
res.body.decode('utf-8'))
def test_create_with_location_unknown_scheme(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'bad+scheme://localhost:0/image.qcow2',
'x-image-meta-disk-format': 'qcow2',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'External sources are not supported', res.body)
def test_create_with_location_bad_store_uri(self):
fixture_headers = {
'x-image-meta-store': 'file',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://',
'x-image-meta-disk-format': 'qcow2',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid location', res.body)
def test_create_image_with_too_many_properties(self):
self.config(image_property_quota=1)
another_request = unit_test_utils.get_fake_request(
path='/images', method='POST')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'x-image-meta-property-x_all_permitted': '1',
'x-image-meta-property-x_all_permitted_foo': '2'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE,
output.status_int)
def test_bad_container_format(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'invalid',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid container format', res.body)
def test_bad_image_size(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': self._http_loc_url('/image.tar.gz'),
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'bare',
}
def exec_bad_size_test(bad_size, expected_substr):
fixture_headers['x-image-meta-size'] = bad_size
req = webob.Request.blank("/images",
method='POST',
headers=fixture_headers)
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(expected_substr, res.body)
expected = b"Cannot convert image size 'invalid' to an integer."
exec_bad_size_test('invalid', expected)
expected = b"Cannot be a negative value."
exec_bad_size_test(-10, expected)
def test_bad_image_name(self):
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'X' * 256,
'x-image-meta-location': self._http_loc_url('/image.tar.gz'),
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_no_location_no_image_as_body(self):
"""Tests creates a queued image for no body and no loc header"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-created_at': '2015-11-20',
'x-image-updated_at': '2015-12-01 12:10:01',
'x-image-deleted_at': '2000'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
# Test that we are able to edit the Location field
# per LP Bug #911599
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-location'] = 'http://localhost:0/images/123'
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mocked_size:
mocked_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_body = jsonutils.loads(res.body)['image']
# Once the location is set, the image should be activated
# see LP Bug #939484
self.assertEqual('active', res_body['status'])
self.assertNotIn('location', res_body) # location never shown
def test_add_image_no_location_no_content_type(self):
"""Tests creates a queued image for no body and no loc header"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
req.body = b"chunk00000remainder"
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_size_header_too_big(self):
"""Tests raises BadRequest for supplied image size that is too big"""
fixture_headers = {'x-image-meta-size': CONF.image_size_cap + 1,
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_size_chunked_data_too_big(self):
self.config(image_size_cap=512)
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'ami',
'x-image-meta-disk_format': 'ami',
'transfer-encoding': 'chunked',
'content-type': 'application/octet-stream',
}
req = webob.Request.blank("/images")
req.method = 'POST'
req.body_file = six.StringIO('X' * (CONF.image_size_cap + 1))
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
def test_add_image_size_data_too_big(self):
self.config(image_size_cap=512)
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'ami',
'x-image-meta-disk_format': 'ami',
'content-type': 'application/octet-stream',
}
req = webob.Request.blank("/images")
req.method = 'POST'
req.body = b'X' * (CONF.image_size_cap + 1)
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_size_header_exceed_quota(self):
quota = 500
self.config(user_storage_quota=str(quota))
fixture_headers = {'x-image-meta-size': quota + 1,
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'bare',
'x-image-meta-disk_format': 'qcow2',
'content-type': 'application/octet-stream',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.body = b'X' * (quota + 1)
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
def test_add_image_size_data_exceed_quota(self):
quota = 500
self.config(user_storage_quota=str(quota))
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'bare',
'x-image-meta-disk_format': 'qcow2',
'content-type': 'application/octet-stream',
}
req = webob.Request.blank("/images")
req.method = 'POST'
req.body = b'X' * (quota + 1)
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
def test_add_image_size_data_exceed_quota_readd(self):
quota = 500
self.config(user_storage_quota=str(quota))
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'bare',
'x-image-meta-disk_format': 'qcow2',
'content-type': 'application/octet-stream',
}
req = webob.Request.blank("/images")
req.method = 'POST'
req.body = b'X' * (quota + 1)
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
used_size = sum([f['size'] for f in self.FIXTURES])
req = webob.Request.blank("/images")
req.method = 'POST'
req.body = b'X' * (quota - used_size)
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
def _add_check_no_url_info(self):
fixture_headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-size': '0',
'x-image-meta-name': 'empty image'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
res_body = jsonutils.loads(res.body)['image']
self.assertNotIn('locations', res_body)
self.assertNotIn('direct_url', res_body)
image_id = res_body['id']
# HEAD empty image
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertNotIn('x-image-meta-locations', res.headers)
self.assertNotIn('x-image-meta-direct_url', res.headers)
def test_add_check_no_url_info_ml(self):
self.config(show_multiple_locations=True)
self._add_check_no_url_info()
def test_add_check_no_url_info_direct_url(self):
self.config(show_image_direct_url=True)
self._add_check_no_url_info()
def test_add_check_no_url_info_both_on(self):
self.config(show_image_direct_url=True)
self.config(show_multiple_locations=True)
self._add_check_no_url_info()
def test_add_check_no_url_info_both_off(self):
self._add_check_no_url_info()
def test_add_image_zero_size(self):
"""Tests creating an active image with explicitly zero size"""
fixture_headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-size': '0',
'x-image-meta-name': 'empty image'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('active', res_body['status'])
image_id = res_body['id']
# GET empty image
req = webob.Request.blank("/images/%s" % image_id)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(0, len(res.body))
def _do_test_add_image_attribute_mismatch(self, attributes):
fixture_headers = {
'x-image-meta-name': 'fake image #3',
}
fixture_headers.update(attributes)
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"XXXX"
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_checksum_mismatch(self):
attributes = {
'x-image-meta-checksum': 'asdf',
}
self._do_test_add_image_attribute_mismatch(attributes)
def test_add_image_size_mismatch(self):
attributes = {
'x-image-meta-size': str(len("XXXX") + 1),
}
self._do_test_add_image_attribute_mismatch(attributes)
def test_add_image_checksum_and_size_mismatch(self):
attributes = {
'x-image-meta-checksum': 'asdf',
'x-image-meta-size': str(len("XXXX") + 1),
}
self._do_test_add_image_attribute_mismatch(attributes)
def test_add_image_bad_store(self):
"""Tests raises BadRequest for invalid store header"""
fixture_headers = {'x-image-meta-store': 'bad',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_basic_file_store(self):
"""Tests to add a basic image in the file store"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
# Test that the Location: header is set to the URI to
# edit the newly-created image, as required by APP.
# See LP Bug #719825
self.assertIn('location', res.headers,
"'location' not in response headers.\n"
"res.headerlist = %r" % res.headerlist)
res_body = jsonutils.loads(res.body)['image']
self.assertIn('/images/%s' % res_body['id'], res.headers['location'])
self.assertEqual('active', res_body['status'])
image_id = res_body['id']
# Test that we are NOT able to edit the Location field
# per LP Bug #911599
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
url = self._http_loc_url('/images/123')
req.headers['x-image-meta-location'] = url
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_image_unauthorized(self):
rules = {"add_image": '!'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_add_publicize_image_unauthorized(self):
rules = {"add_image": '@', "modify_image": '@',
"publicize_image": '!'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-is-public': 'true',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_add_publicize_image_authorized(self):
rules = {"add_image": '@', "modify_image": '@',
"publicize_image": '@', "upload_image": '@'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-is-public': 'true',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
def test_add_copy_from_image_unauthorized(self):
rules = {"add_image": '@', "copy_from": '!'}
self.set_policy_rules(rules)
url = self._http_loc_url('/i.ovf')
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-glance-api-copy-from': url,
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_add_copy_from_upload_image_unauthorized(self):
rules = {"add_image": '@', "copy_from": '@', "upload_image": '!'}
self.set_policy_rules(rules)
url = self._http_loc_url('/i.ovf')
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-glance-api-copy-from': url,
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_add_copy_from_image_authorized_upload_image_authorized(self):
rules = {"add_image": '@', "copy_from": '@', "upload_image": '@'}
self.set_policy_rules(rules)
url = self._http_loc_url('/i.ovf')
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-glance-api-copy-from': url,
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as mock_size:
mock_size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
def test_upload_image_http_nonexistent_location_url(self):
# Ensure HTTP 404 response returned when try to upload
# image from non-existent http location URL.
rules = {"add_image": '@', "copy_from": '@', "upload_image": '@'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-glance-api-copy-from':
self._http_loc_url('/non_existing_image_path'),
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_add_copy_from_with_nonempty_body(self):
"""Tests creates an image from copy-from and nonempty body"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-glance-api-copy-from': 'http://0.0.0.0:1/c.ovf',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.headers['Content-Type'] = 'application/octet-stream'
req.method = 'POST'
req.body = b"chunk00000remainder"
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_location_with_nonempty_body(self):
"""Tests creates an image from location and nonempty body"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-location': 'http://0.0.0.0:1/c.tgz',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.headers['Content-Type'] = 'application/octet-stream'
req.method = 'POST'
req.body = b"chunk00000remainder"
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_location_with_conflict_image_size(self):
"""Tests creates an image from location and conflict image size"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-location': 'http://a/b/c.tar.gz',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F',
'x-image-meta-size': '1'}
req = webob.Request.blank("/images")
req.headers['Content-Type'] = 'application/octet-stream'
req.method = 'POST'
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as size:
size.return_value = 2
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CONFLICT, res.status_int)
def test_add_location_with_invalid_location_on_conflict_image_size(self):
"""Tests creates an image from location and conflict image size"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-location': 'http://0.0.0.0:1/c.tgz',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F',
'x-image-meta-size': '1'}
req = webob.Request.blank("/images")
req.headers['Content-Type'] = 'application/octet-stream'
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_location_with_invalid_location_on_restricted_sources(self):
"""Tests creates an image from location and restricted sources"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-location': 'file:///etc/passwd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.headers['Content-Type'] = 'application/octet-stream'
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-location': 'swift+config://xxx',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
req = webob.Request.blank("/images")
req.headers['Content-Type'] = 'application/octet-stream'
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_create_image_with_nonexistent_location_url(self):
# Ensure HTTP 404 response returned when try to create
# image with non-existent http location URL.
fixture_headers = {
'x-image-meta-name': 'bogus',
'x-image-meta-location':
self._http_loc_url('/non_existing_image_path'),
'x-image-meta-disk-format': 'qcow2',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_add_copy_from_with_location(self):
"""Tests creates an image from copy-from and location"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-glance-api-copy-from': 'http://0.0.0.0:1/c.ovf',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F',
'x-image-meta-location': 'http://0.0.0.0:1/c.tgz'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_copy_from_with_restricted_sources(self):
"""Tests creates an image from copy-from with restricted sources"""
header_template = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #F'}
schemas = ["file:///etc/passwd",
"swift+config:///xxx",
"filesystem:///etc/passwd"]
for schema in schemas:
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(header_template):
req.headers[k] = v
req.headers['x-glance-api-copy-from'] = schema
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_add_copy_from_upload_image_unauthorized_with_body(self):
rules = {"upload_image": '!', "modify_image": '@',
"add_image": '@'}
self.set_policy_rules(rules)
self.config(image_size_cap=512)
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'ami',
'x-image-meta-disk_format': 'ami',
'transfer-encoding': 'chunked',
'content-type': 'application/octet-stream',
}
req = webob.Request.blank("/images")
req.method = 'POST'
req.body_file = six.StringIO('X' * (CONF.image_size_cap))
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_update_data_upload_bad_store_uri(self):
fixture_headers = {'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['x-image-disk-format'] = 'vhd'
req.headers['x-image-container-format'] = 'ovf'
req.headers['x-image-meta-location'] = 'http://'
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
self.assertIn(b'Invalid location', res.body)
def test_update_data_upload_image_unauthorized(self):
rules = {"upload_image": '!', "modify_image": '@',
"add_image": '@'}
self.set_policy_rules(rules)
"""Tests creates a queued image for no body and no loc header"""
self.config(image_size_cap=512)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['transfer-encoding'] = 'chunked'
req.headers['x-image-disk-format'] = 'vhd'
req.headers['x-image-container-format'] = 'ovf'
req.body_file = six.StringIO('X' * (CONF.image_size_cap))
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_update_copy_from_upload_image_unauthorized(self):
rules = {"upload_image": '!', "modify_image": '@',
"add_image": '@', "copy_from": '@'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['x-glance-api-copy-from'] = self._http_loc_url('/i.ovf')
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_update_copy_from_unauthorized(self):
rules = {"upload_image": '@', "modify_image": '@',
"add_image": '@', "copy_from": '!'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['x-glance-api-copy-from'] = self._http_loc_url('/i.ovf')
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def _do_test_post_image_content_missing_format(self, missing):
"""Tests creation of an image with missing format"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
header = 'x-image-meta-' + missing.replace('_', '-')
del fixture_headers[header]
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_post_image_content_missing_disk_format(self):
"""Tests creation of an image with missing disk format"""
self._do_test_post_image_content_missing_format('disk_format')
def test_post_image_content_missing_container_type(self):
"""Tests creation of an image with missing container format"""
self._do_test_post_image_content_missing_format('container_format')
def _do_test_put_image_content_missing_format(self, missing):
"""Tests delayed activation of an image with missing format"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
header = 'x-image-meta-' + missing.replace('_', '-')
del fixture_headers[header]
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
image_id = res_body['id']
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_put_image_content_missing_disk_format(self):
"""Tests delayed activation of image with missing disk format"""
self._do_test_put_image_content_missing_format('disk_format')
def test_put_image_content_missing_container_type(self):
"""Tests delayed activation of image with missing container format"""
self._do_test_put_image_content_missing_format('container_format')
def test_download_deactivated_images(self):
"""Tests exception raised trying to download a deactivated image"""
req = webob.Request.blank("/images/%s" % UUID3)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_update_deleted_image(self):
"""Tests that exception raised trying to update a deleted image"""
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
fixture = {'name': 'test_del_img'}
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
self.assertIn(b'Forbidden to update deleted image', res.body)
def test_delete_deleted_image(self):
"""Tests that exception raised trying to delete a deleted image"""
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# Verify the status is 'deleted'
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual("deleted", res.headers['x-image-meta-status'])
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
msg = "Image %s not found." % UUID2
self.assertIn(msg, res.body.decode())
# Verify the status is still 'deleted'
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual("deleted", res.headers['x-image-meta-status'])
def test_image_status_when_delete_fails(self):
"""
Tests that the image status set to active if deletion of image fails.
"""
fs = store.get_store_from_scheme('file')
with mock.patch.object(fs, 'delete') as mock_fsstore_delete:
mock_fsstore_delete.side_effect = exception.Forbidden()
# trigger the v1 delete api
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
self.assertIn(b'Forbidden to delete image', res.body)
# check image metadata is still there with active state
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual("active", res.headers['x-image-meta-status'])
def test_delete_pending_delete_image(self):
"""
Tests that correct response returned when deleting
a pending_delete image
"""
# First deletion
self.config(delayed_delete=True)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# Verify the status is 'pending_delete'
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual("pending_delete", res.headers['x-image-meta-status'])
# Second deletion
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
self.assertIn(b'Forbidden to delete a pending_delete image', res.body)
# Verify the status is still 'pending_delete'
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual("pending_delete", res.headers['x-image-meta-status'])
def test_upload_to_image_status_saving(self):
"""Test image upload conflict.
If an image is uploaded before an existing upload to the same image
completes, the original upload should succeed and the conflicting
one should fail and any data be deleted.
"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'some-foo-image'}
# create an image but don't upload yet.
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
image_id = res_body['id']
self.assertIn('/images/%s' % image_id, res.headers['location'])
# verify the status is 'queued'
self.assertEqual('queued', res_body['status'])
orig_get_image_metadata = registry.get_image_metadata
orig_image_get = db_api._image_get
orig_image_update = db_api._image_update
orig_initiate_deletion = upload_utils.initiate_deletion
# this will be used to track what is called and their order.
call_sequence = []
# use this to determine if we are within a db session i.e. atomic
# operation, that is setting our active state.
# We want first status check to be 'queued' so we get past the
# first guard.
test_status = {
'activate_session_started': False,
'queued_guard_passed': False
}
state_changes = []
def mock_image_update(context, values, image_id, purge_props=False,
from_state=None):
status = values.get('status')
if status:
state_changes.append(status)
if status == 'active':
# We only expect this state to be entered once.
if test_status['activate_session_started']:
raise Exception("target session already started")
test_status['activate_session_started'] = True
call_sequence.append('update_active')
else:
call_sequence.append('update')
return orig_image_update(context, values, image_id,
purge_props=purge_props,
from_state=from_state)
def mock_image_get(*args, **kwargs):
"""Force status to 'saving' if not within activate db session.
If we are in the activate db session we return 'active' which we
then expect to cause exception.Conflict to be raised since this
indicates that another upload has succeeded.
"""
image = orig_image_get(*args, **kwargs)
if test_status['activate_session_started']:
call_sequence.append('image_get_active')
setattr(image, 'status', 'active')
else:
setattr(image, 'status', 'saving')
return image
def mock_get_image_metadata(*args, **kwargs):
"""Force image status sequence.
"""
call_sequence.append('get_image_meta')
meta = orig_get_image_metadata(*args, **kwargs)
if not test_status['queued_guard_passed']:
meta['status'] = 'queued'
test_status['queued_guard_passed'] = True
return meta
def mock_initiate_deletion(*args, **kwargs):
call_sequence.append('init_del')
orig_initiate_deletion(*args, **kwargs)
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
with mock.patch.object(
upload_utils, 'initiate_deletion') as mock_init_del:
mock_init_del.side_effect = mock_initiate_deletion
with mock.patch.object(
registry, 'get_image_metadata') as mock_get_meta:
mock_get_meta.side_effect = mock_get_image_metadata
with mock.patch.object(db_api, '_image_get') as mock_db_get:
mock_db_get.side_effect = mock_image_get
with mock.patch.object(
db_api, '_image_update') as mock_db_update:
mock_db_update.side_effect = mock_image_update
# Expect a 409 Conflict.
res = req.get_response(self.api)
self.assertEqual(http_client.CONFLICT, res.status_int)
# Check expected call sequence
self.assertEqual(['get_image_meta', 'get_image_meta',
'update', 'update_active',
'image_get_active',
'init_del'],
call_sequence)
self.assertTrue(mock_get_meta.called)
self.assertTrue(mock_db_get.called)
self.assertTrue(mock_db_update.called)
# Ensure cleanup occurred.
self.assertEqual(1, mock_init_del.call_count)
self.assertEqual(['saving', 'active'], state_changes)
def test_register_and_upload(self):
"""
Test that the process of registering an image with
some metadata, then uploading an image file with some
more metadata doesn't mark the original metadata deleted
:see LP Bug#901534
"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-meta-property-key1': 'value1'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertIn('id', res_body)
image_id = res_body['id']
self.assertIn('/images/%s' % image_id, res.headers['location'])
# Verify the status is queued
self.assertIn('status', res_body)
self.assertEqual('queued', res_body['status'])
# Check properties are not deleted
self.assertIn('properties', res_body)
self.assertIn('key1', res_body['properties'])
self.assertEqual('value1', res_body['properties']['key1'])
# Now upload the image file along with some more
# metadata and verify original metadata properties
# are not marked deleted
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['x-image-meta-property-key2'] = 'value2'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# Verify the status is 'queued'
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertIn('x-image-meta-property-key1', res.headers,
"Did not find required property in headers. "
"Got headers: %r" % res.headers)
self.assertEqual("active", res.headers['x-image-meta-status'])
def test_upload_image_raises_store_disabled(self):
"""Test that uploading an image file returns HTTTP 410 response"""
# create image
fs = store.get_store_from_scheme('file')
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-meta-property-key1': 'value1'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertIn('id', res_body)
image_id = res_body['id']
self.assertIn('/images/%s' % image_id, res.headers['location'])
# Verify the status is queued
self.assertIn('status', res_body)
self.assertEqual('queued', res_body['status'])
# Now upload the image file
with mock.patch.object(fs, 'add') as mock_fsstore_add:
mock_fsstore_add.side_effect = store.StoreAddDisabled
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.GONE, res.status_int)
self._verify_image_status(image_id, 'killed')
def _get_image_status(self, image_id):
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'HEAD'
return req.get_response(self.api)
def _verify_image_status(self, image_id, status, check_deleted=False,
use_cached=False):
if not use_cached:
res = self._get_image_status(image_id)
else:
res = self.image_status.pop(0)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(status, res.headers['x-image-meta-status'])
self.assertEqual(str(check_deleted),
res.headers['x-image-meta-deleted'])
def _upload_safe_kill_common(self, mocks):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-meta-property-key1': 'value1'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertIn('id', res_body)
self.image_id = res_body['id']
self.assertIn('/images/%s' %
self.image_id, res.headers['location'])
# Verify the status is 'queued'
self.assertEqual('queued', res_body['status'])
for m in mocks:
m['mock'].side_effect = m['side_effect']
# Now upload the image file along with some more metadata and
# verify original metadata properties are not marked deleted
req = webob.Request.blank("/images/%s" % self.image_id)
req.method = 'PUT'
req.headers['Content-Type'] = 'application/octet-stream'
req.headers['x-image-meta-property-key2'] = 'value2'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
# We expect 500 since an exception occurred during upload.
self.assertEqual(http_client.INTERNAL_SERVER_ERROR, res.status_int)
@mock.patch('glance_store.store_add_to_backend')
def test_upload_safe_kill(self, mock_store_add_to_backend):
def mock_store_add_to_backend_w_exception(*args, **kwargs):
"""Trigger mid-upload failure by raising an exception."""
self.image_status.append(self._get_image_status(self.image_id))
# Raise an exception to emulate failed upload.
raise Exception("== UNIT TEST UPLOAD EXCEPTION ==")
mocks = [{'mock': mock_store_add_to_backend,
'side_effect': mock_store_add_to_backend_w_exception}]
self._upload_safe_kill_common(mocks)
# Check we went from 'saving' -> 'killed'
self._verify_image_status(self.image_id, 'saving', use_cached=True)
self._verify_image_status(self.image_id, 'killed')
self.assertEqual(1, mock_store_add_to_backend.call_count)
@mock.patch('glance_store.store_add_to_backend')
def test_upload_safe_kill_deleted(self, mock_store_add_to_backend):
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router_api,
is_admin=True)
def mock_store_add_to_backend_w_exception(*args, **kwargs):
"""We now delete the image, assert status is 'deleted' then
raise an exception to emulate a failed upload. This will be caught
by upload_data_to_store() which will then try to set status to
'killed' which will be ignored since the image has been deleted.
"""
# expect 'saving'
self.image_status.append(self._get_image_status(self.image_id))
req = webob.Request.blank("/images/%s" % self.image_id)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# expect 'deleted'
self.image_status.append(self._get_image_status(self.image_id))
# Raise an exception to make the upload fail.
raise Exception("== UNIT TEST UPLOAD EXCEPTION ==")
mocks = [{'mock': mock_store_add_to_backend,
'side_effect': mock_store_add_to_backend_w_exception}]
self._upload_safe_kill_common(mocks)
# Check we went from 'saving' -> 'deleted' -> 'deleted'
self._verify_image_status(self.image_id, 'saving', check_deleted=False,
use_cached=True)
self._verify_image_status(self.image_id, 'deleted', check_deleted=True,
use_cached=True)
self._verify_image_status(self.image_id, 'deleted', check_deleted=True)
self.assertEqual(1, mock_store_add_to_backend.call_count)
def _check_delete_during_image_upload(self, is_admin=False):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-meta-property-key1': 'value1'}
req = unit_test_utils.get_fake_request(path="/images",
is_admin=is_admin)
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertIn('id', res_body)
image_id = res_body['id']
self.assertIn('/images/%s' % image_id, res.headers['location'])
# Verify the status is 'queued'
self.assertEqual('queued', res_body['status'])
called = {'initiate_deletion': False}
def mock_initiate_deletion(*args, **kwargs):
called['initiate_deletion'] = True
self.stubs.Set(glance.api.v1.upload_utils, 'initiate_deletion',
mock_initiate_deletion)
orig_update_image_metadata = registry.update_image_metadata
data = b"somedata"
def mock_update_image_metadata(*args, **kwargs):
if args[2].get('size') == len(data):
path = "/images/%s" % image_id
req = unit_test_utils.get_fake_request(path=path,
method='DELETE',
is_admin=is_admin)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.stubs.Set(registry, 'update_image_metadata',
orig_update_image_metadata)
return orig_update_image_metadata(*args, **kwargs)
self.stubs.Set(registry, 'update_image_metadata',
mock_update_image_metadata)
req = unit_test_utils.get_fake_request(path="/images/%s" % image_id,
method='PUT')
req.headers['Content-Type'] = 'application/octet-stream'
req.body = data
res = req.get_response(self.api)
self.assertEqual(http_client.PRECONDITION_FAILED, res.status_int)
self.assertFalse(res.location)
self.assertTrue(called['initiate_deletion'])
req = unit_test_utils.get_fake_request(path="/images/%s" % image_id,
method='HEAD',
is_admin=True)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual('True', res.headers['x-image-meta-deleted'])
self.assertEqual('deleted', res.headers['x-image-meta-status'])
def test_delete_during_image_upload_by_normal_user(self):
self._check_delete_during_image_upload(is_admin=False)
def test_delete_during_image_upload_by_admin(self):
self._check_delete_during_image_upload(is_admin=True)
def test_disable_purge_props(self):
"""
Test the special x-glance-registry-purge-props header controls
the purge property behaviour of the registry.
:see LP Bug#901534
"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-meta-property-key1': 'value1'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = b"chunk00000remainder"
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertIn('id', res_body)
image_id = res_body['id']
self.assertIn('/images/%s' % image_id, res.headers['location'])
# Verify the status is queued
self.assertIn('status', res_body)
self.assertEqual('active', res_body['status'])
# Check properties are not deleted
self.assertIn('properties', res_body)
self.assertIn('key1', res_body['properties'])
self.assertEqual('value1', res_body['properties']['key1'])
# Now update the image, setting new properties without
# passing the x-glance-registry-purge-props header and
# verify that original properties are marked deleted.
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-property-key2'] = 'value2'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# Verify the original property no longer in headers
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertIn('x-image-meta-property-key2', res.headers,
"Did not find required property in headers. "
"Got headers: %r" % res.headers)
self.assertNotIn('x-image-meta-property-key1', res.headers,
"Found property in headers that was not expected. "
"Got headers: %r" % res.headers)
# Now update the image, setting new properties and
# passing the x-glance-registry-purge-props header with
# a value of "false" and verify that second property
# still appears in headers.
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.headers['x-image-meta-property-key3'] = 'value3'
req.headers['x-glance-registry-purge-props'] = 'false'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# Verify the second and third property in headers
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertIn('x-image-meta-property-key2', res.headers,
"Did not find required property in headers. "
"Got headers: %r" % res.headers)
self.assertIn('x-image-meta-property-key3', res.headers,
"Did not find required property in headers. "
"Got headers: %r" % res.headers)
def test_publicize_image_unauthorized(self):
"""Create a non-public image then fail to make public"""
rules = {"add_image": '@', "publicize_image": '!'}
self.set_policy_rules(rules)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-is-public': 'false',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
req = webob.Request.blank("/images/%s" % res_body['id'])
req.method = 'PUT'
req.headers['x-image-meta-is-public'] = 'true'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_update_image_size_header_too_big(self):
"""Tests raises BadRequest for supplied image size that is too big"""
fixture_headers = {'x-image-meta-size': CONF.image_size_cap + 1}
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'PUT'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_update_image_size_data_too_big(self):
self.config(image_size_cap=512)
fixture_headers = {'content-type': 'application/octet-stream'}
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'PUT'
req.body = b'X' * (CONF.image_size_cap + 1)
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_update_image_size_chunked_data_too_big(self):
self.config(image_size_cap=512)
# Create new image that has no data
req = webob.Request.blank("/images")
req.method = 'POST'
req.headers['x-image-meta-name'] = 'something'
req.headers['x-image-meta-container_format'] = 'ami'
req.headers['x-image-meta-disk_format'] = 'ami'
res = req.get_response(self.api)
image_id = jsonutils.loads(res.body)['image']['id']
fixture_headers = {
'content-type': 'application/octet-stream',
'transfer-encoding': 'chunked',
}
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'PUT'
req.body_file = six.StringIO('X' * (CONF.image_size_cap + 1))
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
def test_update_non_existing_image(self):
self.config(image_size_cap=100)
req = webob.Request.blank("images/%s" % _gen_uuid())
req.method = 'PUT'
req.body = b'test'
req.headers['x-image-meta-name'] = 'test'
req.headers['x-image-meta-container_format'] = 'ami'
req.headers['x-image-meta-disk_format'] = 'ami'
req.headers['x-image-meta-is_public'] = 'False'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_update_public_image(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-is-public': 'true',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
req = webob.Request.blank("/images/%s" % res_body['id'])
req.method = 'PUT'
req.headers['x-image-meta-name'] = 'updated public image'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
@mock.patch.object(registry, 'update_image_metadata')
def test_update_without_public_attribute(self, mock_update_image_metadata):
req = webob.Request.blank("/images/%s" % UUID1)
req.context = self.context
image_meta = {'properties': {}}
image_controller = glance.api.v1.images.Controller()
with mock.patch.object(
image_controller, 'update_store_acls'
) as mock_update_store_acls:
mock_update_store_acls.return_value = None
mock_update_image_metadata.return_value = {}
image_controller.update(
req, UUID1, image_meta, None)
self.assertEqual(0, mock_update_store_acls.call_count)
def test_add_image_wrong_content_type(self):
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'ami',
'x-image-meta-disk_format': 'ami',
'transfer-encoding': 'chunked',
'content-type': 'application/octet-st',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_get_index_sort_name_asc(self):
"""
Tests that the /images API returns list of
public images sorted alphabetically by name in
ascending order.
"""
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'is_public': True,
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'asdf',
'size': 19,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'is_public': True,
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'xyz',
'size': 20,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
req = webob.Request.blank('/images?sort_key=name&sort_dir=asc')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(3, len(images))
self.assertEqual(UUID3, images[0]['id'])
self.assertEqual(UUID2, images[1]['id'])
self.assertEqual(UUID4, images[2]['id'])
def test_get_details_filter_changes_since(self):
"""
Tests that the /images/detail API returns list of
images that changed since the time defined by changes-since
"""
dt1 = timeutils.utcnow() - datetime.timedelta(1)
iso1 = timeutils.isotime(dt1)
date_only1 = dt1.strftime('%Y-%m-%d')
date_only2 = dt1.strftime('%Y%m%d')
date_only3 = dt1.strftime('%Y-%m%d')
dt2 = timeutils.utcnow() + datetime.timedelta(1)
iso2 = timeutils.isotime(dt2)
image_ts = timeutils.utcnow() + datetime.timedelta(2)
hour_before = image_ts.strftime('%Y-%m-%dT%H:%M:%S%%2B01:00')
hour_after = image_ts.strftime('%Y-%m-%dT%H:%M:%S-01:00')
dt4 = timeutils.utcnow() + datetime.timedelta(3)
iso4 = timeutils.isotime(dt4)
UUID3 = _gen_uuid()
extra_fixture = {'id': UUID3,
'status': 'active',
'is_public': True,
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'fake image #3',
'size': 18,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
db_api.image_destroy(self.context, UUID3)
UUID4 = _gen_uuid()
extra_fixture = {'id': UUID4,
'status': 'active',
'is_public': True,
'disk_format': 'ami',
'container_format': 'ami',
'name': 'fake image #4',
'size': 20,
'checksum': None,
'created_at': image_ts,
'updated_at': image_ts}
db_api.image_create(self.context, extra_fixture)
# Check a standard list, 4 images in db (2 deleted)
req = webob.Request.blank('/images/detail')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(2, len(images))
self.assertEqual(UUID4, images[0]['id'])
self.assertEqual(UUID2, images[1]['id'])
# Expect 3 images (1 deleted)
req = webob.Request.blank('/images/detail?changes-since=%s' % iso1)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(3, len(images))
self.assertEqual(UUID4, images[0]['id'])
self.assertEqual(UUID3, images[1]['id']) # deleted
self.assertEqual(UUID2, images[2]['id'])
# Expect 1 images (0 deleted)
req = webob.Request.blank('/images/detail?changes-since=%s' % iso2)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
self.assertEqual(UUID4, images[0]['id'])
# Expect 1 images (0 deleted)
req = webob.Request.blank('/images/detail?changes-since=%s' %
hour_before)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(1, len(images))
self.assertEqual(UUID4, images[0]['id'])
# Expect 0 images (0 deleted)
req = webob.Request.blank('/images/detail?changes-since=%s' %
hour_after)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(0, len(images))
# Expect 0 images (0 deleted)
req = webob.Request.blank('/images/detail?changes-since=%s' % iso4)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(0, len(images))
for param in [date_only1, date_only2, date_only3]:
# Expect 3 images (1 deleted)
req = webob.Request.blank('/images/detail?changes-since=%s' %
param)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
self.assertEqual(3, len(images))
self.assertEqual(UUID4, images[0]['id'])
self.assertEqual(UUID3, images[1]['id']) # deleted
self.assertEqual(UUID2, images[2]['id'])
# Bad request (empty changes-since param)
req = webob.Request.blank('/images/detail?changes-since=')
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_get_images_bad_urls(self):
"""Check that routes collections are not on (LP bug 1185828)"""
req = webob.Request.blank('/images/detail.xxx')
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
req = webob.Request.blank('/images.xxx')
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
req = webob.Request.blank('/images/new')
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
req = webob.Request.blank("/images/%s/members" % UUID1)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank("/images/%s/members.xxx" % UUID1)
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_get_index_filter_on_user_defined_properties(self):
"""Check that image filtering works on user-defined properties"""
image1_id = _gen_uuid()
properties = {'distro': 'ubuntu', 'arch': 'i386'}
extra_fixture = {'id': image1_id,
'status': 'active',
'is_public': True,
'disk_format': 'vhd',
'container_format': 'ovf',
'name': 'image-extra-1',
'size': 18, 'properties': properties,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
image2_id = _gen_uuid()
properties = {'distro': 'ubuntu', 'arch': 'x86_64', 'foo': 'bar'}
extra_fixture = {'id': image2_id,
'status': 'active',
'is_public': True,
'disk_format': 'ami',
'container_format': 'ami',
'name': 'image-extra-2',
'size': 20, 'properties': properties,
'checksum': None}
db_api.image_create(self.context, extra_fixture)
# Test index with filter containing one user-defined property.
# Filter is 'property-distro=ubuntu'.
# Verify both image1 and image2 are returned
req = webob.Request.blank('/images?property-distro=ubuntu')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(2, len(images))
self.assertEqual(image2_id, images[0]['id'])
self.assertEqual(image1_id, images[1]['id'])
# Test index with filter containing one user-defined property but
# non-existent value. Filter is 'property-distro=fedora'.
# Verify neither images are returned
req = webob.Request.blank('/images?property-distro=fedora')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing one user-defined property but
# unique value. Filter is 'property-arch=i386'.
# Verify only image1 is returned.
req = webob.Request.blank('/images?property-arch=i386')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image1_id, images[0]['id'])
# Test index with filter containing one user-defined property but
# unique value. Filter is 'property-arch=x86_64'.
# Verify only image1 is returned.
req = webob.Request.blank('/images?property-arch=x86_64')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Test index with filter containing unique user-defined property.
# Filter is 'property-foo=bar'.
# Verify only image2 is returned.
req = webob.Request.blank('/images?property-foo=bar')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Test index with filter containing unique user-defined property but
# .value is non-existent. Filter is 'property-foo=baz'.
# Verify neither images are returned.
req = webob.Request.blank('/images?property-foo=baz')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties
# Filter is 'property-arch=x86_64&property-distro=ubuntu'.
# Verify only image2 is returned.
req = webob.Request.blank('/images?property-arch=x86_64&'
'property-distro=ubuntu')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image2_id, images[0]['id'])
# Test index with filter containing multiple user-defined properties
# Filter is 'property-arch=i386&property-distro=ubuntu'.
# Verify only image1 is returned.
req = webob.Request.blank('/images?property-arch=i386&'
'property-distro=ubuntu')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
self.assertEqual(image1_id, images[0]['id'])
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-arch=random&property-distro=ubuntu'.
# Verify neither images are returned.
req = webob.Request.blank('/images?property-arch=random&'
'property-distro=ubuntu')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-arch=random&property-distro=random'.
# Verify neither images are returned.
req = webob.Request.blank('/images?property-arch=random&'
'property-distro=random')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-boo=far&property-poo=far'.
# Verify neither images are returned.
req = webob.Request.blank('/images?property-boo=far&'
'property-poo=far')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
# Test index with filter containing multiple user-defined properties.
# Filter is 'property-foo=bar&property-poo=far'.
# Verify neither images are returned.
req = webob.Request.blank('/images?property-foo=bar&'
'property-poo=far')
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(0, len(images))
def test_get_images_detailed_unauthorized(self):
rules = {"get_images": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank('/images/detail')
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_get_images_unauthorized(self):
rules = {"get_images": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank('/images')
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_store_location_not_revealed(self):
"""
Test that the internal store location is NOT revealed
through the API server
"""
# Check index and details...
for url in ('/images', '/images/detail'):
req = webob.Request.blank(url)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_dict = jsonutils.loads(res.body)
images = res_dict['images']
num_locations = sum([1 for record in images
if 'location' in record.keys()])
self.assertEqual(0, num_locations, images)
# Check GET
req = webob.Request.blank("/images/%s" % UUID2)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertNotIn('X-Image-Meta-Location', res.headers)
# Check HEAD
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertNotIn('X-Image-Meta-Location', res.headers)
# Check PUT
req = webob.Request.blank("/images/%s" % UUID2)
req.body = res.body
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
res_body = jsonutils.loads(res.body)
self.assertNotIn('location', res_body['image'])
# Check POST
req = webob.Request.blank("/images")
headers = {'x-image-meta-location': 'http://localhost',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
for k, v in six.iteritems(headers):
req.headers[k] = v
req.method = 'POST'
http = store.get_store_from_scheme('http')
with mock.patch.object(http, 'get_size') as size:
size.return_value = 0
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)
self.assertNotIn('location', res_body['image'])
def test_image_is_checksummed(self):
"""Test that the image contents are checksummed properly"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
image_contents = b"chunk00000remainder"
image_checksum = hashlib.md5(image_contents).hexdigest()
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = image_contents
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual(image_checksum, res_body['checksum'],
"Mismatched checksum. Expected %s, got %s" %
(image_checksum, res_body['checksum']))
def test_etag_equals_checksum_header(self):
"""Test that the ETag header matches the x-image-meta-checksum"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
image_contents = b"chunk00000remainder"
image_checksum = hashlib.md5(image_contents).hexdigest()
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = image_contents
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
image = jsonutils.loads(res.body)['image']
# HEAD the image and check the ETag equals the checksum header...
expected_headers = {'x-image-meta-checksum': image_checksum,
'etag': image_checksum}
req = webob.Request.blank("/images/%s" % image['id'])
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
for key in expected_headers.keys():
self.assertIn(key, res.headers,
"required header '%s' missing from "
"returned headers" % key)
for key, value in six.iteritems(expected_headers):
self.assertEqual(value, res.headers[key])
def test_bad_checksum_prevents_image_creation(self):
"""Test that the image contents are checksummed properly"""
image_contents = b"chunk00000remainder"
bad_checksum = hashlib.md5(b"invalid").hexdigest()
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3',
'x-image-meta-checksum': bad_checksum,
'x-image-meta-is-public': 'true'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
req.headers['Content-Type'] = 'application/octet-stream'
req.body = image_contents
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
# Test that only one image was returned (that already exists)
req = webob.Request.blank("/images")
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
images = jsonutils.loads(res.body)['images']
self.assertEqual(1, len(images))
def test_image_meta(self):
"""Test for HEAD /images/"""
expected_headers = {'x-image-meta-id': UUID2,
'x-image-meta-name': 'fake image #2'}
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertFalse(res.location)
for key, value in six.iteritems(expected_headers):
self.assertEqual(value, res.headers[key])
def test_image_meta_unauthorized(self):
rules = {"get_image": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_show_image_basic(self):
req = webob.Request.blank("/images/%s" % UUID2)
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertFalse(res.location)
self.assertEqual('application/octet-stream', res.content_type)
self.assertEqual(b'chunk00000remainder', res.body)
def test_show_non_exists_image(self):
req = webob.Request.blank("/images/%s" % _gen_uuid())
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_show_image_unauthorized(self):
rules = {"get_image": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank("/images/%s" % UUID2)
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_show_image_unauthorized_download(self):
rules = {"download_image": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank("/images/%s" % UUID2)
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_show_image_restricted_download_for_core_property(self):
rules = {
"restricted":
"not ('1024M':%(min_ram)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
req = webob.Request.blank("/images/%s" % UUID2)
req.headers['X-Auth-Token'] = 'user:tenant:_member_'
req.headers['min_ram'] = '1024M'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_show_image_restricted_download_for_custom_property(self):
rules = {
"restricted":
"not ('test_1234'==%(x_test_key)s and role:_member_)",
"download_image": "role:admin or rule:restricted"
}
self.set_policy_rules(rules)
req = webob.Request.blank("/images/%s" % UUID2)
req.headers['X-Auth-Token'] = 'user:tenant:_member_'
req.headers['x_test_key'] = 'test_1234'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_download_service_unavailable(self):
"""Test image download returns HTTPServiceUnavailable."""
image_fixture = self.FIXTURES[1]
image_fixture.update({'location': 'http://0.0.0.0:1/file.tar.gz'})
request = webob.Request.blank("/images/%s" % UUID2)
request.context = self.context
image_controller = glance.api.v1.images.Controller()
with mock.patch.object(image_controller,
'get_active_image_meta_or_error'
) as mocked_get_image:
mocked_get_image.return_value = image_fixture
self.assertRaises(webob.exc.HTTPServiceUnavailable,
image_controller.show,
request, mocked_get_image)
@mock.patch('glance_store._drivers.filesystem.Store.get')
def test_show_image_store_get_not_support(self, m_get):
m_get.side_effect = store.StoreGetNotSupported()
req = webob.Request.blank("/images/%s" % UUID2)
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
@mock.patch('glance_store._drivers.filesystem.Store.get')
def test_show_image_store_random_get_not_support(self, m_get):
m_get.side_effect = store.StoreRandomGetNotSupported(chunk_size=0,
offset=0)
req = webob.Request.blank("/images/%s" % UUID2)
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_delete_image(self):
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertFalse(res.location)
self.assertEqual(b'', res.body)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int, res.body)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual('True', res.headers['x-image-meta-deleted'])
self.assertEqual('deleted', res.headers['x-image-meta-status'])
def test_delete_non_exists_image(self):
req = webob.Request.blank("/images/%s" % _gen_uuid())
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_delete_not_allowed(self):
# Verify we can get the image data
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'GET'
req.headers['X-Auth-Token'] = 'user:tenant:'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(19, len(res.body))
# Verify we cannot delete the image
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
# Verify the image data is still there
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(19, len(res.body))
def test_delete_queued_image(self):
"""Delete an image in a queued state
Bug #747799 demonstrated that trying to DELETE an image
that had had its save process killed manually results in failure
because the location attribute is None.
Bug #1048851 demonstrated that the status was not properly
being updated to 'deleted' from 'queued'.
"""
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
# Now try to delete the image...
req = webob.Request.blank("/images/%s" % res_body['id'])
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s' % res_body['id'])
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual('True', res.headers['x-image-meta-deleted'])
self.assertEqual('deleted', res.headers['x-image-meta-status'])
def test_delete_queued_image_delayed_delete(self):
"""Delete an image in a queued state when delayed_delete is on
Bug #1048851 demonstrated that the status was not properly
being updated to 'deleted' from 'queued'.
"""
self.config(delayed_delete=True)
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-name': 'fake image #3'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
# Now try to delete the image...
req = webob.Request.blank("/images/%s" % res_body['id'])
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s' % res_body['id'])
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual('True', res.headers['x-image-meta-deleted'])
self.assertEqual('deleted', res.headers['x-image-meta-status'])
def test_delete_protected_image(self):
fixture_headers = {'x-image-meta-store': 'file',
'x-image-meta-name': 'fake image #3',
'x-image-meta-disk-format': 'vhd',
'x-image-meta-container-format': 'ovf',
'x-image-meta-protected': 'True'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in six.iteritems(fixture_headers):
req.headers[k] = v
res = req.get_response(self.api)
self.assertEqual(http_client.CREATED, res.status_int)
res_body = jsonutils.loads(res.body)['image']
self.assertEqual('queued', res_body['status'])
# Now try to delete the image...
req = webob.Request.blank("/images/%s" % res_body['id'])
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_delete_image_unauthorized(self):
rules = {"delete_image": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
def test_head_details(self):
req = webob.Request.blank('/images/detail')
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.METHOD_NOT_ALLOWED, res.status_int)
self.assertEqual('GET', res.headers.get('Allow'))
self.assertEqual(('GET',), res.allow)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
def test_get_details_invalid_marker(self):
"""
Tests that the /images/detail API returns a 400
when an invalid marker is provided
"""
req = webob.Request.blank('/images/detail?marker=%s' % _gen_uuid())
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_get_image_members(self):
"""
Tests members listing for existing images
"""
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
memb_list = jsonutils.loads(res.body)
num_members = len(memb_list['members'])
self.assertEqual(0, num_members)
def test_get_image_members_allowed_by_policy(self):
rules = {"get_members": '@'}
self.set_policy_rules(rules)
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
memb_list = jsonutils.loads(res.body)
num_members = len(memb_list['members'])
self.assertEqual(0, num_members)
def test_get_image_members_forbidden_by_policy(self):
rules = {"get_members": '!'}
self.set_policy_rules(rules)
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, res.status_int)
def test_get_image_members_not_existing(self):
"""
Tests proper exception is raised if attempt to get members of
non-existing image
"""
req = webob.Request.blank('/images/%s/members' % _gen_uuid())
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_add_member_positive(self):
"""
Tests adding image members
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
def test_get_member_images(self):
"""
Tests image listing for members
"""
req = webob.Request.blank('/shared-images/pattieblack')
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
memb_list = jsonutils.loads(res.body)
num_members = len(memb_list['shared_images'])
self.assertEqual(0, num_members)
def test_replace_members(self):
"""
Tests replacing image members raises right exception
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=False)
fixture = dict(member_id='pattieblack')
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.UNAUTHORIZED, res.status_int)
def test_active_image_immutable_props_for_user(self):
"""
Tests user cannot update immutable props of active image
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=False)
fixture_header_list = [{'x-image-meta-checksum': '1234'},
{'x-image-meta-size': '12345'}]
for fixture_header in fixture_header_list:
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'PUT'
for k, v in six.iteritems(fixture_header):
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
orig_value = res.headers[k]
req = webob.Request.blank('/images/%s' % UUID2)
req.headers[k] = v
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
prop = k[len('x-image-meta-'):]
body = res.body.decode('utf-8')
self.assertNotEqual(-1, body.find(
"Forbidden to modify '%s' of active image" % prop))
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(orig_value, res.headers[k])
def test_deactivated_image_immutable_props_for_user(self):
"""
Tests user cannot update immutable props of deactivated image
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=False)
fixture_header_list = [{'x-image-meta-checksum': '1234'},
{'x-image-meta-size': '12345'}]
for fixture_header in fixture_header_list:
req = webob.Request.blank('/images/%s' % UUID3)
req.method = 'PUT'
for k, v in six.iteritems(fixture_header):
req = webob.Request.blank('/images/%s' % UUID3)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
orig_value = res.headers[k]
req = webob.Request.blank('/images/%s' % UUID3)
req.headers[k] = v
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, res.status_int)
prop = k[len('x-image-meta-'):]
body = res.body.decode('utf-8')
self.assertNotEqual(-1, body.find(
"Forbidden to modify '%s' of deactivated image" % prop))
req = webob.Request.blank('/images/%s' % UUID3)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(orig_value, res.headers[k])
def test_props_of_active_image_mutable_for_admin(self):
"""
Tests admin can update 'immutable' props of active image
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
fixture_header_list = [{'x-image-meta-checksum': '1234'},
{'x-image-meta-size': '12345'}]
for fixture_header in fixture_header_list:
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'PUT'
for k, v in six.iteritems(fixture_header):
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s' % UUID2)
req.headers[k] = v
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s' % UUID2)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(v, res.headers[k])
def test_props_of_deactivated_image_mutable_for_admin(self):
"""
Tests admin can update 'immutable' props of deactivated image
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
fixture_header_list = [{'x-image-meta-checksum': '1234'},
{'x-image-meta-size': '12345'}]
for fixture_header in fixture_header_list:
req = webob.Request.blank('/images/%s' % UUID3)
req.method = 'PUT'
for k, v in six.iteritems(fixture_header):
req = webob.Request.blank('/images/%s' % UUID3)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s' % UUID3)
req.headers[k] = v
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s' % UUID3)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(v, res.headers[k])
def test_replace_members_non_existing_image(self):
"""
Tests replacing image members raises right exception
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
fixture = dict(member_id='pattieblack')
req = webob.Request.blank('/images/%s/members' % _gen_uuid())
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_replace_members_bad_request(self):
"""
Tests replacing image members raises bad request if body is wrong
"""
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
fixture = dict(member_id='pattieblack')
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(image_memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
def test_replace_members_positive(self):
"""
Tests replacing image members
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router, is_admin=True)
fixture = [dict(member_id='pattieblack', can_share=False)]
# Replace
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
def test_replace_members_forbidden_by_policy(self):
rules = {"modify_member": '!'}
self.set_policy_rules(rules)
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper),
is_admin=True)
fixture = [{'member_id': 'pattieblack', 'can_share': 'false'}]
req = webob.Request.blank('/images/%s/members' % UUID1)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, res.status_int)
def test_replace_members_allowed_by_policy(self):
rules = {"modify_member": '@'}
self.set_policy_rules(rules)
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper),
is_admin=True)
fixture = [{'member_id': 'pattieblack', 'can_share': 'false'}]
req = webob.Request.blank('/images/%s/members' % UUID1)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int)
def test_add_member_unauthorized(self):
"""
Tests adding image members raises right exception
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router, is_admin=False)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.UNAUTHORIZED, res.status_int)
def test_add_member_non_existing_image(self):
"""
Tests adding image members raises right exception
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router, is_admin=True)
test_uri = '/images/%s/members/pattieblack'
req = webob.Request.blank(test_uri % _gen_uuid())
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_add_member_with_body(self):
"""
Tests adding image members
"""
fixture = dict(can_share=True)
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router, is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
req.body = jsonutils.dump_as_bytes(dict(member=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
def test_add_member_overlimit(self):
self.config(image_member_quota=0)
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
def test_add_member_unlimited(self):
self.config(image_member_quota=-1)
test_router_api = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router_api, is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
def test_add_member_forbidden_by_policy(self):
rules = {"modify_member": '!'}
self.set_policy_rules(rules)
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper),
is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID1)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, res.status_int)
def test_add_member_allowed_by_policy(self):
rules = {"modify_member": '@'}
self.set_policy_rules(rules)
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper),
is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID1)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int)
def test_get_members_of_deleted_image_raises_404(self):
"""
Tests members listing for deleted image raises 404.
"""
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)
self.assertIn('Image with identifier %s has been deleted.' % UUID2,
res.body.decode())
def test_delete_member_of_deleted_image_raises_404(self):
"""
Tests deleting members of deleted image raises 404.
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)
self.assertIn('Image with identifier %s has been deleted.' % UUID2,
res.body.decode())
def test_update_members_of_deleted_image_raises_404(self):
"""
Tests update members of deleted image raises 404.
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
fixture = [{'member_id': 'pattieblack', 'can_share': 'false'}]
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)
body = res.body.decode('utf-8')
self.assertIn(
'Image with identifier %s has been deleted.' % UUID2, body)
def test_replace_members_of_image(self):
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
fixture = [{'member_id': 'pattieblack', 'can_share': 'false'}]
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
memb_list = jsonutils.loads(res.body)
self.assertEqual(1, len(memb_list))
def test_replace_members_of_image_overlimit(self):
# Set image_member_quota to 1
self.config(image_member_quota=1)
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
# PUT an original member entry
fixture = [{'member_id': 'baz', 'can_share': False}]
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
# GET original image member list
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
original_members = jsonutils.loads(res.body)['members']
self.assertEqual(1, len(original_members))
# PUT 2 image members to replace existing (overlimit)
fixture = [{'member_id': 'foo1', 'can_share': False},
{'member_id': 'foo2', 'can_share': False}]
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, res.status_int)
# GET member list
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
# Assert the member list was not changed
memb_list = jsonutils.loads(res.body)['members']
self.assertEqual(original_members, memb_list)
def test_replace_members_of_image_unlimited(self):
self.config(image_member_quota=-1)
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
fixture = [{'member_id': 'foo1', 'can_share': False},
{'member_id': 'foo2', 'can_share': False}]
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'PUT'
req.body = jsonutils.dump_as_bytes(dict(memberships=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
req = webob.Request.blank('/images/%s/members' % UUID2)
req.method = 'GET'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
memb_list = jsonutils.loads(res.body)['members']
self.assertEqual(fixture, memb_list)
def test_create_member_to_deleted_image_raises_404(self):
"""
Tests adding members to deleted image raises 404.
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.OK, res.status_int)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)
self.assertIn('Image with identifier %s has been deleted.' % UUID2,
res.body.decode())
def test_delete_member(self):
"""
Tests deleting image members raises right exception
"""
test_router = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_router, is_admin=False)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(http_client.UNAUTHORIZED, res.status_int)
def test_delete_member_on_non_existing_image(self):
"""
Tests deleting image members raises right exception
"""
test_router = router.API(self.mapper)
api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
test_uri = '/images/%s/members/pattieblack'
req = webob.Request.blank(test_uri % _gen_uuid())
req.method = 'DELETE'
res = req.get_response(api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_delete_non_exist_member(self):
"""
Test deleting image members raises right exception
"""
test_router = router.API(self.mapper)
api = test_utils.FakeAuthMiddleware(
test_router, is_admin=True)
req = webob.Request.blank('/images/%s/members/test_user' % UUID2)
req.method = 'DELETE'
res = req.get_response(api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
def test_delete_image_member(self):
test_rserver = router.API(self.mapper)
self.api = test_utils.FakeAuthMiddleware(
test_rserver, is_admin=True)
# Add member to image:
fixture = dict(can_share=True)
test_uri = '/images/%s/members/test_add_member_positive'
req = webob.Request.blank(test_uri % UUID2)
req.method = 'PUT'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes(dict(member=fixture))
res = req.get_response(self.api)
self.assertEqual(http_client.NO_CONTENT, res.status_int)
# Delete member
test_uri = '/images/%s/members/test_add_member_positive'
req = webob.Request.blank(test_uri % UUID2)
req.headers['X-Auth-Token'] = 'test1:test1:'
req.method = 'DELETE'
req.content_type = 'application/json'
res = req.get_response(self.api)
self.assertEqual(http_client.NOT_FOUND, res.status_int)
self.assertIn(b'Forbidden', res.body)
def test_delete_member_allowed_by_policy(self):
rules = {"delete_member": '@', "modify_member": '@'}
self.set_policy_rules(rules)
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper),
is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int)
def test_delete_member_forbidden_by_policy(self):
rules = {"delete_member": '!', "modify_member": '@'}
self.set_policy_rules(rules)
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper),
is_admin=True)
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
req.method = 'PUT'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int)
req.method = 'DELETE'
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, res.status_int)
class TestImageSerializer(base.IsolatedUnitTest):
def setUp(self):
"""Establish a clean test environment"""
super(TestImageSerializer, self).setUp()
self.receiving_user = 'fake_user'
self.receiving_tenant = 2
self.context = glance.context.RequestContext(
is_admin=True,
user=self.receiving_user,
tenant=self.receiving_tenant)
self.serializer = glance.api.v1.images.ImageSerializer()
def image_iter():
for x in [b'chunk', b'678911234', b'56789']:
yield x
self.FIXTURE = {
'image_iterator': image_iter(),
'image_meta': {
'id': UUID2,
'name': 'fake image #2',
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'is_public': True,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': '06ff575a2856444fbe93100157ed74ab92eb7eff',
'size': 19,
'owner': _gen_uuid(),
'location': "file:///tmp/glance-tests/2",
'properties': {},
}
}
def test_meta(self):
exp_headers = {'x-image-meta-id': UUID2,
'x-image-meta-location': 'file:///tmp/glance-tests/2',
'ETag': self.FIXTURE['image_meta']['checksum'],
'x-image-meta-name': 'fake image #2'}
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
req.remote_addr = "1.2.3.4"
req.context = self.context
response = webob.Response(request=req)
self.serializer.meta(response, self.FIXTURE)
for key, value in six.iteritems(exp_headers):
self.assertEqual(value, response.headers[key])
def test_meta_utf8(self):
# We get unicode strings from JSON, and therefore all strings in the
# metadata will actually be unicode when handled internally. But we
# want to output utf-8.
FIXTURE = {
'image_meta': {
'id': six.text_type(UUID2),
'name': u'fake image #2 with utf-8 éà è',
'status': u'active',
'disk_format': u'vhd',
'container_format': u'ovf',
'is_public': True,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': u'06ff575a2856444fbe93100157ed74ab92eb7eff',
'size': 19,
'owner': six.text_type(_gen_uuid()),
'location': u"file:///tmp/glance-tests/2",
'properties': {
u'prop_éé': u'ça marche',
u'prop_çé': u'çé',
}
}
}
exp_headers = {'x-image-meta-id': UUID2,
'x-image-meta-location': 'file:///tmp/glance-tests/2',
'ETag': '06ff575a2856444fbe93100157ed74ab92eb7eff',
'x-image-meta-size': '19', # str, not int
'x-image-meta-name': 'fake image #2 with utf-8 éà è',
'x-image-meta-property-prop_éé': 'ça marche',
'x-image-meta-property-prop_çé': 'çé'}
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'HEAD'
req.remote_addr = "1.2.3.4"
req.context = self.context
response = webob.Response(request=req)
self.serializer.meta(response, FIXTURE)
if six.PY2:
self.assertNotEqual(type(FIXTURE['image_meta']['name']),
type(response.headers['x-image-meta-name']))
if six.PY3:
self.assertEqual(FIXTURE['image_meta']['name'],
response.headers['x-image-meta-name'])
else:
self.assertEqual(
FIXTURE['image_meta']['name'],
response.headers['x-image-meta-name'].decode('utf-8'))
for key, value in six.iteritems(exp_headers):
self.assertEqual(value, response.headers[key])
if six.PY2:
FIXTURE['image_meta']['properties'][u'prop_bad'] = 'çé'
self.assertRaises(UnicodeDecodeError,
self.serializer.meta, response, FIXTURE)
def test_show(self):
exp_headers = {'x-image-meta-id': UUID2,
'x-image-meta-location': 'file:///tmp/glance-tests/2',
'ETag': self.FIXTURE['image_meta']['checksum'],
'x-image-meta-name': 'fake image #2'}
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'GET'
req.context = self.context
response = webob.Response(request=req)
self.serializer.show(response, self.FIXTURE)
for key, value in six.iteritems(exp_headers):
self.assertEqual(value, response.headers[key])
self.assertEqual(b'chunk67891123456789', response.body)
def test_show_notify(self):
"""Make sure an eventlet posthook for notify_image_sent is added."""
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'GET'
req.context = self.context
response = webob.Response(request=req)
response.request.environ['eventlet.posthooks'] = []
self.serializer.show(response, self.FIXTURE)
# just make sure the app_iter is called
for chunk in response.app_iter:
pass
self.assertNotEqual([], response.request.environ['eventlet.posthooks'])
def test_image_send_notification(self):
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'GET'
req.remote_addr = '1.2.3.4'
req.context = self.context
image_meta = self.FIXTURE['image_meta']
called = {"notified": False}
expected_payload = {
'bytes_sent': 19,
'image_id': UUID2,
'owner_id': image_meta['owner'],
'receiver_tenant_id': self.receiving_tenant,
'receiver_user_id': self.receiving_user,
'destination_ip': '1.2.3.4',
}
def fake_info(_event_type, _payload):
self.assertEqual(expected_payload, _payload)
called['notified'] = True
self.stubs.Set(self.serializer.notifier, 'info', fake_info)
glance.api.common.image_send_notification(19, 19, image_meta, req,
self.serializer.notifier)
self.assertTrue(called['notified'])
def test_image_send_notification_error(self):
"""Ensure image.send notification is sent on error."""
req = webob.Request.blank("/images/%s" % UUID2)
req.method = 'GET'
req.remote_addr = '1.2.3.4'
req.context = self.context
image_meta = self.FIXTURE['image_meta']
called = {"notified": False}
expected_payload = {
'bytes_sent': 17,
'image_id': UUID2,
'owner_id': image_meta['owner'],
'receiver_tenant_id': self.receiving_tenant,
'receiver_user_id': self.receiving_user,
'destination_ip': '1.2.3.4',
}
def fake_error(_event_type, _payload):
self.assertEqual(expected_payload, _payload)
called['notified'] = True
self.stubs.Set(self.serializer.notifier, 'error', fake_error)
# expected and actually sent bytes differ
glance.api.common.image_send_notification(17, 19, image_meta, req,
self.serializer.notifier)
self.assertTrue(called['notified'])
def test_redact_location(self):
"""Ensure location redaction does not change original metadata"""
image_meta = {'size': 3, 'id': '123', 'location': 'http://localhost'}
redacted_image_meta = {'size': 3, 'id': '123'}
copy_image_meta = copy.deepcopy(image_meta)
tmp_image_meta = glance.api.v1.images.redact_loc(image_meta)
self.assertEqual(image_meta, copy_image_meta)
self.assertEqual(redacted_image_meta, tmp_image_meta)
def test_noop_redact_location(self):
"""Check no-op location redaction does not change original metadata"""
image_meta = {'size': 3, 'id': '123'}
redacted_image_meta = {'size': 3, 'id': '123'}
copy_image_meta = copy.deepcopy(image_meta)
tmp_image_meta = glance.api.v1.images.redact_loc(image_meta)
self.assertEqual(image_meta, copy_image_meta)
self.assertEqual(redacted_image_meta, tmp_image_meta)
self.assertEqual(redacted_image_meta, image_meta)
class TestFilterValidator(base.IsolatedUnitTest):
def test_filter_validator(self):
self.assertFalse(glance.api.v1.filters.validate('size_max', -1))
self.assertTrue(glance.api.v1.filters.validate('size_max', 1))
self.assertTrue(glance.api.v1.filters.validate('protected', 'True'))
self.assertTrue(glance.api.v1.filters.validate('protected', 'FALSE'))
self.assertFalse(glance.api.v1.filters.validate('protected', '-1'))
class TestAPIProtectedProps(base.IsolatedUnitTest):
def setUp(self):
"""Establish a clean test environment"""
super(TestAPIProtectedProps, self).setUp()
self.mapper = routes.Mapper()
# turn on property protections
self.set_property_protections()
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper))
db_api.get_engine()
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def tearDown(self):
"""Clear the test environment"""
super(TestAPIProtectedProps, self).tearDown()
self.destroy_fixtures()
def destroy_fixtures(self):
# Easiest to just drop the models and re-create them...
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def _create_admin_image(self, props=None):
if props is None:
props = {}
request = unit_test_utils.get_fake_request(path='/images')
headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-name': 'foo',
'x-image-meta-size': '0',
'x-auth-token': 'user:tenant:admin'}
headers.update(props)
for k, v in six.iteritems(headers):
request.headers[k] = v
created_image = request.get_response(self.api)
res_body = jsonutils.loads(created_image.body)['image']
image_id = res_body['id']
return image_id
def test_prop_protection_with_create_and_permitted_role(self):
"""
As admin role, create an image and verify permitted role 'member' can
create a protected property
"""
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_owner_foo': 'bar'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('bar', res_body['properties']['x_owner_foo'])
def test_prop_protection_with_permitted_policy_config(self):
"""
As admin role, create an image and verify permitted role 'member' can
create a protected property
"""
self.set_property_protections(use_policies=True)
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:admin',
'x-image-meta-property-spl_create_prop_policy': 'bar'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('bar',
res_body['properties']['spl_create_prop_policy'])
def test_prop_protection_with_create_and_unpermitted_role(self):
"""
As admin role, create an image and verify unpermitted role
'fake_member' can *not* create a protected property
"""
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:fake_member',
'x-image-meta-property-x_owner_foo': 'bar'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
another_request.get_response(self.api)
output = another_request.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, output.status_int)
self.assertIn("Property '%s' is protected" %
"x_owner_foo", output.body.decode())
def test_prop_protection_with_show_and_permitted_role(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'member' can read that protected property via HEAD
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:member'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
res2 = another_request.get_response(self.api)
self.assertEqual('bar',
res2.headers['x-image-meta-property-x_owner_foo'])
def test_prop_protection_with_show_and_unpermitted_role(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'fake_role' can *not* read that protected property via
HEAD
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:fake_role'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual(b'', output.body)
self.assertNotIn('x-image-meta-property-x_owner_foo', output.headers)
def test_prop_protection_with_get_and_permitted_role(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'member' can read that protected property via GET
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='GET', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:member'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
res2 = another_request.get_response(self.api)
self.assertEqual('bar',
res2.headers['x-image-meta-property-x_owner_foo'])
def test_prop_protection_with_get_and_unpermitted_role(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'fake_role' can *not* read that protected property via
GET
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='GET', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:fake_role'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual(b'', output.body)
self.assertNotIn('x-image-meta-property-x_owner_foo', output.headers)
def test_prop_protection_with_detail_and_permitted_role(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'member' can read that protected property via
/images/detail
"""
self._create_admin_image({'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='GET', path='/images/detail')
headers = {'x-auth-token': 'user:tenant:member'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
res_body = jsonutils.loads(output.body)['images'][0]
self.assertEqual('bar', res_body['properties']['x_owner_foo'])
def test_prop_protection_with_detail_and_permitted_policy(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'member' can read that protected property via
/images/detail
"""
self.set_property_protections(use_policies=True)
self._create_admin_image({'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='GET', path='/images/detail')
headers = {'x-auth-token': 'user:tenant:member'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
res_body = jsonutils.loads(output.body)['images'][0]
self.assertEqual('bar', res_body['properties']['x_owner_foo'])
def test_prop_protection_with_detail_and_unpermitted_role(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'fake_role' can *not* read that protected property via
/images/detail
"""
self._create_admin_image({'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='GET', path='/images/detail')
headers = {'x-auth-token': 'user:tenant:fake_role'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
res_body = jsonutils.loads(output.body)['images'][0]
self.assertNotIn('x-image-meta-property-x_owner_foo',
res_body['properties'])
def test_prop_protection_with_detail_and_unpermitted_policy(self):
"""
As admin role, create an image with a protected property, and verify
permitted role 'fake_role' can *not* read that protected property via
/images/detail
"""
self.set_property_protections(use_policies=True)
self._create_admin_image({'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
method='GET', path='/images/detail')
headers = {'x-auth-token': 'user:tenant:fake_role'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
res_body = jsonutils.loads(output.body)['images'][0]
self.assertNotIn('x-image-meta-property-x_owner_foo',
res_body['properties'])
def test_prop_protection_with_update_and_permitted_role(self):
"""
As admin role, create an image with protected property, and verify
permitted role 'member' can update that protected property
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_owner_foo': 'baz'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('baz', res_body['properties']['x_owner_foo'])
def test_prop_protection_with_update_and_permitted_policy(self):
"""
As admin role, create an image with protected property, and verify
permitted role 'admin' can update that protected property
"""
self.set_property_protections(use_policies=True)
image_id = self._create_admin_image(
{'x-image-meta-property-spl_default_policy': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:admin',
'x-image-meta-property-spl_default_policy': 'baz'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('baz', res_body['properties']['spl_default_policy'])
def test_prop_protection_with_update_and_unpermitted_role(self):
"""
As admin role, create an image with protected property, and verify
unpermitted role 'fake_role' can *not* update that protected property
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:fake_role',
'x-image-meta-property-x_owner_foo': 'baz'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, output.status_int)
self.assertIn("Property '%s' is protected" %
"x_owner_foo", output.body.decode())
def test_prop_protection_with_update_and_unpermitted_policy(self):
"""
As admin role, create an image with protected property, and verify
unpermitted role 'fake_role' can *not* update that protected property
"""
self.set_property_protections(use_policies=True)
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:fake_role',
'x-image-meta-property-x_owner_foo': 'baz'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, output.status_int)
self.assertIn("Property '%s' is protected" %
"x_owner_foo", output.body.decode())
def test_prop_protection_update_without_read(self):
"""
Test protected property cannot be updated without read permission
"""
image_id = self._create_admin_image(
{'x-image-meta-property-spl_update_only_prop': 'foo'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:spl_role',
'x-image-meta-property-spl_update_only_prop': 'bar'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(webob.exc.HTTPForbidden.code, output.status_int)
self.assertIn("Property '%s' is protected" %
"spl_update_only_prop", output.body.decode())
def test_prop_protection_update_noop(self):
"""
Test protected property update is allowed as long as the user has read
access and the value is unchanged
"""
image_id = self._create_admin_image(
{'x-image-meta-property-spl_read_prop': 'foo'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:spl_role',
'x-image-meta-property-spl_read_prop': 'foo'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('foo', res_body['properties']['spl_read_prop'])
self.assertEqual(http_client.OK, output.status_int)
def test_prop_protection_with_delete_and_permitted_role(self):
"""
As admin role, create an image with protected property, and verify
permitted role 'member' can can delete that protected property
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual({}, res_body['properties'])
def test_prop_protection_with_delete_and_permitted_policy(self):
"""
As admin role, create an image with protected property, and verify
permitted role 'member' can can delete that protected property
"""
self.set_property_protections(use_policies=True)
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual({}, res_body['properties'])
def test_prop_protection_with_delete_and_unpermitted_read(self):
"""
Test protected property cannot be deleted without read permission
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:fake_role',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertNotIn('x-image-meta-property-x_owner_foo', output.headers)
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:admin'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual(b'', output.body)
self.assertEqual('bar',
output.headers['x-image-meta-property-x_owner_foo'])
def test_prop_protection_with_delete_and_unpermitted_delete(self):
"""
Test protected property cannot be deleted without delete permission
"""
image_id = self._create_admin_image(
{'x-image-meta-property-spl_update_prop': 'foo'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:spl_role',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
self.assertIn("Property '%s' is protected" %
"spl_update_prop", output.body.decode())
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:admin'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual(b'', output.body)
self.assertEqual(
'foo', output.headers['x-image-meta-property-spl_update_prop'])
def test_read_protected_props_leak_with_update(self):
"""
Verify when updating props that ones we don't have read permission for
are not disclosed
"""
image_id = self._create_admin_image(
{'x-image-meta-property-spl_update_prop': '0',
'x-image-meta-property-foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:spl_role',
'x-image-meta-property-spl_update_prop': '1',
'X-Glance-Registry-Purge-Props': 'False'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('1', res_body['properties']['spl_update_prop'])
self.assertNotIn('foo', res_body['properties'])
def test_update_protected_props_mix_no_read(self):
"""
Create an image with two props - one only readable by admin, and one
readable/updatable by member. Verify member can successfully update
their property while the admin owned one is ignored transparently
"""
image_id = self._create_admin_image(
{'x-image-meta-property-admin_foo': 'bar',
'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_owner_foo': 'baz'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('baz', res_body['properties']['x_owner_foo'])
self.assertNotIn('admin_foo', res_body['properties'])
def test_update_protected_props_mix_read(self):
"""
Create an image with two props - one readable/updatable by admin, but
also readable by spl_role. The other is readable/updatable by
spl_role. Verify spl_role can successfully update their property but
not the admin owned one
"""
custom_props = {
'x-image-meta-property-spl_read_only_prop': '1',
'x-image-meta-property-spl_update_prop': '2'
}
image_id = self._create_admin_image(custom_props)
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
# verify spl_role can update it's prop
headers = {'x-auth-token': 'user:tenant:spl_role',
'x-image-meta-property-spl_read_only_prop': '1',
'x-image-meta-property-spl_update_prop': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual('1', res_body['properties']['spl_read_only_prop'])
self.assertEqual('1', res_body['properties']['spl_update_prop'])
# verify spl_role can not update admin controlled prop
headers = {'x-auth-token': 'user:tenant:spl_role',
'x-image-meta-property-spl_read_only_prop': '2',
'x-image-meta-property-spl_update_prop': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
def test_delete_protected_props_mix_no_read(self):
"""
Create an image with two props - one only readable by admin, and one
readable/deletable by member. Verify member can successfully delete
their property while the admin owned one is ignored transparently
"""
image_id = self._create_admin_image(
{'x-image-meta-property-admin_foo': 'bar',
'x-image-meta-property-x_owner_foo': 'bar'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertNotIn('x_owner_foo', res_body['properties'])
self.assertNotIn('admin_foo', res_body['properties'])
def test_delete_protected_props_mix_read(self):
"""
Create an image with two props - one readable/deletable by admin, but
also readable by spl_role. The other is readable/deletable by
spl_role. Verify spl_role is forbidden to purge_props in this scenario
without retaining the readable prop.
"""
custom_props = {
'x-image-meta-property-spl_read_only_prop': '1',
'x-image-meta-property-spl_delete_prop': '2'
}
image_id = self._create_admin_image(custom_props)
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:spl_role',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
def test_create_protected_prop_check_case_insensitive(self):
"""
Verify that role check is case-insensitive i.e. the property
marked with role Member is creatable by the member role
"""
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_case_insensitive': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('1', res_body['properties']['x_case_insensitive'])
def test_read_protected_prop_check_case_insensitive(self):
"""
Verify that role check is case-insensitive i.e. the property
marked with role Member is readable by the member role
"""
custom_props = {
'x-image-meta-property-x_case_insensitive': '1'
}
image_id = self._create_admin_image(custom_props)
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:member'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual(b'', output.body)
self.assertEqual(
'1', output.headers['x-image-meta-property-x_case_insensitive'])
def test_update_protected_props_check_case_insensitive(self):
"""
Verify that role check is case-insensitive i.e. the property
marked with role Member is updatable by the member role
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_case_insensitive': '1'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_case_insensitive': '2'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('2', res_body['properties']['x_case_insensitive'])
def test_delete_protected_props_check_case_insensitive(self):
"""
Verify that role check is case-insensitive i.e. the property
marked with role Member is deletable by the member role
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_case_insensitive': '1'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual({}, res_body['properties'])
def test_create_non_protected_prop(self):
"""
Verify property marked with special char '@' is creatable by an unknown
role
"""
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'x-image-meta-property-x_all_permitted': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('1', res_body['properties']['x_all_permitted'])
def test_read_non_protected_prop(self):
"""
Verify property marked with special char '@' is readable by an unknown
role
"""
custom_props = {
'x-image-meta-property-x_all_permitted': '1'
}
image_id = self._create_admin_image(custom_props)
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:joe_soap'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertEqual(b'', output.body)
self.assertEqual(
'1', output.headers['x-image-meta-property-x_all_permitted'])
def test_update_non_protected_prop(self):
"""
Verify property marked with special char '@' is updatable by an unknown
role
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_all_permitted': '1'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'x-image-meta-property-x_all_permitted': '2'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('2', res_body['properties']['x_all_permitted'])
def test_delete_non_protected_prop(self):
"""
Verify property marked with special char '@' is deletable by an unknown
role
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_all_permitted': '1'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual({}, res_body['properties'])
def test_create_locked_down_protected_prop(self):
"""
Verify a property protected by special char '!' is creatable by no one
"""
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_none_permitted': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
# also check admin can not create
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:admin',
'x-image-meta-property-x_none_permitted_admin': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
def test_read_locked_down_protected_prop(self):
"""
Verify a property protected by special char '!' is readable by no one
"""
custom_props = {
'x-image-meta-property-x_none_read': '1'
}
image_id = self._create_admin_image(custom_props)
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:member'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertNotIn('x_none_read', output.headers)
# also check admin can not read
another_request = unit_test_utils.get_fake_request(
method='HEAD', path='/images/%s' % image_id)
headers = {'x-auth-token': 'user:tenant:admin'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
self.assertNotIn('x_none_read', output.headers)
def test_update_locked_down_protected_prop(self):
"""
Verify a property protected by special char '!' is updatable by no one
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_none_update': '1'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'x-image-meta-property-x_none_update': '2'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
# also check admin can't update property
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:admin',
'x-image-meta-property-x_none_update': '2'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
def test_delete_locked_down_protected_prop(self):
"""
Verify a property protected by special char '!' is deletable by no one
"""
image_id = self._create_admin_image(
{'x-image-meta-property-x_none_delete': '1'})
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:member',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
# also check admin can't delete
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:admin',
'X-Glance-Registry-Purge-Props': 'True'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.FORBIDDEN, output.status_int)
class TestAPIPropertyQuotas(base.IsolatedUnitTest):
def setUp(self):
"""Establish a clean test environment"""
super(TestAPIPropertyQuotas, self).setUp()
self.mapper = routes.Mapper()
self.api = test_utils.FakeAuthMiddleware(router.API(self.mapper))
db_api.get_engine()
db_models.unregister_models(db_api.get_engine())
db_models.register_models(db_api.get_engine())
def _create_admin_image(self, props=None):
if props is None:
props = {}
request = unit_test_utils.get_fake_request(path='/images')
headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-name': 'foo',
'x-image-meta-size': '0',
'x-auth-token': 'user:tenant:admin'}
headers.update(props)
for k, v in six.iteritems(headers):
request.headers[k] = v
created_image = request.get_response(self.api)
res_body = jsonutils.loads(created_image.body)['image']
image_id = res_body['id']
return image_id
def test_update_image_with_too_many_properties(self):
"""
Ensure that updating image properties enforces the quota.
"""
self.config(image_property_quota=1)
image_id = self._create_admin_image()
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'x-image-meta-property-x_all_permitted': '1',
'x-image-meta-property-x_all_permitted_foo': '2'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE,
output.status_int)
self.assertIn("Attempted: 2, Maximum: 1", output.text)
def test_update_image_with_too_many_properties_without_purge_props(self):
"""
Ensure that updating image properties counts existing image properties
when enforcing property quota.
"""
self.config(image_property_quota=1)
request = unit_test_utils.get_fake_request(path='/images')
headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-name': 'foo',
'x-image-meta-size': '0',
'x-image-meta-property-x_all_permitted_create': '1',
'x-auth-token': 'user:tenant:admin'}
for k, v in six.iteritems(headers):
request.headers[k] = v
created_image = request.get_response(self.api)
res_body = jsonutils.loads(created_image.body)['image']
image_id = res_body['id']
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'x-glance-registry-purge-props': 'False',
'x-image-meta-property-x_all_permitted': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE,
output.status_int)
self.assertIn("Attempted: 2, Maximum: 1", output.text)
def test_update_properties_without_purge_props_overwrite_value(self):
"""
Ensure that updating image properties does not count against image
property quota.
"""
self.config(image_property_quota=2)
request = unit_test_utils.get_fake_request(path='/images')
headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-name': 'foo',
'x-image-meta-size': '0',
'x-image-meta-property-x_all_permitted_create': '1',
'x-auth-token': 'user:tenant:admin'}
for k, v in six.iteritems(headers):
request.headers[k] = v
created_image = request.get_response(self.api)
res_body = jsonutils.loads(created_image.body)['image']
image_id = res_body['id']
another_request = unit_test_utils.get_fake_request(
path='/images/%s' % image_id, method='PUT')
headers = {'x-auth-token': 'user:tenant:joe_soap',
'x-glance-registry-purge-props': 'False',
'x-image-meta-property-x_all_permitted_create': '3',
'x-image-meta-property-x_all_permitted': '1'}
for k, v in six.iteritems(headers):
another_request.headers[k] = v
output = another_request.get_response(self.api)
self.assertEqual(http_client.OK, output.status_int)
res_body = jsonutils.loads(output.body)['image']
self.assertEqual('1', res_body['properties']['x_all_permitted'])
self.assertEqual('3', res_body['properties']['x_all_permitted_create'])
glance-16.0.1/glance/tests/unit/test_glance_manage.py 0000666 0001750 0001750 00000007363 13267672245 022627 0 ustar zuul zuul 0000000 0000000 # Copyright 2016 OpenStack Foundation.
# Copyright 2016 NTT Data.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_db import exception as db_exception
from glance.cmd import manage
from glance import context
from glance.db.sqlalchemy import api as db_api
import glance.tests.utils as test_utils
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
class DBCommandsTestCase(test_utils.BaseTestCase):
def setUp(self):
super(DBCommandsTestCase, self).setUp()
self.commands = manage.DbCommands()
self.context = context.RequestContext(
user=USER1, tenant=TENANT1)
@mock.patch.object(db_api, 'purge_deleted_rows')
@mock.patch.object(context, 'get_admin_context')
def test_purge_command(self, mock_context, mock_db_purge):
mock_context.return_value = self.context
self.commands.purge(0, 100)
mock_db_purge.assert_called_once_with(self.context, 0, 100)
def test_purge_command_negative_rows(self):
exit = self.assertRaises(SystemExit, self.commands.purge, 1, -1)
self.assertEqual("Minimal rows limit is 1.", exit.code)
def test_purge_invalid_age_in_days(self):
age_in_days = 'abcd'
ex = self.assertRaises(SystemExit, self.commands.purge, age_in_days)
expected = ("Invalid int value for age_in_days: "
"%(age_in_days)s") % {'age_in_days': age_in_days}
self.assertEqual(expected, ex.code)
def test_purge_negative_age_in_days(self):
ex = self.assertRaises(SystemExit, self.commands.purge, '-1')
self.assertEqual("Must supply a non-negative value for age.", ex.code)
def test_purge_invalid_max_rows(self):
max_rows = 'abcd'
ex = self.assertRaises(SystemExit, self.commands.purge, 1, max_rows)
expected = ("Invalid int value for max_rows: "
"%(max_rows)s") % {'max_rows': max_rows}
self.assertEqual(expected, ex.code)
@mock.patch.object(db_api, 'purge_deleted_rows')
@mock.patch.object(context, 'get_admin_context')
def test_purge_max_rows(self, mock_context, mock_db_purge):
mock_context.return_value = self.context
value = (2 ** 31) - 1
self.commands.purge(age_in_days=1, max_rows=value)
mock_db_purge.assert_called_once_with(self.context, 1, value)
def test_purge_command_exceeded_maximum_rows(self):
# value(2 ** 31) is greater than max_rows(2147483647) by 1.
value = 2 ** 31
ex = self.assertRaises(SystemExit, self.commands.purge, age_in_days=1,
max_rows=value)
expected = "'max_rows' value out of range, must not exceed 2147483647."
self.assertEqual(expected, ex.code)
@mock.patch('glance.db.sqlalchemy.api.purge_deleted_rows')
def test_purge_command_fk_constraint_failure(self, purge_deleted_rows):
purge_deleted_rows.side_effect = db_exception.DBReferenceError(
'fake_table', 'fake_constraint', 'fake_key', 'fake_key_table')
exit = self.assertRaises(SystemExit, self.commands.purge, 10, 100)
self.assertEqual("Purge command failed, check glance-manage logs"
" for more details.", exit.code)
glance-16.0.1/glance/tests/unit/common/ 0000775 0001750 0001750 00000000000 13267672475 017737 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/common/test_location_strategy.py 0000666 0001750 0001750 00000020503 13267672245 025077 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import stevedore
from glance.common import location_strategy
from glance.common.location_strategy import location_order
from glance.common.location_strategy import store_type
from glance.tests.unit import base
class TestLocationStrategy(base.IsolatedUnitTest):
"""Test routines in glance.common.location_strategy"""
def _set_original_strategies(self, original_strategies):
for name in location_strategy._available_strategies.keys():
if name not in original_strategies:
del location_strategy._available_strategies[name]
def setUp(self):
super(TestLocationStrategy, self).setUp()
original_strategies = ['location_order', 'store_type']
self.addCleanup(self._set_original_strategies, original_strategies)
def test_load_strategy_modules(self):
modules = location_strategy._load_strategies()
# By default we have two built-in strategy modules.
self.assertEqual(2, len(modules))
self.assertEqual(set(['location_order', 'store_type']),
set(modules.keys()))
self.assertEqual(location_strategy._available_strategies, modules)
def test_load_strategy_module_with_deduplicating(self):
modules = ['module1', 'module2']
def _fake_stevedore_extension_manager(*args, **kwargs):
ret = lambda: None
ret.names = lambda: modules
return ret
def _fake_stevedore_driver_manager(*args, **kwargs):
ret = lambda: None
ret.driver = lambda: None
ret.driver.__name__ = kwargs['name']
# Module 1 and 2 has a same strategy name
ret.driver.get_strategy_name = lambda: 'module_name'
ret.driver.init = lambda: None
return ret
self.stub = self.stubs.Set(stevedore.extension, "ExtensionManager",
_fake_stevedore_extension_manager)
self.stub = self.stubs.Set(stevedore.driver, "DriverManager",
_fake_stevedore_driver_manager)
loaded_modules = location_strategy._load_strategies()
self.assertEqual(1, len(loaded_modules))
self.assertIn('module_name', loaded_modules)
# Skipped module #2, duplicated one.
self.assertEqual('module1', loaded_modules['module_name'].__name__)
def test_load_strategy_module_with_init_exception(self):
modules = ['module_init_exception', 'module_good']
def _fake_stevedore_extension_manager(*args, **kwargs):
ret = lambda: None
ret.names = lambda: modules
return ret
def _fake_stevedore_driver_manager(*args, **kwargs):
if kwargs['name'] == 'module_init_exception':
raise Exception('strategy module failed to initialize.')
else:
ret = lambda: None
ret.driver = lambda: None
ret.driver.__name__ = kwargs['name']
ret.driver.get_strategy_name = lambda: kwargs['name']
ret.driver.init = lambda: None
return ret
self.stub = self.stubs.Set(stevedore.extension, "ExtensionManager",
_fake_stevedore_extension_manager)
self.stub = self.stubs.Set(stevedore.driver, "DriverManager",
_fake_stevedore_driver_manager)
loaded_modules = location_strategy._load_strategies()
self.assertEqual(1, len(loaded_modules))
self.assertIn('module_good', loaded_modules)
# Skipped module #1, initialize failed one.
self.assertEqual('module_good', loaded_modules['module_good'].__name__)
def test_verify_valid_location_strategy(self):
for strategy_name in ['location_order', 'store_type']:
self.config(location_strategy=strategy_name)
location_strategy.verify_location_strategy()
def test_get_ordered_locations_with_none_or_empty_locations(self):
self.assertEqual([], location_strategy.get_ordered_locations(None))
self.assertEqual([], location_strategy.get_ordered_locations([]))
def test_get_ordered_locations(self):
self.config(location_strategy='location_order')
original_locs = [{'url': 'loc1'}, {'url': 'loc2'}]
ordered_locs = location_strategy.get_ordered_locations(original_locs)
# Original location list should remain unchanged
self.assertNotEqual(id(original_locs), id(ordered_locs))
self.assertEqual(original_locs, ordered_locs)
def test_choose_best_location_with_none_or_empty_locations(self):
self.assertIsNone(location_strategy.choose_best_location(None))
self.assertIsNone(location_strategy.choose_best_location([]))
def test_choose_best_location(self):
self.config(location_strategy='location_order')
original_locs = [{'url': 'loc1'}, {'url': 'loc2'}]
best_loc = location_strategy.choose_best_location(original_locs)
# Deep copy protect original location.
self.assertNotEqual(id(original_locs), id(best_loc))
self.assertEqual(original_locs[0], best_loc)
class TestLocationOrderStrategyModule(base.IsolatedUnitTest):
"""Test routines in glance.common.location_strategy.location_order"""
def test_get_ordered_locations(self):
original_locs = [{'url': 'loc1'}, {'url': 'loc2'}]
ordered_locs = location_order.get_ordered_locations(original_locs)
# The result will ordered by original natural order.
self.assertEqual(original_locs, ordered_locs)
class TestStoreTypeStrategyModule(base.IsolatedUnitTest):
"""Test routines in glance.common.location_strategy.store_type"""
def test_get_ordered_locations(self):
self.config(store_type_preference=[' rbd', 'sheepdog ', ' file',
'swift ', ' http ', 'vmware'],
group='store_type_location_strategy')
locs = [{'url': 'file://image0', 'metadata': {'idx': 3}},
{'url': 'rbd://image1', 'metadata': {'idx': 0}},
{'url': 'file://image3', 'metadata': {'idx': 4}},
{'url': 'swift://image4', 'metadata': {'idx': 6}},
{'url': 'cinder://image5', 'metadata': {'idx': 9}},
{'url': 'file://image6', 'metadata': {'idx': 5}},
{'url': 'rbd://image7', 'metadata': {'idx': 1}},
{'url': 'vsphere://image9', 'metadata': {'idx': 8}},
{'url': 'sheepdog://image8', 'metadata': {'idx': 2}}]
ordered_locs = store_type.get_ordered_locations(copy.deepcopy(locs))
locs.sort(key=lambda loc: loc['metadata']['idx'])
# The result will ordered by preferred store type order.
self.assertEqual(locs, ordered_locs)
def test_get_ordered_locations_with_invalid_store_name(self):
self.config(store_type_preference=[' rbd', 'sheepdog ', 'invalid',
'swift ', ' http '],
group='store_type_location_strategy')
locs = [{'url': 'file://image0', 'metadata': {'idx': 4}},
{'url': 'rbd://image1', 'metadata': {'idx': 0}},
{'url': 'file://image3', 'metadata': {'idx': 5}},
{'url': 'swift://image4', 'metadata': {'idx': 3}},
{'url': 'cinder://image5', 'metadata': {'idx': 6}},
{'url': 'file://image6', 'metadata': {'idx': 7}},
{'url': 'rbd://image7', 'metadata': {'idx': 1}},
{'url': 'sheepdog://image8', 'metadata': {'idx': 2}}]
ordered_locs = store_type.get_ordered_locations(copy.deepcopy(locs))
locs.sort(key=lambda loc: loc['metadata']['idx'])
# The result will ordered by preferred store type order.
self.assertEqual(locs, ordered_locs)
glance-16.0.1/glance/tests/unit/common/test_config.py 0000666 0001750 0001750 00000010664 13267672245 022621 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os.path
import shutil
import fixtures
import oslo_middleware
from oslotest import moxstubout
from glance.api.middleware import context
from glance.common import config
from glance.tests import utils as test_utils
class TestPasteApp(test_utils.BaseTestCase):
def setUp(self):
super(TestPasteApp, self).setUp()
mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.stubs = mox_fixture.stubs
def _do_test_load_paste_app(self,
expected_app_type,
make_paste_file=True,
paste_flavor=None,
paste_config_file=None,
paste_append=None):
def _writeto(path, str):
with open(path, 'w') as f:
f.write(str or '')
f.flush()
def _appendto(orig, copy, str):
shutil.copy(orig, copy)
with open(copy, 'a') as f:
f.write(str or '')
f.flush()
self.config(flavor=paste_flavor,
config_file=paste_config_file,
group='paste_deploy')
temp_dir = self.useFixture(fixtures.TempDir()).path
temp_file = os.path.join(temp_dir, 'testcfg.conf')
_writeto(temp_file, '[DEFAULT]\n')
config.parse_args(['--config-file', temp_file])
paste_to = temp_file.replace('.conf', '-paste.ini')
if not paste_config_file and make_paste_file:
paste_from = os.path.join(os.getcwd(),
'etc/glance-registry-paste.ini')
_appendto(paste_from, paste_to, paste_append)
app = config.load_paste_app('glance-registry')
self.assertIsInstance(app, expected_app_type)
def test_load_paste_app(self):
expected_middleware = oslo_middleware.Healthcheck
self._do_test_load_paste_app(expected_middleware)
def test_load_paste_app_paste_config_not_found(self):
expected_middleware = context.UnauthenticatedContextMiddleware
self.assertRaises(RuntimeError, self._do_test_load_paste_app,
expected_middleware, make_paste_file=False)
def test_load_paste_app_with_paste_flavor(self):
pipeline = ('[pipeline:glance-registry-incomplete]\n'
'pipeline = context registryapp')
expected_middleware = context.ContextMiddleware
self._do_test_load_paste_app(expected_middleware,
paste_flavor='incomplete',
paste_append=pipeline)
def test_load_paste_app_with_paste_config_file(self):
paste_config_file = os.path.join(os.getcwd(),
'etc/glance-registry-paste.ini')
expected_middleware = oslo_middleware.Healthcheck
self._do_test_load_paste_app(expected_middleware,
paste_config_file=paste_config_file)
def test_load_paste_app_with_paste_config_file_but_not_exist(self):
paste_config_file = os.path.abspath("glance-registry-paste.ini")
expected_middleware = oslo_middleware.Healthcheck
self.assertRaises(RuntimeError, self._do_test_load_paste_app,
expected_middleware,
paste_config_file=paste_config_file)
def test_get_path_non_exist(self):
self.assertRaises(RuntimeError, config._get_deployment_config_file)
class TestDefaultConfig(test_utils.BaseTestCase):
def setUp(self):
super(TestDefaultConfig, self).setUp()
self.CONF = config.cfg.CONF
self.CONF.import_group('profiler', 'glance.common.wsgi')
def test_osprofiler_disabled(self):
self.assertFalse(self.CONF.profiler.enabled)
self.assertFalse(self.CONF.profiler.trace_sqlalchemy)
glance-16.0.1/glance/tests/unit/common/__init__.py 0000666 0001750 0001750 00000000000 13267672245 022033 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/common/test_timeutils.py 0000666 0001750 0001750 00000021061 13267672245 023364 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import calendar
import datetime
import iso8601
import mock
from glance.common import timeutils
from glance.tests import utils as test_utils
class TimeUtilsTest(test_utils.BaseTestCase):
def setUp(self):
super(TimeUtilsTest, self).setUp()
self.skynet_self_aware_time_str = '1997-08-29T06:14:00Z'
self.skynet_self_aware_time_ms_str = '1997-08-29T06:14:00.000123Z'
self.skynet_self_aware_time = datetime.datetime(1997, 8, 29, 6, 14, 0)
self.skynet_self_aware_ms_time = datetime.datetime(
1997, 8, 29, 6, 14, 0, 123)
self.one_minute_before = datetime.datetime(1997, 8, 29, 6, 13, 0)
self.one_minute_after = datetime.datetime(1997, 8, 29, 6, 15, 0)
self.skynet_self_aware_time_perfect_str = '1997-08-29T06:14:00.000000'
self.skynet_self_aware_time_perfect = datetime.datetime(1997, 8, 29,
6, 14, 0)
def test_isotime(self):
with mock.patch('datetime.datetime') as datetime_mock:
datetime_mock.utcnow.return_value = self.skynet_self_aware_time
dt = timeutils.isotime()
self.assertEqual(dt, self.skynet_self_aware_time_str)
def test_isotimei_micro_second_precision(self):
with mock.patch('datetime.datetime') as datetime_mock:
datetime_mock.utcnow.return_value = self.skynet_self_aware_ms_time
dt = timeutils.isotime(subsecond=True)
self.assertEqual(dt, self.skynet_self_aware_time_ms_str)
def test_parse_isotime(self):
expect = timeutils.parse_isotime(self.skynet_self_aware_time_str)
skynet_self_aware_time_utc = self.skynet_self_aware_time.replace(
tzinfo=iso8601.iso8601.UTC)
self.assertEqual(skynet_self_aware_time_utc, expect)
def test_parse_isotime_micro_second_precision(self):
expect = timeutils.parse_isotime(self.skynet_self_aware_time_ms_str)
skynet_self_aware_time_ms_utc = self.skynet_self_aware_ms_time.replace(
tzinfo=iso8601.iso8601.UTC)
self.assertEqual(skynet_self_aware_time_ms_utc, expect)
def test_utcnow(self):
with mock.patch('datetime.datetime') as datetime_mock:
datetime_mock.utcnow.return_value = self.skynet_self_aware_time
self.assertEqual(timeutils.utcnow(), self.skynet_self_aware_time)
self.assertFalse(timeutils.utcnow() == self.skynet_self_aware_time)
self.assertTrue(timeutils.utcnow())
def test_delta_seconds(self):
before = timeutils.utcnow()
after = before + datetime.timedelta(days=7, seconds=59,
microseconds=123456)
self.assertAlmostEquals(604859.123456,
timeutils.delta_seconds(before, after))
def test_iso8601_from_timestamp(self):
utcnow = timeutils.utcnow()
iso = timeutils.isotime(utcnow)
ts = calendar.timegm(utcnow.timetuple())
self.assertEqual(iso, timeutils.iso8601_from_timestamp(ts))
class TestIso8601Time(test_utils.BaseTestCase):
def _instaneous(self, timestamp, yr, mon, day, hr, minute, sec, micro):
self.assertEqual(timestamp.year, yr)
self.assertEqual(timestamp.month, mon)
self.assertEqual(timestamp.day, day)
self.assertEqual(timestamp.hour, hr)
self.assertEqual(timestamp.minute, minute)
self.assertEqual(timestamp.second, sec)
self.assertEqual(timestamp.microsecond, micro)
def _do_test(self, time_str, yr, mon, day, hr, minute, sec, micro, shift):
DAY_SECONDS = 24 * 60 * 60
timestamp = timeutils.parse_isotime(time_str)
self._instaneous(timestamp, yr, mon, day, hr, minute, sec, micro)
offset = timestamp.tzinfo.utcoffset(None)
self.assertEqual(offset.seconds + offset.days * DAY_SECONDS, shift)
def test_zulu(self):
time_str = '2012-02-14T20:53:07Z'
self._do_test(time_str, 2012, 2, 14, 20, 53, 7, 0, 0)
def test_zulu_micros(self):
time_str = '2012-02-14T20:53:07.123Z'
self._do_test(time_str, 2012, 2, 14, 20, 53, 7, 123000, 0)
def test_offset_east(self):
time_str = '2012-02-14T20:53:07+04:30'
offset = 4.5 * 60 * 60
self._do_test(time_str, 2012, 2, 14, 20, 53, 7, 0, offset)
def test_offset_east_micros(self):
time_str = '2012-02-14T20:53:07.42+04:30'
offset = 4.5 * 60 * 60
self._do_test(time_str, 2012, 2, 14, 20, 53, 7, 420000, offset)
def test_offset_west(self):
time_str = '2012-02-14T20:53:07-05:30'
offset = -5.5 * 60 * 60
self._do_test(time_str, 2012, 2, 14, 20, 53, 7, 0, offset)
def test_offset_west_micros(self):
time_str = '2012-02-14T20:53:07.654321-05:30'
offset = -5.5 * 60 * 60
self._do_test(time_str, 2012, 2, 14, 20, 53, 7, 654321, offset)
def test_compare(self):
zulu = timeutils.parse_isotime('2012-02-14T20:53:07')
east = timeutils.parse_isotime('2012-02-14T20:53:07-01:00')
west = timeutils.parse_isotime('2012-02-14T20:53:07+01:00')
self.assertGreater(east, west)
self.assertGreater(east, zulu)
self.assertGreater(zulu, west)
def test_compare_micros(self):
zulu = timeutils.parse_isotime('2012-02-14T20:53:07.6544')
east = timeutils.parse_isotime('2012-02-14T19:53:07.654321-01:00')
west = timeutils.parse_isotime('2012-02-14T21:53:07.655+01:00')
self.assertLess(east, west)
self.assertLess(east, zulu)
self.assertLess(zulu, west)
def test_zulu_roundtrip(self):
time_str = '2012-02-14T20:53:07Z'
zulu = timeutils.parse_isotime(time_str)
self.assertEqual(zulu.tzinfo, iso8601.iso8601.UTC)
self.assertEqual(timeutils.isotime(zulu), time_str)
def test_east_roundtrip(self):
time_str = '2012-02-14T20:53:07-07:00'
east = timeutils.parse_isotime(time_str)
self.assertEqual(east.tzinfo.tzname(None), '-07:00')
self.assertEqual(timeutils.isotime(east), time_str)
def test_west_roundtrip(self):
time_str = '2012-02-14T20:53:07+11:30'
west = timeutils.parse_isotime(time_str)
self.assertEqual(west.tzinfo.tzname(None), '+11:30')
self.assertEqual(timeutils.isotime(west), time_str)
def test_now_roundtrip(self):
time_str = timeutils.isotime()
now = timeutils.parse_isotime(time_str)
self.assertEqual(now.tzinfo, iso8601.iso8601.UTC)
self.assertEqual(timeutils.isotime(now), time_str)
def test_zulu_normalize(self):
time_str = '2012-02-14T20:53:07Z'
zulu = timeutils.parse_isotime(time_str)
normed = timeutils.normalize_time(zulu)
self._instaneous(normed, 2012, 2, 14, 20, 53, 7, 0)
def test_east_normalize(self):
time_str = '2012-02-14T20:53:07-07:00'
east = timeutils.parse_isotime(time_str)
normed = timeutils.normalize_time(east)
self._instaneous(normed, 2012, 2, 15, 3, 53, 7, 0)
def test_west_normalize(self):
time_str = '2012-02-14T20:53:07+21:00'
west = timeutils.parse_isotime(time_str)
normed = timeutils.normalize_time(west)
self._instaneous(normed, 2012, 2, 13, 23, 53, 7, 0)
def test_normalize_aware_to_naive(self):
dt = datetime.datetime(2011, 2, 14, 20, 53, 7)
time_str = '2011-02-14T20:53:07+21:00'
aware = timeutils.parse_isotime(time_str)
naive = timeutils.normalize_time(aware)
self.assertLess(naive, dt)
def test_normalize_zulu_aware_to_naive(self):
dt = datetime.datetime(2011, 2, 14, 20, 53, 7)
time_str = '2011-02-14T19:53:07Z'
aware = timeutils.parse_isotime(time_str)
naive = timeutils.normalize_time(aware)
self.assertLess(naive, dt)
def test_normalize_naive(self):
dt = datetime.datetime(2011, 2, 14, 20, 53, 7)
dtn = datetime.datetime(2011, 2, 14, 19, 53, 7)
naive = timeutils.normalize_time(dtn)
self.assertLess(naive, dt)
glance-16.0.1/glance/tests/unit/common/test_swift_store_utils.py 0000666 0001750 0001750 00000007142 13267672245 025141 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 Rackspace
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
from glance.common import exception
from glance.common import swift_store_utils
from glance.tests.unit import base
class TestSwiftParams(base.IsolatedUnitTest):
def setUp(self):
super(TestSwiftParams, self).setUp()
conf_file = "glance-swift.conf"
test_dir = self.useFixture(fixtures.TempDir()).path
self.swift_config_file = self._copy_data_file(conf_file, test_dir)
self.config(swift_store_config_file=self.swift_config_file)
def test_multiple_swift_account_enabled(self):
self.config(swift_store_config_file="glance-swift.conf")
self.assertTrue(
swift_store_utils.is_multiple_swift_store_accounts_enabled())
def test_multiple_swift_account_disabled(self):
self.config(swift_store_config_file=None)
self.assertFalse(
swift_store_utils.is_multiple_swift_store_accounts_enabled())
def test_swift_config_file_doesnt_exist(self):
self.config(swift_store_config_file='fake-file.conf')
self.assertRaises(exception.InvalidSwiftStoreConfiguration,
swift_store_utils.SwiftParams)
def test_swift_config_uses_default_values_multiple_account_disabled(self):
default_user = 'user_default'
default_key = 'key_default'
default_auth_address = 'auth@default.com'
default_account_reference = 'ref_default'
confs = {'swift_store_config_file': None,
'swift_store_user': default_user,
'swift_store_key': default_key,
'swift_store_auth_address': default_auth_address,
'default_swift_reference': default_account_reference}
self.config(**confs)
swift_params = swift_store_utils.SwiftParams().params
self.assertEqual(1, len(swift_params.keys()))
self.assertEqual(default_user,
swift_params[default_account_reference]['user']
)
self.assertEqual(default_key,
swift_params[default_account_reference]['key']
)
self.assertEqual(default_auth_address,
swift_params[default_account_reference]
['auth_address']
)
def test_swift_store_config_validates_for_creds_auth_address(self):
swift_params = swift_store_utils.SwiftParams().params
self.assertEqual('tenant:user1',
swift_params['ref1']['user']
)
self.assertEqual('key1',
swift_params['ref1']['key']
)
self.assertEqual('example.com',
swift_params['ref1']['auth_address'])
self.assertEqual('user2',
swift_params['ref2']['user'])
self.assertEqual('key2',
swift_params['ref2']['key'])
self.assertEqual('http://example.com',
swift_params['ref2']['auth_address']
)
glance-16.0.1/glance/tests/unit/common/test_utils.py 0000666 0001750 0001750 00000045044 13267672254 022514 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# Copyright 2015 Mirantis, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import tempfile
import six
import webob
from glance.common import exception
from glance.common import utils
from glance.tests import utils as test_utils
class TestUtils(test_utils.BaseTestCase):
"""Test routines in glance.utils"""
def test_cooperative_reader(self):
"""Ensure cooperative reader class accesses all bytes of file"""
BYTES = 1024
bytes_read = 0
with tempfile.TemporaryFile('w+') as tmp_fd:
tmp_fd.write('*' * BYTES)
tmp_fd.seek(0)
for chunk in utils.CooperativeReader(tmp_fd):
bytes_read += len(chunk)
self.assertEqual(BYTES, bytes_read)
bytes_read = 0
with tempfile.TemporaryFile('w+') as tmp_fd:
tmp_fd.write('*' * BYTES)
tmp_fd.seek(0)
reader = utils.CooperativeReader(tmp_fd)
byte = reader.read(1)
while len(byte) != 0:
bytes_read += 1
byte = reader.read(1)
self.assertEqual(BYTES, bytes_read)
def test_cooperative_reader_of_iterator(self):
"""Ensure cooperative reader supports iterator backends too"""
data = b'abcdefgh'
data_list = [data[i:i + 1] * 3 for i in range(len(data))]
reader = utils.CooperativeReader(data_list)
chunks = []
while True:
chunks.append(reader.read(3))
if chunks[-1] == b'':
break
meat = b''.join(chunks)
self.assertEqual(b'aaabbbcccdddeeefffggghhh', meat)
def test_cooperative_reader_of_iterator_stop_iteration_err(self):
"""Ensure cooperative reader supports iterator backends too"""
reader = utils.CooperativeReader([l * 3 for l in ''])
chunks = []
while True:
chunks.append(reader.read(3))
if chunks[-1] == b'':
break
meat = b''.join(chunks)
self.assertEqual(b'', meat)
def _create_generator(self, chunk_size, max_iterations):
chars = b'abc'
iteration = 0
while True:
index = iteration % len(chars)
chunk = chars[index:index + 1] * chunk_size
yield chunk
iteration += 1
if iteration >= max_iterations:
raise StopIteration()
def _test_reader_chunked(self, chunk_size, read_size, max_iterations=5):
generator = self._create_generator(chunk_size, max_iterations)
reader = utils.CooperativeReader(generator)
result = bytearray()
while True:
data = reader.read(read_size)
if len(data) == 0:
break
self.assertLessEqual(len(data), read_size)
result += data
expected = (b'a' * chunk_size +
b'b' * chunk_size +
b'c' * chunk_size +
b'a' * chunk_size +
b'b' * chunk_size)
self.assertEqual(expected, bytes(result))
def test_cooperative_reader_preserves_size_chunk_less_then_read(self):
self._test_reader_chunked(43, 101)
def test_cooperative_reader_preserves_size_chunk_equals_read(self):
self._test_reader_chunked(1024, 1024)
def test_cooperative_reader_preserves_size_chunk_more_then_read(self):
chunk_size = 16 * 1024 * 1024 # 16 Mb, as in remote http source
read_size = 8 * 1024 # 8k, as in httplib
self._test_reader_chunked(chunk_size, read_size)
def test_limiting_reader(self):
"""Ensure limiting reader class accesses all bytes of file"""
BYTES = 1024
bytes_read = 0
data = six.StringIO("*" * BYTES)
for chunk in utils.LimitingReader(data, BYTES):
bytes_read += len(chunk)
self.assertEqual(BYTES, bytes_read)
bytes_read = 0
data = six.StringIO("*" * BYTES)
reader = utils.LimitingReader(data, BYTES)
byte = reader.read(1)
while len(byte) != 0:
bytes_read += 1
byte = reader.read(1)
self.assertEqual(BYTES, bytes_read)
def test_limiting_reader_fails(self):
"""Ensure limiting reader class throws exceptions if limit exceeded"""
BYTES = 1024
def _consume_all_iter():
bytes_read = 0
data = six.StringIO("*" * BYTES)
for chunk in utils.LimitingReader(data, BYTES - 1):
bytes_read += len(chunk)
self.assertRaises(exception.ImageSizeLimitExceeded, _consume_all_iter)
def _consume_all_read():
bytes_read = 0
data = six.StringIO("*" * BYTES)
reader = utils.LimitingReader(data, BYTES - 1)
byte = reader.read(1)
while len(byte) != 0:
bytes_read += 1
byte = reader.read(1)
self.assertRaises(exception.ImageSizeLimitExceeded, _consume_all_read)
def test_get_meta_from_headers(self):
resp = webob.Response()
resp.headers = {"x-image-meta-name": 'test',
'x-image-meta-virtual-size': 80}
result = utils.get_image_meta_from_headers(resp)
self.assertEqual({'name': 'test', 'properties': {},
'virtual_size': 80}, result)
def test_get_meta_from_headers_none_virtual_size(self):
resp = webob.Response()
resp.headers = {"x-image-meta-name": 'test',
'x-image-meta-virtual-size': 'None'}
result = utils.get_image_meta_from_headers(resp)
self.assertEqual({'name': 'test', 'properties': {},
'virtual_size': None}, result)
def test_get_meta_from_headers_bad_headers(self):
resp = webob.Response()
resp.headers = {"x-image-meta-bad": 'test'}
self.assertRaises(webob.exc.HTTPBadRequest,
utils.get_image_meta_from_headers, resp)
resp.headers = {"x-image-meta-": 'test'}
self.assertRaises(webob.exc.HTTPBadRequest,
utils.get_image_meta_from_headers, resp)
resp.headers = {"x-image-meta-*": 'test'}
self.assertRaises(webob.exc.HTTPBadRequest,
utils.get_image_meta_from_headers, resp)
def test_image_meta(self):
image_meta = {'x-image-meta-size': 'test'}
image_meta_properties = {'properties': {'test': "test"}}
actual = utils.image_meta_to_http_headers(image_meta)
actual_test2 = utils.image_meta_to_http_headers(
image_meta_properties)
self.assertEqual({'x-image-meta-x-image-meta-size': u'test'}, actual)
self.assertEqual({'x-image-meta-property-test': u'test'},
actual_test2)
def test_create_mashup_dict_with_different_core_custom_properties(self):
image_meta = {
'id': 'test-123',
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': True,
'updated_at': '',
'properties': {'test_key': 'test_1234'},
}
mashup_dict = utils.create_mashup_dict(image_meta)
self.assertNotIn('properties', mashup_dict)
self.assertEqual(image_meta['properties']['test_key'],
mashup_dict['test_key'])
def test_create_mashup_dict_with_same_core_custom_properties(self):
image_meta = {
'id': 'test-123',
'name': 'fake_image',
'status': 'active',
'created_at': '',
'min_disk': '10G',
'min_ram': '1024M',
'protected': False,
'locations': '',
'checksum': 'c1234',
'owner': '',
'disk_format': 'raw',
'container_format': 'bare',
'size': '123456789',
'virtual_size': '123456789',
'is_public': 'public',
'deleted': True,
'updated_at': '',
'properties': {'min_ram': '2048M'},
}
mashup_dict = utils.create_mashup_dict(image_meta)
self.assertNotIn('properties', mashup_dict)
self.assertNotEqual(image_meta['properties']['min_ram'],
mashup_dict['min_ram'])
self.assertEqual(image_meta['min_ram'], mashup_dict['min_ram'])
def test_mutating(self):
class FakeContext(object):
def __init__(self):
self.read_only = False
class Fake(object):
def __init__(self):
self.context = FakeContext()
def fake_function(req, context):
return 'test passed'
req = webob.Request.blank('/some_request')
result = utils.mutating(fake_function)
self.assertEqual("test passed", result(req, Fake()))
def test_validate_key_cert_key(self):
self.config(digest_algorithm='sha256')
var_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../', 'var'))
keyfile = os.path.join(var_dir, 'privatekey.key')
certfile = os.path.join(var_dir, 'certificate.crt')
utils.validate_key_cert(keyfile, certfile)
def test_validate_key_cert_no_private_key(self):
with tempfile.NamedTemporaryFile('w+') as tmpf:
self.assertRaises(RuntimeError,
utils.validate_key_cert,
"/not/a/file", tmpf.name)
def test_validate_key_cert_cert_cant_read(self):
with tempfile.NamedTemporaryFile('w+') as keyf:
with tempfile.NamedTemporaryFile('w+') as certf:
os.chmod(certf.name, 0)
self.assertRaises(RuntimeError,
utils.validate_key_cert,
keyf.name, certf.name)
def test_validate_key_cert_key_cant_read(self):
with tempfile.NamedTemporaryFile('w+') as keyf:
with tempfile.NamedTemporaryFile('w+') as certf:
os.chmod(keyf.name, 0)
self.assertRaises(RuntimeError,
utils.validate_key_cert,
keyf.name, certf.name)
def test_invalid_digest_algorithm(self):
self.config(digest_algorithm='fake_algorithm')
var_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../', 'var'))
keyfile = os.path.join(var_dir, 'privatekey.key')
certfile = os.path.join(var_dir, 'certificate.crt')
self.assertRaises(ValueError,
utils.validate_key_cert,
keyfile, certfile)
def test_valid_hostname(self):
valid_inputs = ['localhost',
'glance04-a'
'G',
'528491']
for input_str in valid_inputs:
self.assertTrue(utils.is_valid_hostname(input_str))
def test_valid_hostname_fail(self):
invalid_inputs = ['localhost.localdomain',
'192.168.0.1',
u'\u2603',
'glance02.stack42.local']
for input_str in invalid_inputs:
self.assertFalse(utils.is_valid_hostname(input_str))
def test_valid_fqdn(self):
valid_inputs = ['localhost.localdomain',
'glance02.stack42.local'
'glance04-a.stack47.local',
'img83.glance.xn--penstack-r74e.org']
for input_str in valid_inputs:
self.assertTrue(utils.is_valid_fqdn(input_str))
def test_valid_fqdn_fail(self):
invalid_inputs = ['localhost',
'192.168.0.1',
'999.88.77.6',
u'\u2603.local',
'glance02.stack42']
for input_str in invalid_inputs:
self.assertFalse(utils.is_valid_fqdn(input_str))
def test_valid_host_port_string(self):
valid_pairs = ['10.11.12.13:80',
'172.17.17.1:65535',
'[fe80::a:b:c:d]:9990',
'localhost:9990',
'localhost.localdomain:9990',
'glance02.stack42.local:1234',
'glance04-a.stack47.local:1234',
'img83.glance.xn--penstack-r74e.org:13080']
for pair_str in valid_pairs:
host, port = utils.parse_valid_host_port(pair_str)
escaped = pair_str.startswith('[')
expected_host = '%s%s%s' % ('[' if escaped else '', host,
']' if escaped else '')
self.assertTrue(pair_str.startswith(expected_host))
self.assertGreater(port, 0)
expected_pair = '%s:%d' % (expected_host, port)
self.assertEqual(expected_pair, pair_str)
def test_valid_host_port_string_fail(self):
invalid_pairs = ['',
'10.11.12.13',
'172.17.17.1:99999',
'290.12.52.80:5673',
'absurd inputs happen',
u'\u2601',
u'\u2603:8080',
'fe80::1',
'[fe80::2]',
':5673',
'[fe80::a:b:c:d]9990',
'fe80:a:b:c:d:e:f:1:2:3:4',
'fe80:a:b:c:d:e:f:g',
'fe80::1:8080',
'[fe80:a:b:c:d:e:f:g]:9090',
'[a:b:s:u:r:d]:fe80']
for pair in invalid_pairs:
self.assertRaises(ValueError,
utils.parse_valid_host_port,
pair)
class SplitFilterOpTestCase(test_utils.BaseTestCase):
def test_less_than_operator(self):
expr = 'lt:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('lt', 'bar'), returned)
def test_less_than_equal_operator(self):
expr = 'lte:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('lte', 'bar'), returned)
def test_greater_than_operator(self):
expr = 'gt:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('gt', 'bar'), returned)
def test_greater_than_equal_operator(self):
expr = 'gte:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('gte', 'bar'), returned)
def test_not_equal_operator(self):
expr = 'neq:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('neq', 'bar'), returned)
def test_equal_operator(self):
expr = 'eq:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('eq', 'bar'), returned)
def test_in_operator(self):
expr = 'in:bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('in', 'bar'), returned)
def test_split_filter_value_for_quotes(self):
expr = '\"fake\\\"name\",fakename,\"fake,name\"'
returned = utils.split_filter_value_for_quotes(expr)
list_values = ['fake\\"name', 'fakename', 'fake,name']
self.assertEqual(list_values, returned)
def test_validate_quotes(self):
expr = '\"aaa\\\"aa\",bb,\"cc\"'
returned = utils.validate_quotes(expr)
self.assertIsNone(returned)
invalid_expr = ['\"aa', 'ss\"', 'aa\"bb\"cc', '\"aa\"\"bb\"']
for expr in invalid_expr:
self.assertRaises(exception.InvalidParameterValue,
utils.validate_quotes,
expr)
def test_default_operator(self):
expr = 'bar'
returned = utils.split_filter_op(expr)
self.assertEqual(('eq', expr), returned)
def test_default_operator_with_datetime(self):
expr = '2015-08-27T09:49:58Z'
returned = utils.split_filter_op(expr)
self.assertEqual(('eq', expr), returned)
def test_operator_with_datetime(self):
expr = 'lt:2015-08-27T09:49:58Z'
returned = utils.split_filter_op(expr)
self.assertEqual(('lt', '2015-08-27T09:49:58Z'), returned)
class EvaluateFilterOpTestCase(test_utils.BaseTestCase):
def test_less_than_operator(self):
self.assertTrue(utils.evaluate_filter_op(9, 'lt', 10))
self.assertFalse(utils.evaluate_filter_op(10, 'lt', 10))
self.assertFalse(utils.evaluate_filter_op(11, 'lt', 10))
def test_less_than_equal_operator(self):
self.assertTrue(utils.evaluate_filter_op(9, 'lte', 10))
self.assertTrue(utils.evaluate_filter_op(10, 'lte', 10))
self.assertFalse(utils.evaluate_filter_op(11, 'lte', 10))
def test_greater_than_operator(self):
self.assertFalse(utils.evaluate_filter_op(9, 'gt', 10))
self.assertFalse(utils.evaluate_filter_op(10, 'gt', 10))
self.assertTrue(utils.evaluate_filter_op(11, 'gt', 10))
def test_greater_than_equal_operator(self):
self.assertFalse(utils.evaluate_filter_op(9, 'gte', 10))
self.assertTrue(utils.evaluate_filter_op(10, 'gte', 10))
self.assertTrue(utils.evaluate_filter_op(11, 'gte', 10))
def test_not_equal_operator(self):
self.assertTrue(utils.evaluate_filter_op(9, 'neq', 10))
self.assertFalse(utils.evaluate_filter_op(10, 'neq', 10))
self.assertTrue(utils.evaluate_filter_op(11, 'neq', 10))
def test_equal_operator(self):
self.assertFalse(utils.evaluate_filter_op(9, 'eq', 10))
self.assertTrue(utils.evaluate_filter_op(10, 'eq', 10))
self.assertFalse(utils.evaluate_filter_op(11, 'eq', 10))
def test_invalid_operator(self):
self.assertRaises(exception.InvalidFilterOperatorValue,
utils.evaluate_filter_op, '10', 'bar', '8')
glance-16.0.1/glance/tests/unit/common/test_rpc.py 0000666 0001750 0001750 00000030634 13267672245 022137 0 ustar zuul zuul 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import routes
import six
from six.moves import http_client as http
import webob
from glance.common import exception
from glance.common import rpc
from glance.common import wsgi
from glance.tests.unit import base
from glance.tests import utils as test_utils
class FakeResource(object):
"""
Fake resource defining some methods that
will be called later by the api.
"""
def get_images(self, context, keyword=None):
return keyword
def count_images(self, context, images):
return len(images)
def get_all_images(self, context):
return False
def raise_value_error(self, context):
raise ValueError("Yep, Just like that!")
def raise_weird_error(self, context):
class WeirdError(Exception):
pass
raise WeirdError("Weirdness")
def create_api():
deserializer = rpc.RPCJSONDeserializer()
serializer = rpc.RPCJSONSerializer()
controller = rpc.Controller()
controller.register(FakeResource())
res = wsgi.Resource(controller, deserializer, serializer)
mapper = routes.Mapper()
mapper.connect("/rpc", controller=res,
conditions=dict(method=["POST"]),
action="__call__")
return test_utils.FakeAuthMiddleware(wsgi.Router(mapper), is_admin=True)
class TestRPCController(base.IsolatedUnitTest):
def setUp(self):
super(TestRPCController, self).setUp()
self.res = FakeResource()
self.controller = rpc.Controller()
self.controller.register(self.res)
def test_register(self):
res = FakeResource()
controller = rpc.Controller()
controller.register(res)
self.assertIn("get_images", controller._registered)
self.assertIn("get_all_images", controller._registered)
def test_reigster_filtered(self):
res = FakeResource()
controller = rpc.Controller()
controller.register(res, filtered=["get_all_images"])
self.assertIn("get_all_images", controller._registered)
def test_reigster_excluded(self):
res = FakeResource()
controller = rpc.Controller()
controller.register(res, excluded=["get_all_images"])
self.assertIn("get_images", controller._registered)
def test_reigster_refiner(self):
res = FakeResource()
controller = rpc.Controller()
# Not callable
self.assertRaises(TypeError,
controller.register,
res, refiner="get_all_images")
# Filter returns False
controller.register(res, refiner=lambda x: False)
self.assertNotIn("get_images", controller._registered)
self.assertNotIn("get_images", controller._registered)
# Filter returns True
controller.register(res, refiner=lambda x: True)
self.assertIn("get_images", controller._registered)
self.assertIn("get_images", controller._registered)
def test_request(self):
api = create_api()
req = webob.Request.blank('/rpc')
req.method = 'POST'
req.body = jsonutils.dump_as_bytes([
{
"command": "get_images",
"kwargs": {"keyword": 1}
}
])
res = req.get_response(api)
returned = jsonutils.loads(res.body)
self.assertIsInstance(returned, list)
self.assertEqual(1, returned[0])
def test_request_exc(self):
api = create_api()
req = webob.Request.blank('/rpc')
req.method = 'POST'
req.body = jsonutils.dump_as_bytes([
{
"command": "get_all_images",
"kwargs": {"keyword": 1}
}
])
# Sending non-accepted keyword
# to get_all_images method
res = req.get_response(api)
returned = jsonutils.loads(res.body)
self.assertIn("_error", returned[0])
def test_rpc_errors(self):
api = create_api()
req = webob.Request.blank('/rpc')
req.method = 'POST'
req.content_type = 'application/json'
# Body is not a list, it should fail
req.body = jsonutils.dump_as_bytes({})
res = req.get_response(api)
self.assertEqual(http.BAD_REQUEST, res.status_int)
# cmd is not dict, it should fail.
req.body = jsonutils.dump_as_bytes([None])
res = req.get_response(api)
self.assertEqual(http.BAD_REQUEST, res.status_int)
# No command key, it should fail.
req.body = jsonutils.dump_as_bytes([{}])
res = req.get_response(api)
self.assertEqual(http.BAD_REQUEST, res.status_int)
# kwargs not dict, it should fail.
req.body = jsonutils.dump_as_bytes([{"command": "test", "kwargs": 2}])
res = req.get_response(api)
self.assertEqual(http.BAD_REQUEST, res.status_int)
# Command does not exist, it should fail.
req.body = jsonutils.dump_as_bytes([{"command": "test"}])
res = req.get_response(api)
self.assertEqual(http.NOT_FOUND, res.status_int)
def test_rpc_exception_propagation(self):
api = create_api()
req = webob.Request.blank('/rpc')
req.method = 'POST'
req.content_type = 'application/json'
req.body = jsonutils.dump_as_bytes([{"command": "raise_value_error"}])
res = req.get_response(api)
self.assertEqual(http.OK, res.status_int)
returned = jsonutils.loads(res.body)[0]
err_cls = 'builtins.ValueError' if six.PY3 else 'exceptions.ValueError'
self.assertEqual(err_cls, returned['_error']['cls'])
req.body = jsonutils.dump_as_bytes([{"command": "raise_weird_error"}])
res = req.get_response(api)
self.assertEqual(http.OK, res.status_int)
returned = jsonutils.loads(res.body)[0]
self.assertEqual('glance.common.exception.RPCError',
returned['_error']['cls'])
class TestRPCClient(base.IsolatedUnitTest):
def setUp(self):
super(TestRPCClient, self).setUp()
self.api = create_api()
self.client = rpc.RPCClient(host="http://127.0.0.1:9191")
self.client._do_request = self.fake_request
def fake_request(self, method, url, body, headers):
req = webob.Request.blank(url.path)
body = encodeutils.to_utf8(body)
req.body = body
req.method = method
webob_res = req.get_response(self.api)
return test_utils.FakeHTTPResponse(status=webob_res.status_int,
headers=webob_res.headers,
data=webob_res.body)
def test_method_proxy(self):
proxy = self.client.some_method
self.assertIn("method_proxy", str(proxy))
def test_bulk_request(self):
commands = [{"command": "get_images", 'kwargs': {'keyword': True}},
{"command": "get_all_images"}]
res = self.client.bulk_request(commands)
self.assertEqual(2, len(res))
self.assertTrue(res[0])
self.assertFalse(res[1])
def test_exception_raise(self):
try:
self.client.raise_value_error()
self.fail("Exception not raised")
except ValueError as exc:
self.assertEqual("Yep, Just like that!", str(exc))
def test_rpc_exception(self):
try:
self.client.raise_weird_error()
self.fail("Exception not raised")
except exception.RPCError:
pass
def test_non_str_or_dict_response(self):
rst = self.client.count_images(images=[1, 2, 3, 4])
self.assertEqual(4, rst)
self.assertIsInstance(rst, int)
class TestRPCJSONSerializer(test_utils.BaseTestCase):
def test_to_json(self):
fixture = {"key": "value"}
expected = b'{"key": "value"}'
actual = rpc.RPCJSONSerializer().to_json(fixture)
self.assertEqual(expected, actual)
def test_to_json_with_date_format_value(self):
fixture = {"date": datetime.datetime(1900, 3, 8, 2)}
expected = {"date": {"_value": "1900-03-08T02:00:00",
"_type": "datetime"}}
actual = rpc.RPCJSONSerializer().to_json(fixture)
actual = jsonutils.loads(actual)
for k in expected['date']:
self.assertEqual(expected['date'][k], actual['date'][k])
def test_to_json_with_more_deep_format(self):
fixture = {"is_public": True, "name": [{"name1": "test"}]}
expected = {"is_public": True, "name": [{"name1": "test"}]}
actual = rpc.RPCJSONSerializer().to_json(fixture)
actual = wsgi.JSONResponseSerializer().to_json(fixture)
actual = jsonutils.loads(actual)
for k in expected:
self.assertEqual(expected[k], actual[k])
def test_default(self):
fixture = {"key": "value"}
response = webob.Response()
rpc.RPCJSONSerializer().default(response, fixture)
self.assertEqual(http.OK, response.status_int)
content_types = [h for h in response.headerlist
if h[0] == 'Content-Type']
self.assertEqual(1, len(content_types))
self.assertEqual('application/json', response.content_type)
self.assertEqual(b'{"key": "value"}', response.body)
class TestRPCJSONDeserializer(test_utils.BaseTestCase):
def test_has_body_no_content_length(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'asdf'
request.headers.pop('Content-Length')
self.assertFalse(rpc.RPCJSONDeserializer().has_body(request))
def test_has_body_zero_content_length(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'asdf'
request.headers['Content-Length'] = 0
self.assertFalse(rpc.RPCJSONDeserializer().has_body(request))
def test_has_body_has_content_length(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'asdf'
self.assertIn('Content-Length', request.headers)
self.assertTrue(rpc.RPCJSONDeserializer().has_body(request))
def test_no_body_no_content_length(self):
request = wsgi.Request.blank('/')
self.assertFalse(rpc.RPCJSONDeserializer().has_body(request))
def test_from_json(self):
fixture = '{"key": "value"}'
expected = {"key": "value"}
actual = rpc.RPCJSONDeserializer().from_json(fixture)
self.assertEqual(expected, actual)
def test_from_json_malformed(self):
fixture = 'kjasdklfjsklajf'
self.assertRaises(webob.exc.HTTPBadRequest,
rpc.RPCJSONDeserializer().from_json, fixture)
def test_default_no_body(self):
request = wsgi.Request.blank('/')
actual = rpc.RPCJSONDeserializer().default(request)
expected = {}
self.assertEqual(expected, actual)
def test_default_with_body(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'{"key": "value"}'
actual = rpc.RPCJSONDeserializer().default(request)
expected = {"body": {"key": "value"}}
self.assertEqual(expected, actual)
def test_has_body_has_transfer_encoding(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'fake_body'
request.headers['transfer-encoding'] = ''
self.assertIn('transfer-encoding', request.headers)
self.assertTrue(rpc.RPCJSONDeserializer().has_body(request))
def test_to_json_with_date_format_value(self):
fixture = ('{"date": {"_value": "1900-03-08T02:00:00.000000",'
'"_type": "datetime"}}')
expected = {"date": datetime.datetime(1900, 3, 8, 2)}
actual = rpc.RPCJSONDeserializer().from_json(fixture)
self.assertEqual(expected, actual)
glance-16.0.1/glance/tests/unit/common/scripts/ 0000775 0001750 0001750 00000000000 13267672475 021426 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/common/scripts/test_scripts_utils.py 0000666 0001750 0001750 00000012335 13267672245 025747 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from six.moves import urllib
from glance.common import exception
from glance.common.scripts import utils as script_utils
import glance.tests.utils as test_utils
class TestScriptsUtils(test_utils.BaseTestCase):
def setUp(self):
super(TestScriptsUtils, self).setUp()
def test_get_task(self):
task = mock.ANY
task_repo = mock.Mock(return_value=task)
task_id = mock.ANY
self.assertEqual(task, script_utils.get_task(task_repo, task_id))
def test_unpack_task_input(self):
task_input = {"import_from": "foo",
"import_from_format": "bar",
"image_properties": "baz"}
task = mock.Mock(task_input=task_input)
self.assertEqual(task_input,
script_utils.unpack_task_input(task))
def test_unpack_task_input_error(self):
task_input1 = {"import_from_format": "bar", "image_properties": "baz"}
task_input2 = {"import_from": "foo", "image_properties": "baz"}
task_input3 = {"import_from": "foo", "import_from_format": "bar"}
task1 = mock.Mock(task_input=task_input1)
task2 = mock.Mock(task_input=task_input2)
task3 = mock.Mock(task_input=task_input3)
self.assertRaises(exception.Invalid,
script_utils.unpack_task_input, task1)
self.assertRaises(exception.Invalid,
script_utils.unpack_task_input, task2)
self.assertRaises(exception.Invalid,
script_utils.unpack_task_input, task3)
def test_set_base_image_properties(self):
properties = {}
script_utils.set_base_image_properties(properties)
self.assertIn('disk_format', properties)
self.assertIn('container_format', properties)
self.assertEqual('qcow2', properties['disk_format'])
self.assertEqual('bare', properties['container_format'])
def test_set_base_image_properties_none(self):
properties = None
script_utils.set_base_image_properties(properties)
self.assertIsNone(properties)
def test_set_base_image_properties_not_empty(self):
properties = {'disk_format': 'vmdk', 'container_format': 'bare'}
script_utils.set_base_image_properties(properties)
self.assertIn('disk_format', properties)
self.assertIn('container_format', properties)
self.assertEqual('vmdk', properties.get('disk_format'))
self.assertEqual('bare', properties.get('container_format'))
def test_validate_location_http(self):
location = 'http://example.com'
self.assertEqual(location,
script_utils.validate_location_uri(location))
def test_validate_location_https(self):
location = 'https://example.com'
self.assertEqual(location,
script_utils.validate_location_uri(location))
def test_validate_location_none_error(self):
self.assertRaises(exception.BadStoreUri,
script_utils.validate_location_uri, '')
def test_validate_location_file_location_error(self):
self.assertRaises(exception.BadStoreUri,
script_utils.validate_location_uri, "file:///tmp")
self.assertRaises(exception.BadStoreUri,
script_utils.validate_location_uri,
"filesystem:///tmp")
def test_validate_location_unsupported_error(self):
location = 'swift'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'swift+http'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'swift+https'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'swift+config'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'vsphere'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'sheepdog://'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'rbd://'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
location = 'cinder://'
self.assertRaises(urllib.error.URLError,
script_utils.validate_location_uri, location)
glance-16.0.1/glance/tests/unit/common/scripts/image_import/ 0000775 0001750 0001750 00000000000 13267672475 024102 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/common/scripts/image_import/test_main.py 0000666 0001750 0001750 00000012272 13267672245 026440 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from six.moves import urllib
import glance.common.exception as exception
from glance.common.scripts.image_import import main as image_import_script
from glance.common.scripts import utils
from glance.common import store_utils
import glance.tests.utils as test_utils
class TestImageImport(test_utils.BaseTestCase):
def setUp(self):
super(TestImageImport, self).setUp()
def test_run(self):
with mock.patch.object(image_import_script,
'_execute') as mock_execute:
task_id = mock.ANY
context = mock.ANY
task_repo = mock.ANY
image_repo = mock.ANY
image_factory = mock.ANY
image_import_script.run(task_id, context, task_repo, image_repo,
image_factory)
mock_execute.assert_called_once_with(task_id, task_repo, image_repo,
image_factory)
def test_import_image(self):
image_id = mock.ANY
image = mock.Mock(image_id=image_id)
image_repo = mock.Mock()
image_repo.get.return_value = image
image_factory = mock.ANY
task_input = mock.Mock(image_properties=mock.ANY)
uri = mock.ANY
with mock.patch.object(image_import_script,
'create_image') as mock_create_image:
with mock.patch.object(image_import_script,
'set_image_data') as mock_set_img_data:
mock_create_image.return_value = image
self.assertEqual(
image_id,
image_import_script.import_image(image_repo, image_factory,
task_input, None, uri))
# Check image is in saving state before image_repo.save called
self.assertEqual('saving', image.status)
self.assertTrue(image_repo.save.called)
mock_set_img_data.assert_called_once_with(image, uri, None)
self.assertTrue(image_repo.get.called)
self.assertTrue(image_repo.save.called)
def test_create_image(self):
image = mock.ANY
image_repo = mock.Mock()
image_factory = mock.Mock()
image_factory.new_image.return_value = image
# Note: include some base properties to ensure no error while
# attempting to verify them
image_properties = {'disk_format': 'foo',
'id': 'bar'}
self.assertEqual(image,
image_import_script.create_image(image_repo,
image_factory,
image_properties,
None))
@mock.patch.object(utils, 'get_image_data_iter')
def test_set_image_data_http(self, mock_image_iter):
uri = 'http://www.example.com'
image = mock.Mock()
mock_image_iter.return_value = test_utils.FakeHTTPResponse()
self.assertIsNone(image_import_script.set_image_data(image,
uri,
None))
def test_set_image_data_http_error(self):
uri = 'blahhttp://www.example.com'
image = mock.Mock()
self.assertRaises(urllib.error.URLError,
image_import_script.set_image_data, image, uri, None)
@mock.patch.object(image_import_script, 'create_image')
@mock.patch.object(image_import_script, 'set_image_data')
@mock.patch.object(store_utils, 'delete_image_location_from_backend')
def test_import_image_failed_with_expired_token(
self, mock_delete_data, mock_set_img_data, mock_create_image):
image_id = mock.ANY
locations = ['location']
image = mock.Mock(image_id=image_id, locations=locations)
image_repo = mock.Mock()
image_repo.get.side_effect = [image, exception.NotAuthenticated]
image_factory = mock.ANY
task_input = mock.Mock(image_properties=mock.ANY)
uri = mock.ANY
mock_create_image.return_value = image
self.assertRaises(exception.NotAuthenticated,
image_import_script.import_image,
image_repo, image_factory,
task_input, None, uri)
self.assertEqual(1, mock_set_img_data.call_count)
mock_delete_data.assert_called_once_with(
mock_create_image().context, image_id, 'location')
glance-16.0.1/glance/tests/unit/common/scripts/image_import/__init__.py 0000666 0001750 0001750 00000000000 13267672245 026176 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/common/scripts/__init__.py 0000666 0001750 0001750 00000000000 13267672245 023522 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/common/test_exception.py 0000666 0001750 0001750 00000004122 13267672245 023342 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import encodeutils
import six
from six.moves import http_client as http
from glance.common import exception
from glance.tests import utils as test_utils
class GlanceExceptionTestCase(test_utils.BaseTestCase):
def test_default_error_msg(self):
class FakeGlanceException(exception.GlanceException):
message = "default message"
exc = FakeGlanceException()
self.assertEqual('default message',
encodeutils.exception_to_unicode(exc))
def test_specified_error_msg(self):
msg = exception.GlanceException('test')
self.assertIn('test', encodeutils.exception_to_unicode(msg))
def test_default_error_msg_with_kwargs(self):
class FakeGlanceException(exception.GlanceException):
message = "default message: %(code)s"
exc = FakeGlanceException(code=int(http.INTERNAL_SERVER_ERROR))
self.assertEqual("default message: 500",
encodeutils.exception_to_unicode(exc))
def test_specified_error_msg_with_kwargs(self):
msg = exception.GlanceException('test: %(code)s',
code=int(http.INTERNAL_SERVER_ERROR))
self.assertIn('test: 500', encodeutils.exception_to_unicode(msg))
def test_non_unicode_error_msg(self):
exc = exception.GlanceException(str('test'))
self.assertIsInstance(encodeutils.exception_to_unicode(exc),
six.text_type)
glance-16.0.1/glance/tests/unit/common/test_wsgi.py 0000666 0001750 0001750 00000077642 13267672245 022336 0 ustar zuul zuul 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import gettext
import os
import socket
from babel import localedata
import eventlet.patcher
import fixtures
import mock
from oslo_concurrency import processutils
from oslo_serialization import jsonutils
import routes
import six
from six.moves import http_client as http
import webob
from glance.api.v1 import router as router_v1
from glance.api.v2 import router as router_v2
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
from glance import i18n
from glance.tests import utils as test_utils
class RequestTest(test_utils.BaseTestCase):
def _set_expected_languages(self, all_locales=None, avail_locales=None):
if all_locales is None:
all_locales = []
# Override localedata.locale_identifiers to return some locales.
def returns_some_locales(*args, **kwargs):
return all_locales
self.stubs.Set(localedata, 'locale_identifiers', returns_some_locales)
# Override gettext.find to return other than None for some languages.
def fake_gettext_find(lang_id, *args, **kwargs):
found_ret = '/glance/%s/LC_MESSAGES/glance.mo' % lang_id
if avail_locales is None:
# All locales are available.
return found_ret
languages = kwargs['languages']
if languages[0] in avail_locales:
return found_ret
return None
self.stubs.Set(gettext, 'find', fake_gettext_find)
def test_content_range(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Content-Range"] = 'bytes 10-99/*'
range_ = request.get_range_from_request(120)
self.assertEqual(10, range_.start)
self.assertEqual(100, range_.stop) # non-inclusive
self.assertIsNone(range_.length)
def test_content_range_invalid(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Content-Range"] = 'bytes=0-99'
self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
request.get_range_from_request, 120)
def test_range(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Range"] = 'bytes=10-99'
range_ = request.get_range_from_request(120)
self.assertEqual(10, range_.start)
self.assertEqual(100, range_.end) # non-inclusive
def test_range_invalid(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Range"] = 'bytes=150-'
self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
request.get_range_from_request, 120)
def test_content_type_missing(self):
request = wsgi.Request.blank('/tests/123')
self.assertRaises(exception.InvalidContentType,
request.get_content_type, ('application/xml',))
def test_content_type_unsupported(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Content-Type"] = "text/html"
self.assertRaises(exception.InvalidContentType,
request.get_content_type, ('application/xml',))
def test_content_type_with_charset(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Content-Type"] = "application/json; charset=UTF-8"
result = request.get_content_type(('application/json',))
self.assertEqual("application/json", result)
def test_params(self):
if six.PY2:
expected = webob.multidict.NestedMultiDict({
'limit': '20', 'name':
'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82',
'sort_key': 'name', 'sort_dir': 'asc'})
else:
expected = webob.multidict.NestedMultiDict({
'limit': '20', 'name': 'Привет', 'sort_key': 'name',
'sort_dir': 'asc'})
request = wsgi.Request.blank("/?limit=20&name=%D0%9F%D1%80%D0%B8"
"%D0%B2%D0%B5%D1%82&sort_key=name"
"&sort_dir=asc")
actual = request.params
self.assertEqual(expected, actual)
def test_content_type_from_accept_xml(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = "application/xml"
result = request.best_match_content_type()
self.assertEqual("application/json", result)
def test_content_type_from_accept_json(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = "application/json"
result = request.best_match_content_type()
self.assertEqual("application/json", result)
def test_content_type_from_accept_xml_json(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = "application/xml, application/json"
result = request.best_match_content_type()
self.assertEqual("application/json", result)
def test_content_type_from_accept_json_xml_quality(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = ("application/json; q=0.3, "
"application/xml; q=0.9")
result = request.best_match_content_type()
self.assertEqual("application/json", result)
def test_content_type_accept_default(self):
request = wsgi.Request.blank('/tests/123.unsupported')
request.headers["Accept"] = "application/unsupported1"
result = request.best_match_content_type()
self.assertEqual("application/json", result)
def test_language_accept_default(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept-Language"] = "zz-ZZ,zz;q=0.8"
result = request.best_match_language()
self.assertIsNone(result)
def test_language_accept_none(self):
request = wsgi.Request.blank('/tests/123')
result = request.best_match_language()
self.assertIsNone(result)
def test_best_match_language_expected(self):
# If Accept-Language is a supported language, best_match_language()
# returns it.
self._set_expected_languages(all_locales=['it'])
req = wsgi.Request.blank('/', headers={'Accept-Language': 'it'})
self.assertEqual('it', req.best_match_language())
def test_request_match_language_unexpected(self):
# If Accept-Language is a language we do not support,
# best_match_language() returns None.
self._set_expected_languages(all_locales=['it'])
req = wsgi.Request.blank('/', headers={'Accept-Language': 'unknown'})
self.assertIsNone(req.best_match_language())
@mock.patch.object(webob.acceptparse.AcceptLanguage, 'best_match')
def test_best_match_language_unknown(self, mock_best_match):
# Test that we are actually invoking language negotiation by webop
request = wsgi.Request.blank('/')
accepted = 'unknown-lang'
request.headers = {'Accept-Language': accepted}
mock_best_match.return_value = None
self.assertIsNone(request.best_match_language())
# If Accept-Language is missing or empty, match should be None
request.headers = {'Accept-Language': ''}
self.assertIsNone(request.best_match_language())
request.headers.pop('Accept-Language')
self.assertIsNone(request.best_match_language())
def test_http_error_response_codes(self):
sample_id, member_id, tag_val, task_id = 'abc', '123', '1', '2'
"""Makes sure v1 unallowed methods return 405"""
unallowed_methods = [
('/images', ['PUT', 'DELETE', 'HEAD', 'PATCH']),
('/images/detail', ['POST', 'PUT', 'DELETE', 'PATCH']),
('/images/%s' % sample_id, ['POST', 'PATCH']),
('/images/%s/members' % sample_id,
['POST', 'DELETE', 'HEAD', 'PATCH']),
('/images/%s/members/%s' % (sample_id, member_id),
['POST', 'HEAD', 'PATCH']),
]
api = test_utils.FakeAuthMiddleware(router_v1.API(routes.Mapper()))
for uri, methods in unallowed_methods:
for method in methods:
req = webob.Request.blank(uri)
req.method = method
res = req.get_response(api)
self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
"""Makes sure v2 unallowed methods return 405"""
unallowed_methods = [
('/schemas/image', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
('/schemas/images', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
('/schemas/member', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
('/schemas/members', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
('/schemas/task', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
('/schemas/tasks', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
('/images', ['PUT', 'DELETE', 'PATCH', 'HEAD']),
('/images/%s' % sample_id, ['POST', 'PUT', 'HEAD']),
('/images/%s/file' % sample_id,
['POST', 'DELETE', 'PATCH', 'HEAD']),
('/images/%s/tags/%s' % (sample_id, tag_val),
['GET', 'POST', 'PATCH', 'HEAD']),
('/images/%s/members' % sample_id,
['PUT', 'DELETE', 'PATCH', 'HEAD']),
('/images/%s/members/%s' % (sample_id, member_id),
['POST', 'PATCH', 'HEAD']),
('/tasks', ['PUT', 'DELETE', 'PATCH', 'HEAD']),
('/tasks/%s' % task_id, ['POST', 'PUT', 'PATCH', 'HEAD']),
]
api = test_utils.FakeAuthMiddleware(router_v2.API(routes.Mapper()))
for uri, methods in unallowed_methods:
for method in methods:
req = webob.Request.blank(uri)
req.method = method
res = req.get_response(api)
self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
# Makes sure not implemented methods return 405
req = webob.Request.blank('/schemas/image')
req.method = 'NonexistentMethod'
res = req.get_response(api)
self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
class ResourceTest(test_utils.BaseTestCase):
def test_get_action_args(self):
env = {
'wsgiorg.routing_args': [
None,
{
'controller': None,
'format': None,
'action': 'update',
'id': 12,
},
],
}
expected = {'action': 'update', 'id': 12}
actual = wsgi.Resource(None, None, None).get_action_args(env)
self.assertEqual(expected, actual)
def test_get_action_args_invalid_index(self):
env = {'wsgiorg.routing_args': []}
expected = {}
actual = wsgi.Resource(None, None, None).get_action_args(env)
self.assertEqual(expected, actual)
def test_get_action_args_del_controller_error(self):
actions = {'format': None,
'action': 'update',
'id': 12}
env = {'wsgiorg.routing_args': [None, actions]}
expected = {'action': 'update', 'id': 12}
actual = wsgi.Resource(None, None, None).get_action_args(env)
self.assertEqual(expected, actual)
def test_get_action_args_del_format_error(self):
actions = {'action': 'update', 'id': 12}
env = {'wsgiorg.routing_args': [None, actions]}
expected = {'action': 'update', 'id': 12}
actual = wsgi.Resource(None, None, None).get_action_args(env)
self.assertEqual(expected, actual)
def test_dispatch(self):
class Controller(object):
def index(self, shirt, pants=None):
return (shirt, pants)
resource = wsgi.Resource(None, None, None)
actual = resource.dispatch(Controller(), 'index', 'on', pants='off')
expected = ('on', 'off')
self.assertEqual(expected, actual)
def test_dispatch_default(self):
class Controller(object):
def default(self, shirt, pants=None):
return (shirt, pants)
resource = wsgi.Resource(None, None, None)
actual = resource.dispatch(Controller(), 'index', 'on', pants='off')
expected = ('on', 'off')
self.assertEqual(expected, actual)
def test_dispatch_no_default(self):
class Controller(object):
def show(self, shirt, pants=None):
return (shirt, pants)
resource = wsgi.Resource(None, None, None)
self.assertRaises(AttributeError, resource.dispatch, Controller(),
'index', 'on', pants='off')
def test_call(self):
class FakeController(object):
def index(self, shirt, pants=None):
return (shirt, pants)
resource = wsgi.Resource(FakeController(), None, None)
def dispatch(self, obj, action, *args, **kwargs):
if isinstance(obj, wsgi.JSONRequestDeserializer):
return []
if isinstance(obj, wsgi.JSONResponseSerializer):
raise webob.exc.HTTPForbidden()
self.stubs.Set(wsgi.Resource, 'dispatch', dispatch)
request = wsgi.Request.blank('/')
response = resource.__call__(request)
self.assertIsInstance(response, webob.exc.HTTPForbidden)
self.assertEqual(http.FORBIDDEN, response.status_code)
def test_call_raises_exception(self):
class FakeController(object):
def index(self, shirt, pants=None):
return (shirt, pants)
resource = wsgi.Resource(FakeController(), None, None)
def dispatch(self, obj, action, *args, **kwargs):
raise Exception("test exception")
self.stubs.Set(wsgi.Resource, 'dispatch', dispatch)
request = wsgi.Request.blank('/')
response = resource.__call__(request)
self.assertIsInstance(response, webob.exc.HTTPInternalServerError)
self.assertEqual(http.INTERNAL_SERVER_ERROR, response.status_code)
@mock.patch.object(wsgi, 'translate_exception')
def test_resource_call_error_handle_localized(self,
mock_translate_exception):
class Controller(object):
def delete(self, req, identity):
raise webob.exc.HTTPBadRequest(explanation='Not Found')
actions = {'action': 'delete', 'identity': 12}
env = {'wsgiorg.routing_args': [None, actions]}
request = wsgi.Request.blank('/tests/123', environ=env)
message_es = 'No Encontrado'
resource = wsgi.Resource(Controller(),
wsgi.JSONRequestDeserializer(),
None)
translated_exc = webob.exc.HTTPBadRequest(message_es)
mock_translate_exception.return_value = translated_exc
e = self.assertRaises(webob.exc.HTTPBadRequest,
resource, request)
self.assertEqual(message_es, str(e))
@mock.patch.object(webob.acceptparse.AcceptLanguage, 'best_match')
@mock.patch.object(i18n, 'translate')
def test_translate_exception(self, mock_translate, mock_best_match):
mock_translate.return_value = 'No Encontrado'
mock_best_match.return_value = 'de'
req = wsgi.Request.blank('/tests/123')
req.headers["Accept-Language"] = "de"
e = webob.exc.HTTPNotFound(explanation='Not Found')
e = wsgi.translate_exception(req, e)
self.assertEqual('No Encontrado', e.explanation)
def test_response_headers_encoded(self):
# prepare environment
for_openstack_comrades = \
u'\u0417\u0430 \u043e\u043f\u0435\u043d\u0441\u0442\u0435\u043a, ' \
u'\u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0438'
class FakeController(object):
def index(self, shirt, pants=None):
return (shirt, pants)
class FakeSerializer(object):
def index(self, response, result):
response.headers['unicode_test'] = for_openstack_comrades
# make request
resource = wsgi.Resource(FakeController(), None, FakeSerializer())
actions = {'action': 'index'}
env = {'wsgiorg.routing_args': [None, actions]}
request = wsgi.Request.blank('/tests/123', environ=env)
response = resource.__call__(request)
# ensure it has been encoded correctly
value = (response.headers['unicode_test'].decode('utf-8')
if six.PY2 else response.headers['unicode_test'])
self.assertEqual(for_openstack_comrades, value)
class JSONResponseSerializerTest(test_utils.BaseTestCase):
def test_to_json(self):
fixture = {"key": "value"}
expected = b'{"key": "value"}'
actual = wsgi.JSONResponseSerializer().to_json(fixture)
self.assertEqual(expected, actual)
def test_to_json_with_date_format_value(self):
fixture = {"date": datetime.datetime(1901, 3, 8, 2)}
expected = b'{"date": "1901-03-08T02:00:00.000000"}'
actual = wsgi.JSONResponseSerializer().to_json(fixture)
self.assertEqual(expected, actual)
def test_to_json_with_more_deep_format(self):
fixture = {"is_public": True, "name": [{"name1": "test"}]}
expected = {"is_public": True, "name": [{"name1": "test"}]}
actual = wsgi.JSONResponseSerializer().to_json(fixture)
actual = jsonutils.loads(actual)
for k in expected:
self.assertEqual(expected[k], actual[k])
def test_to_json_with_set(self):
fixture = set(["foo"])
expected = b'["foo"]'
actual = wsgi.JSONResponseSerializer().to_json(fixture)
self.assertEqual(expected, actual)
def test_default(self):
fixture = {"key": "value"}
response = webob.Response()
wsgi.JSONResponseSerializer().default(response, fixture)
self.assertEqual(http.OK, response.status_int)
content_types = [h for h in response.headerlist
if h[0] == 'Content-Type']
self.assertEqual(1, len(content_types))
self.assertEqual('application/json', response.content_type)
self.assertEqual(b'{"key": "value"}', response.body)
class JSONRequestDeserializerTest(test_utils.BaseTestCase):
def test_has_body_no_content_length(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'asdf'
request.headers.pop('Content-Length')
self.assertFalse(wsgi.JSONRequestDeserializer().has_body(request))
def test_has_body_zero_content_length(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'asdf'
request.headers['Content-Length'] = 0
self.assertFalse(wsgi.JSONRequestDeserializer().has_body(request))
def test_has_body_has_content_length(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'asdf'
self.assertIn('Content-Length', request.headers)
self.assertTrue(wsgi.JSONRequestDeserializer().has_body(request))
def test_no_body_no_content_length(self):
request = wsgi.Request.blank('/')
self.assertFalse(wsgi.JSONRequestDeserializer().has_body(request))
def test_from_json(self):
fixture = '{"key": "value"}'
expected = {"key": "value"}
actual = wsgi.JSONRequestDeserializer().from_json(fixture)
self.assertEqual(expected, actual)
def test_from_json_malformed(self):
fixture = 'kjasdklfjsklajf'
self.assertRaises(webob.exc.HTTPBadRequest,
wsgi.JSONRequestDeserializer().from_json, fixture)
def test_default_no_body(self):
request = wsgi.Request.blank('/')
actual = wsgi.JSONRequestDeserializer().default(request)
expected = {}
self.assertEqual(expected, actual)
def test_default_with_body(self):
request = wsgi.Request.blank('/')
request.method = 'POST'
request.body = b'{"key": "value"}'
actual = wsgi.JSONRequestDeserializer().default(request)
expected = {"body": {"key": "value"}}
self.assertEqual(expected, actual)
def test_has_body_has_transfer_encoding(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='chunked'))
def test_has_body_multiple_transfer_encoding(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='chunked, gzip'))
def test_has_body_invalid_transfer_encoding(self):
self.assertFalse(self._check_transfer_encoding(
transfer_encoding='invalid', content_length=0))
def test_has_body_invalid_transfer_encoding_no_content_len_and_body(self):
self.assertFalse(self._check_transfer_encoding(
transfer_encoding='invalid', include_body=False))
def test_has_body_invalid_transfer_encoding_no_content_len_but_body(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='invalid', include_body=True))
def test_has_body_invalid_transfer_encoding_with_content_length(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='invalid', content_length=5))
def test_has_body_valid_transfer_encoding_with_content_length(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='chunked', content_length=1))
def test_has_body_valid_transfer_encoding_without_content_length(self):
self.assertTrue(self._check_transfer_encoding(
transfer_encoding='chunked'))
def _check_transfer_encoding(self, transfer_encoding=None,
content_length=None, include_body=True):
request = wsgi.Request.blank('/')
request.method = 'POST'
if include_body:
request.body = b'fake_body'
request.headers['transfer-encoding'] = transfer_encoding
if content_length is not None:
request.headers['content-length'] = content_length
return wsgi.JSONRequestDeserializer().has_body(request)
def test_get_bind_addr_default_value(self):
expected = ('0.0.0.0', '123456')
actual = wsgi.get_bind_addr(default_port="123456")
self.assertEqual(expected, actual)
class ServerTest(test_utils.BaseTestCase):
def test_create_pool(self):
"""Ensure the wsgi thread pool is an eventlet.greenpool.GreenPool."""
actual = wsgi.Server(threads=1).create_pool()
self.assertIsInstance(actual, eventlet.greenpool.GreenPool)
@mock.patch.object(wsgi.Server, 'configure_socket')
def test_http_keepalive(self, mock_configure_socket):
self.config(http_keepalive=False)
self.config(workers=0)
server = wsgi.Server(threads=1)
server.sock = 'fake_socket'
# mocking eventlet.wsgi server method to check it is called with
# configured 'http_keepalive' value.
with mock.patch.object(eventlet.wsgi,
'server') as mock_server:
fake_application = "fake-application"
server.start(fake_application, 0)
server.wait()
mock_server.assert_called_once_with('fake_socket',
fake_application,
log=server._logger,
debug=False,
custom_pool=server.pool,
keepalive=False,
socket_timeout=900)
def test_number_of_workers(self):
"""Ensure the number of workers matches num cpus limited to 8."""
def pid():
i = 1
while True:
i = i + 1
yield i
with mock.patch.object(os, 'fork') as mock_fork:
with mock.patch('oslo_concurrency.processutils.get_worker_count',
return_value=4):
mock_fork.side_effect = pid
server = wsgi.Server()
server.configure = mock.Mock()
fake_application = "fake-application"
server.start(fake_application, None)
self.assertEqual(4, len(server.children))
with mock.patch('oslo_concurrency.processutils.get_worker_count',
return_value=24):
mock_fork.side_effect = pid
server = wsgi.Server()
server.configure = mock.Mock()
fake_application = "fake-application"
server.start(fake_application, None)
self.assertEqual(8, len(server.children))
mock_fork.side_effect = pid
server = wsgi.Server()
server.configure = mock.Mock()
fake_application = "fake-application"
server.start(fake_application, None)
cpus = processutils.get_worker_count()
expected_workers = cpus if cpus < 8 else 8
self.assertEqual(expected_workers,
len(server.children))
class TestHelpers(test_utils.BaseTestCase):
def test_headers_are_unicode(self):
"""
Verifies that the headers returned by conversion code are unicode.
Headers are passed via http in non-testing mode, which automatically
converts them to unicode. Verifying that the method does the
conversion proves that we aren't passing data that works in tests
but will fail in production.
"""
fixture = {'name': 'fake public image',
'is_public': True,
'size': 19,
'location': "file:///tmp/glance-tests/2",
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
headers = utils.image_meta_to_http_headers(fixture)
for k, v in six.iteritems(headers):
self.assertIsInstance(v, six.text_type)
def test_data_passed_properly_through_headers(self):
"""
Verifies that data is the same after being passed through headers
"""
fixture = {'is_public': True,
'deleted': False,
'name': None,
'size': 19,
'location': "file:///tmp/glance-tests/2",
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
headers = utils.image_meta_to_http_headers(fixture)
class FakeResponse(object):
pass
response = FakeResponse()
response.headers = headers
result = utils.get_image_meta_from_headers(response)
for k, v in six.iteritems(fixture):
if v is not None:
self.assertEqual(v, result[k])
else:
self.assertNotIn(k, result)
class GetSocketTestCase(test_utils.BaseTestCase):
def setUp(self):
super(GetSocketTestCase, self).setUp()
self.useFixture(fixtures.MonkeyPatch(
"glance.common.wsgi.get_bind_addr",
lambda x: ('192.168.0.13', 1234)))
addr_info_list = [(2, 1, 6, '', ('192.168.0.13', 80)),
(2, 2, 17, '', ('192.168.0.13', 80)),
(2, 3, 0, '', ('192.168.0.13', 80))]
self.useFixture(fixtures.MonkeyPatch(
"glance.common.wsgi.socket.getaddrinfo",
lambda *x: addr_info_list))
self.useFixture(fixtures.MonkeyPatch(
"glance.common.wsgi.time.time",
mock.Mock(side_effect=[0, 1, 5, 10, 20, 35])))
self.useFixture(fixtures.MonkeyPatch(
"glance.common.wsgi.utils.validate_key_cert",
lambda *x: None))
wsgi.CONF.cert_file = '/etc/ssl/cert'
wsgi.CONF.key_file = '/etc/ssl/key'
wsgi.CONF.ca_file = '/etc/ssl/ca_cert'
wsgi.CONF.tcp_keepidle = 600
def test_correct_configure_socket(self):
mock_socket = mock.Mock()
self.useFixture(fixtures.MonkeyPatch(
'glance.common.wsgi.ssl.wrap_socket',
mock_socket))
self.useFixture(fixtures.MonkeyPatch(
'glance.common.wsgi.eventlet.listen',
lambda *x, **y: mock_socket))
server = wsgi.Server()
server.default_port = 1234
server.configure_socket()
self.assertIn(mock.call.setsockopt(
socket.SOL_SOCKET,
socket.SO_REUSEADDR,
1), mock_socket.mock_calls)
self.assertIn(mock.call.setsockopt(
socket.SOL_SOCKET,
socket.SO_KEEPALIVE,
1), mock_socket.mock_calls)
if hasattr(socket, 'TCP_KEEPIDLE'):
self.assertIn(mock.call().setsockopt(
socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE,
wsgi.CONF.tcp_keepidle), mock_socket.mock_calls)
def test_get_socket_without_all_ssl_reqs(self):
wsgi.CONF.key_file = None
self.assertRaises(RuntimeError, wsgi.get_socket, 1234)
def test_get_socket_with_bind_problems(self):
self.useFixture(fixtures.MonkeyPatch(
'glance.common.wsgi.eventlet.listen',
mock.Mock(side_effect=(
[wsgi.socket.error(socket.errno.EADDRINUSE)] * 3 + [None]))))
self.useFixture(fixtures.MonkeyPatch(
'glance.common.wsgi.ssl.wrap_socket',
lambda *x, **y: None))
self.assertRaises(RuntimeError, wsgi.get_socket, 1234)
def test_get_socket_with_unexpected_socket_errno(self):
self.useFixture(fixtures.MonkeyPatch(
'glance.common.wsgi.eventlet.listen',
mock.Mock(side_effect=wsgi.socket.error(socket.errno.ENOMEM))))
self.useFixture(fixtures.MonkeyPatch(
'glance.common.wsgi.ssl.wrap_socket',
lambda *x, **y: None))
self.assertRaises(wsgi.socket.error, wsgi.get_socket, 1234)
def _cleanup_uwsgi():
wsgi.uwsgi = None
class Test_UwsgiChunkedFile(test_utils.BaseTestCase):
def test_read_no_data(self):
reader = wsgi._UWSGIChunkFile()
wsgi.uwsgi = mock.MagicMock()
self.addCleanup(_cleanup_uwsgi)
def fake_read():
return None
wsgi.uwsgi.chunked_read = fake_read
out = reader.read()
self.assertEqual(out, b'')
def test_read_data_no_length(self):
reader = wsgi._UWSGIChunkFile()
wsgi.uwsgi = mock.MagicMock()
self.addCleanup(_cleanup_uwsgi)
values = iter([b'a', b'b', b'c', None])
def fake_read():
return next(values)
wsgi.uwsgi.chunked_read = fake_read
out = reader.read()
self.assertEqual(out, b'abc')
def test_read_zero_length(self):
reader = wsgi._UWSGIChunkFile()
self.assertEqual(b'', reader.read(length=0))
def test_read_data_length(self):
reader = wsgi._UWSGIChunkFile()
wsgi.uwsgi = mock.MagicMock()
self.addCleanup(_cleanup_uwsgi)
values = iter([b'a', b'b', b'c', None])
def fake_read():
return next(values)
wsgi.uwsgi.chunked_read = fake_read
out = reader.read(length=2)
self.assertEqual(out, b'ab')
def test_read_data_negative_length(self):
reader = wsgi._UWSGIChunkFile()
wsgi.uwsgi = mock.MagicMock()
self.addCleanup(_cleanup_uwsgi)
values = iter([b'a', b'b', b'c', None])
def fake_read():
return next(values)
wsgi.uwsgi.chunked_read = fake_read
out = reader.read(length=-2)
self.assertEqual(out, b'abc')
glance-16.0.1/glance/tests/unit/common/test_property_utils.py 0000666 0001750 0001750 00000057315 13267672245 024464 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.api import policy
from glance.common import exception
from glance.common import property_utils
import glance.context
from glance.tests.unit import base
CONFIG_SECTIONS = [
'^x_owner_.*',
'spl_create_prop',
'spl_read_prop',
'spl_read_only_prop',
'spl_update_prop',
'spl_update_only_prop',
'spl_delete_prop',
'spl_delete_empty_prop',
'^x_all_permitted.*',
'^x_none_permitted.*',
'x_none_read',
'x_none_update',
'x_none_delete',
'x_case_insensitive',
'x_foo_matcher',
'x_foo_*',
'.*'
]
def create_context(policy, roles=None):
if roles is None:
roles = []
return glance.context.RequestContext(roles=roles,
policy_enforcer=policy)
class TestPropertyRulesWithRoles(base.IsolatedUnitTest):
def setUp(self):
super(TestPropertyRulesWithRoles, self).setUp()
self.set_property_protections()
self.policy = policy.Enforcer()
def test_is_property_protections_enabled_true(self):
self.config(property_protection_file="property-protections.conf")
self.assertTrue(property_utils.is_property_protection_enabled())
def test_is_property_protections_enabled_false(self):
self.config(property_protection_file=None)
self.assertFalse(property_utils.is_property_protection_enabled())
def test_property_protection_file_doesnt_exist(self):
self.config(property_protection_file='fake-file.conf')
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_property_protection_with_mutually_exclusive_rule(self):
exclusive_rules = {'.*': {'create': ['@', '!'],
'read': ['fake-role'],
'update': ['fake-role'],
'delete': ['fake-role']}}
self.set_property_protection_rules(exclusive_rules)
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_property_protection_with_malformed_rule(self):
malformed_rules = {'^[0-9)': {'create': ['fake-role'],
'read': ['fake-role'],
'update': ['fake-role'],
'delete': ['fake-role']}}
self.set_property_protection_rules(malformed_rules)
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_property_protection_with_missing_operation(self):
rules_with_missing_operation = {'^[0-9]': {'create': ['fake-role'],
'update': ['fake-role'],
'delete': ['fake-role']}}
self.set_property_protection_rules(rules_with_missing_operation)
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_property_protection_with_misspelt_operation(self):
rules_with_misspelt_operation = {'^[0-9]': {'create': ['fake-role'],
'rade': ['fake-role'],
'update': ['fake-role'],
'delete': ['fake-role']}}
self.set_property_protection_rules(rules_with_misspelt_operation)
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_property_protection_with_whitespace(self):
rules_whitespace = {
'^test_prop.*': {
'create': ['member ,fake-role'],
'read': ['fake-role, member'],
'update': ['fake-role, member'],
'delete': ['fake-role, member']
}
}
self.set_property_protection_rules(rules_whitespace)
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules('test_prop_1',
'read', create_context(self.policy, ['member'])))
self.assertTrue(self.rules_checker.check_property_rules('test_prop_1',
'read', create_context(self.policy, ['fake-role'])))
def test_check_property_rules_invalid_action(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertFalse(self.rules_checker.check_property_rules('test_prop',
'hall', create_context(self.policy, ['admin'])))
def test_check_property_rules_read_permitted_admin_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules('test_prop',
'read', create_context(self.policy, ['admin'])))
def test_check_property_rules_read_permitted_specific_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules(
'x_owner_prop', 'read',
create_context(self.policy, ['member'])))
def test_check_property_rules_read_unpermitted_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertFalse(self.rules_checker.check_property_rules('test_prop',
'read', create_context(self.policy, ['member'])))
def test_check_property_rules_create_permitted_admin_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules('test_prop',
'create', create_context(self.policy, ['admin'])))
def test_check_property_rules_create_permitted_specific_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules(
'x_owner_prop', 'create',
create_context(self.policy, ['member'])))
def test_check_property_rules_create_unpermitted_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertFalse(self.rules_checker.check_property_rules('test_prop',
'create', create_context(self.policy, ['member'])))
def test_check_property_rules_update_permitted_admin_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules('test_prop',
'update', create_context(self.policy, ['admin'])))
def test_check_property_rules_update_permitted_specific_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules(
'x_owner_prop', 'update',
create_context(self.policy, ['member'])))
def test_check_property_rules_update_unpermitted_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertFalse(self.rules_checker.check_property_rules('test_prop',
'update', create_context(self.policy, ['member'])))
def test_check_property_rules_delete_permitted_admin_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules('test_prop',
'delete', create_context(self.policy, ['admin'])))
def test_check_property_rules_delete_permitted_specific_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertTrue(self.rules_checker.check_property_rules(
'x_owner_prop', 'delete',
create_context(self.policy, ['member'])))
def test_check_property_rules_delete_unpermitted_role(self):
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertFalse(self.rules_checker.check_property_rules('test_prop',
'delete', create_context(self.policy, ['member'])))
def test_property_config_loaded_in_order(self):
"""
Verify the order of loaded config sections matches that from the
configuration file
"""
self.rules_checker = property_utils.PropertyRules(self.policy)
self.assertEqual(CONFIG_SECTIONS, property_utils.CONFIG.sections())
def test_property_rules_loaded_in_order(self):
"""
Verify rules are iterable in the same order as read from the config
file
"""
self.rules_checker = property_utils.PropertyRules(self.policy)
for i in range(len(property_utils.CONFIG.sections())):
self.assertEqual(property_utils.CONFIG.sections()[i],
self.rules_checker.rules[i][0].pattern)
def test_check_property_rules_create_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'create', create_context(self.policy, [''])))
def test_check_property_rules_read_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'read', create_context(self.policy, [''])))
def test_check_property_rules_update_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'update', create_context(self.policy, [''])))
def test_check_property_rules_delete_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'delete', create_context(self.policy, [''])))
def test_check_property_rules_create_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'create', create_context(self.policy, [''])))
def test_check_property_rules_read_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'read', create_context(self.policy, [''])))
def test_check_property_rules_update_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'update', create_context(self.policy, [''])))
def test_check_property_rules_delete_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'delete', create_context(self.policy, [''])))
def test_check_property_rules_read_none(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_read', 'create',
create_context(self.policy, ['admin', 'member'])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_read', 'read',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_read', 'update',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_read', 'delete',
create_context(self.policy, [''])))
def test_check_property_rules_update_none(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_update', 'create',
create_context(self.policy, ['admin', 'member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_update', 'read',
create_context(self.policy, ['admin', 'member'])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_update', 'update',
create_context(self.policy, [''])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_update', 'delete',
create_context(self.policy, ['admin', 'member'])))
def test_check_property_rules_delete_none(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_delete', 'create',
create_context(self.policy, ['admin', 'member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_delete', 'read',
create_context(self.policy, ['admin', 'member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_delete', 'update',
create_context(self.policy, ['admin', 'member'])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_delete', 'delete',
create_context(self.policy, [''])))
def test_check_return_first_match(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'create',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'read',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'update',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'delete',
create_context(self.policy, [''])))
def test_check_case_insensitive_property_rules(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_case_insensitive', 'create',
create_context(self.policy, ['member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_case_insensitive', 'read',
create_context(self.policy, ['member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_case_insensitive', 'update',
create_context(self.policy, ['member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_case_insensitive', 'delete',
create_context(self.policy, ['member'])))
class TestPropertyRulesWithPolicies(base.IsolatedUnitTest):
def setUp(self):
super(TestPropertyRulesWithPolicies, self).setUp()
self.set_property_protections(use_policies=True)
self.policy = policy.Enforcer()
self.rules_checker = property_utils.PropertyRules(self.policy)
def test_check_property_rules_create_permitted_specific_policy(self):
self.assertTrue(self.rules_checker.check_property_rules(
'spl_creator_policy', 'create',
create_context(self.policy, ['spl_role'])))
def test_check_property_rules_create_unpermitted_policy(self):
self.assertFalse(self.rules_checker.check_property_rules(
'spl_creator_policy', 'create',
create_context(self.policy, ['fake-role'])))
def test_check_property_rules_read_permitted_specific_policy(self):
self.assertTrue(self.rules_checker.check_property_rules(
'spl_creator_policy', 'read',
create_context(self.policy, ['spl_role'])))
def test_check_property_rules_read_unpermitted_policy(self):
self.assertFalse(self.rules_checker.check_property_rules(
'spl_creator_policy', 'read',
create_context(self.policy, ['fake-role'])))
def test_check_property_rules_update_permitted_specific_policy(self):
self.assertTrue(self.rules_checker.check_property_rules(
'spl_creator_policy', 'update',
create_context(self.policy, ['admin'])))
def test_check_property_rules_update_unpermitted_policy(self):
self.assertFalse(self.rules_checker.check_property_rules(
'spl_creator_policy', 'update',
create_context(self.policy, ['fake-role'])))
def test_check_property_rules_delete_permitted_specific_policy(self):
self.assertTrue(self.rules_checker.check_property_rules(
'spl_creator_policy', 'delete',
create_context(self.policy, ['admin'])))
def test_check_property_rules_delete_unpermitted_policy(self):
self.assertFalse(self.rules_checker.check_property_rules(
'spl_creator_policy', 'delete',
create_context(self.policy, ['fake-role'])))
def test_property_protection_with_malformed_rule(self):
malformed_rules = {'^[0-9)': {'create': ['fake-policy'],
'read': ['fake-policy'],
'update': ['fake-policy'],
'delete': ['fake-policy']}}
self.set_property_protection_rules(malformed_rules)
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_property_protection_with_multiple_policies(self):
malformed_rules = {'^x_.*': {'create': ['fake-policy, another_pol'],
'read': ['fake-policy'],
'update': ['fake-policy'],
'delete': ['fake-policy']}}
self.set_property_protection_rules(malformed_rules)
self.assertRaises(exception.InvalidPropertyProtectionConfiguration,
property_utils.PropertyRules)
def test_check_property_rules_create_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'create', create_context(self.policy, [''])))
def test_check_property_rules_read_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'read', create_context(self.policy, [''])))
def test_check_property_rules_update_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'update', create_context(self.policy, [''])))
def test_check_property_rules_delete_all_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_all_permitted', 'delete', create_context(self.policy, [''])))
def test_check_property_rules_create_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'create', create_context(self.policy, [''])))
def test_check_property_rules_read_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'read', create_context(self.policy, [''])))
def test_check_property_rules_update_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'update', create_context(self.policy, [''])))
def test_check_property_rules_delete_none_permitted(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_permitted', 'delete', create_context(self.policy, [''])))
def test_check_property_rules_read_none(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_read', 'create',
create_context(self.policy, ['admin', 'member'])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_read', 'read',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_read', 'update',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_read', 'delete',
create_context(self.policy, [''])))
def test_check_property_rules_update_none(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_update', 'create',
create_context(self.policy, ['admin', 'member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_update', 'read',
create_context(self.policy, ['admin', 'member'])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_update', 'update',
create_context(self.policy, [''])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_update', 'delete',
create_context(self.policy, ['admin', 'member'])))
def test_check_property_rules_delete_none(self):
self.rules_checker = property_utils.PropertyRules()
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_delete', 'create',
create_context(self.policy, ['admin', 'member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_delete', 'read',
create_context(self.policy, ['admin', 'member'])))
self.assertTrue(self.rules_checker.check_property_rules(
'x_none_delete', 'update',
create_context(self.policy, ['admin', 'member'])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_none_delete', 'delete',
create_context(self.policy, [''])))
def test_check_return_first_match(self):
self.rules_checker = property_utils.PropertyRules()
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'create',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'read',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'update',
create_context(self.policy, [''])))
self.assertFalse(self.rules_checker.check_property_rules(
'x_foo_matcher', 'delete',
create_context(self.policy, [''])))
glance-16.0.1/glance/tests/unit/common/test_scripts.py 0000666 0001750 0001750 00000002642 13267672245 023040 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import glance.common.scripts as scripts
from glance.common.scripts.image_import import main as image_import
import glance.tests.utils as test_utils
class TestScripts(test_utils.BaseTestCase):
def setUp(self):
super(TestScripts, self).setUp()
def test_run_task(self):
task_id = mock.ANY
task_type = 'import'
context = mock.ANY
task_repo = mock.ANY
image_repo = mock.ANY
image_factory = mock.ANY
with mock.patch.object(image_import, 'run') as mock_run:
scripts.run_task(task_id, task_type, context, task_repo,
image_repo, image_factory)
mock_run.assert_called_once_with(task_id, context, task_repo,
image_repo, image_factory)
glance-16.0.1/glance/tests/unit/common/test_client.py 0000666 0001750 0001750 00000005712 13267672254 022630 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mox3 import mox
from six.moves import http_client
import testtools
from glance.common import auth
from glance.common import client
from glance.tests import utils
class TestClient(testtools.TestCase):
def setUp(self):
super(TestClient, self).setUp()
self.mock = mox.Mox()
self.mock.StubOutWithMock(http_client.HTTPConnection, 'request')
self.mock.StubOutWithMock(http_client.HTTPConnection, 'getresponse')
self.endpoint = 'example.com'
self.client = client.BaseClient(self.endpoint, port=9191,
auth_token=u'abc123')
def tearDown(self):
super(TestClient, self).tearDown()
self.mock.UnsetStubs()
def test_make_auth_plugin(self):
creds = {'strategy': 'keystone'}
insecure = False
configure_via_auth = True
self.mock.StubOutWithMock(auth, 'get_plugin_from_strategy')
auth.get_plugin_from_strategy('keystone', creds, insecure,
configure_via_auth)
self.mock.ReplayAll()
self.client.make_auth_plugin(creds, insecure)
self.mock.VerifyAll()
def test_http_encoding_headers(self):
http_client.HTTPConnection.request(
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
# Lets fake the response
# returned by http_client
fake = utils.FakeHTTPResponse(data=b"Ok")
http_client.HTTPConnection.getresponse().AndReturn(fake)
self.mock.ReplayAll()
headers = {"test": u'ni\xf1o'}
resp = self.client.do_request('GET', '/v1/images/detail',
headers=headers)
self.assertEqual(fake, resp)
def test_http_encoding_params(self):
http_client.HTTPConnection.request(
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
# Lets fake the response
# returned by http_client
fake = utils.FakeHTTPResponse(data=b"Ok")
http_client.HTTPConnection.getresponse().AndReturn(fake)
self.mock.ReplayAll()
params = {"test": u'ni\xf1o'}
resp = self.client.do_request('GET', '/v1/images/detail',
params=params)
self.assertEqual(fake, resp)
glance-16.0.1/glance/tests/unit/test_cached_images.py 0000666 0001750 0001750 00000011157 13267672245 022616 0 ustar zuul zuul 0000000 0000000 # Copyright (C) 2013 Yahoo! Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
import webob
from glance.api import cached_images
from glance.api import policy
from glance.common import exception
from glance import image_cache
class FakePolicyEnforcer(policy.Enforcer):
def __init__(self):
self.default_rule = ''
self.policy_path = ''
self.policy_file_mtime = None
self.policy_file_contents = None
def enforce(self, context, action, target):
return 'pass'
def check(rule, target, creds, exc=None, *args, **kwargs):
return 'pass'
def _check(self, context, rule, target, *args, **kwargs):
return 'pass'
class FakeCache(image_cache.ImageCache):
def __init__(self):
self.init_driver()
self.deleted_images = []
def init_driver(self):
pass
def get_cached_images(self):
return {'id': 'test'}
def delete_cached_image(self, image_id):
self.deleted_images.append(image_id)
def delete_all_cached_images(self):
self.delete_cached_image(self.get_cached_images().get('id'))
return 1
def get_queued_images(self):
return {'test': 'passed'}
def queue_image(self, image_id):
return 'pass'
def delete_queued_image(self, image_id):
self.deleted_images.append(image_id)
def delete_all_queued_images(self):
self.delete_queued_image('deleted_img')
return 1
class FakeController(cached_images.Controller):
def __init__(self):
self.cache = FakeCache()
self.policy = FakePolicyEnforcer()
class TestController(testtools.TestCase):
def test_initialization_without_conf(self):
self.assertRaises(exception.BadDriverConfiguration,
cached_images.Controller)
class TestCachedImages(testtools.TestCase):
def setUp(self):
super(TestCachedImages, self).setUp()
test_controller = FakeController()
self.controller = test_controller
def test_get_cached_images(self):
req = webob.Request.blank('')
req.context = 'test'
result = self.controller.get_cached_images(req)
self.assertEqual({'cached_images': {'id': 'test'}}, result)
def test_delete_cached_image(self):
req = webob.Request.blank('')
req.context = 'test'
self.controller.delete_cached_image(req, image_id='test')
self.assertEqual(['test'], self.controller.cache.deleted_images)
def test_delete_cached_images(self):
req = webob.Request.blank('')
req.context = 'test'
self.assertEqual({'num_deleted': 1},
self.controller.delete_cached_images(req))
self.assertEqual(['test'], self.controller.cache.deleted_images)
def test_policy_enforce_forbidden(self):
def fake_enforce(context, action, target):
raise exception.Forbidden()
self.controller.policy.enforce = fake_enforce
req = webob.Request.blank('')
req.context = 'test'
self.assertRaises(webob.exc.HTTPForbidden,
self.controller.get_cached_images, req)
def test_get_queued_images(self):
req = webob.Request.blank('')
req.context = 'test'
result = self.controller.get_queued_images(req)
self.assertEqual({'queued_images': {'test': 'passed'}}, result)
def test_queue_image(self):
req = webob.Request.blank('')
req.context = 'test'
self.controller.queue_image(req, image_id='test1')
def test_delete_queued_image(self):
req = webob.Request.blank('')
req.context = 'test'
self.controller.delete_queued_image(req, 'deleted_img')
self.assertEqual(['deleted_img'],
self.controller.cache.deleted_images)
def test_delete_queued_images(self):
req = webob.Request.blank('')
req.context = 'test'
self.assertEqual({'num_deleted': 1},
self.controller.delete_queued_images(req))
self.assertEqual(['deleted_img'],
self.controller.cache.deleted_images)
glance-16.0.1/glance/tests/unit/fixtures.py 0000666 0001750 0001750 00000003126 13267672254 020671 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Fixtures for Glance unit tests."""
# NOTE(mriedem): This is needed for importing from fixtures.
from __future__ import absolute_import
import warnings
import fixtures as pyfixtures
class WarningsFixture(pyfixtures.Fixture):
"""Filters out warnings during test runs."""
def setUp(self):
super(WarningsFixture, self).setUp()
# NOTE(sdague): Make deprecation warnings only happen once. Otherwise
# this gets kind of crazy given the way that upstream python libs use
# this.
warnings.simplefilter('once', DeprecationWarning)
# NOTE(sdague): this remains an unresolved item around the way
# forward on is_admin, the deprecation is definitely really premature.
warnings.filterwarnings(
'ignore',
message='Policy enforcement is depending on the value of is_admin.'
' This key is deprecated. Please update your policy '
'file to use the standard policy values.')
self.addCleanup(warnings.resetwarnings)
glance-16.0.1/glance/tests/unit/test_store_image.py 0000666 0001750 0001750 00000112202 13267672254 022351 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cursive import exception as cursive_exception
from cursive import signature_utils
import glance_store
import mock
from glance.common import exception
import glance.location
from glance.tests.unit import base as unit_test_base
from glance.tests.unit import utils as unit_test_utils
from glance.tests import utils
BASE_URI = 'http://storeurl.com/container'
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '228c6da5-29cd-4d67-9457-ed632e083fc0'
class ImageRepoStub(object):
def add(self, image):
return image
def save(self, image, from_state=None):
return image
class ImageStub(object):
def __init__(self, image_id, status=None, locations=None,
visibility=None, extra_properties=None):
self.image_id = image_id
self.status = status
self.locations = locations or []
self.visibility = visibility
self.size = 1
self.extra_properties = extra_properties or {}
def delete(self):
self.status = 'deleted'
def get_member_repo(self):
return FakeMemberRepo(self, [TENANT1, TENANT2])
class ImageFactoryStub(object):
def new_image(self, image_id=None, name=None, visibility='private',
min_disk=0, min_ram=0, protected=False, owner=None,
disk_format=None, container_format=None,
extra_properties=None, tags=None, **other_args):
return ImageStub(image_id, visibility=visibility,
extra_properties=extra_properties, **other_args)
class FakeMemberRepo(object):
def __init__(self, image, tenants=None):
self.image = image
self.factory = glance.domain.ImageMemberFactory()
self.tenants = tenants or []
def list(self, *args, **kwargs):
return [self.factory.new_image_member(self.image, tenant)
for tenant in self.tenants]
def add(self, member):
self.tenants.append(member.member_id)
def remove(self, member):
self.tenants.remove(member.member_id)
class TestStoreImage(utils.BaseTestCase):
def setUp(self):
locations = [{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}, 'status': 'active'}]
self.image_stub = ImageStub(UUID1, 'active', locations)
self.store_api = unit_test_utils.FakeStoreAPI()
self.store_utils = unit_test_utils.FakeStoreUtils(self.store_api)
super(TestStoreImage, self).setUp()
def test_image_delete(self):
image = glance.location.ImageProxy(self.image_stub, {},
self.store_api, self.store_utils)
location = image.locations[0]
self.assertEqual('active', image.status)
self.store_api.get_from_backend(location['url'], context={})
image.delete()
self.assertEqual('deleted', image.status)
self.assertRaises(glance_store.NotFound,
self.store_api.get_from_backend, location['url'], {})
def test_image_get_data(self):
image = glance.location.ImageProxy(self.image_stub, {},
self.store_api, self.store_utils)
self.assertEqual('XXX', image.get_data())
def test_image_get_data_from_second_location(self):
def fake_get_from_backend(self, location, offset=0,
chunk_size=None, context=None):
if UUID1 in location:
raise Exception('not allow download from %s' % location)
else:
return self.data[location]
image1 = glance.location.ImageProxy(self.image_stub, {},
self.store_api, self.store_utils)
self.assertEqual('XXX', image1.get_data())
# Multiple location support
context = glance.context.RequestContext(user=USER1)
(image2, image_stub2) = self._add_image(context, UUID2, 'ZZZ', 3)
location_data = image2.locations[0]
image1.locations.append(location_data)
self.assertEqual(2, len(image1.locations))
self.assertEqual(UUID2, location_data['url'])
self.stubs.Set(unit_test_utils.FakeStoreAPI, 'get_from_backend',
fake_get_from_backend)
# This time, image1.get_data() returns the data wrapped in a
# LimitingReader|CooperativeReader pipeline, so peeking under
# the hood of those objects to get at the underlying string.
self.assertEqual('ZZZ', image1.get_data().data.fd)
image1.locations.pop(0)
self.assertEqual(1, len(image1.locations))
image2.delete()
def test_image_set_data(self):
context = glance.context.RequestContext(user=USER1)
image_stub = ImageStub(UUID2, status='queued', locations=[])
image = glance.location.ImageProxy(image_stub, context,
self.store_api, self.store_utils)
image.set_data('YYYY', 4)
self.assertEqual(4, image.size)
# NOTE(markwash): FakeStore returns image_id for location
self.assertEqual(UUID2, image.locations[0]['url'])
self.assertEqual('Z', image.checksum)
self.assertEqual('active', image.status)
def test_image_set_data_location_metadata(self):
context = glance.context.RequestContext(user=USER1)
image_stub = ImageStub(UUID2, status='queued', locations=[])
loc_meta = {'key': 'value5032'}
store_api = unit_test_utils.FakeStoreAPI(store_metadata=loc_meta)
store_utils = unit_test_utils.FakeStoreUtils(store_api)
image = glance.location.ImageProxy(image_stub, context,
store_api, store_utils)
image.set_data('YYYY', 4)
self.assertEqual(4, image.size)
location_data = image.locations[0]
self.assertEqual(UUID2, location_data['url'])
self.assertEqual(loc_meta, location_data['metadata'])
self.assertEqual('Z', image.checksum)
self.assertEqual('active', image.status)
image.delete()
self.assertEqual(image.status, 'deleted')
self.assertRaises(glance_store.NotFound,
self.store_api.get_from_backend,
image.locations[0]['url'], {})
def test_image_set_data_unknown_size(self):
context = glance.context.RequestContext(user=USER1)
image_stub = ImageStub(UUID2, status='queued', locations=[])
image = glance.location.ImageProxy(image_stub, context,
self.store_api, self.store_utils)
image.set_data('YYYY', None)
self.assertEqual(4, image.size)
# NOTE(markwash): FakeStore returns image_id for location
self.assertEqual(UUID2, image.locations[0]['url'])
self.assertEqual('Z', image.checksum)
self.assertEqual('active', image.status)
image.delete()
self.assertEqual(image.status, 'deleted')
self.assertRaises(glance_store.NotFound,
self.store_api.get_from_backend,
image.locations[0]['url'], context={})
@mock.patch('glance.location.LOG')
def test_image_set_data_valid_signature(self, mock_log):
context = glance.context.RequestContext(user=USER1)
extra_properties = {
'img_signature_certificate_uuid': 'UUID',
'img_signature_hash_method': 'METHOD',
'img_signature_key_type': 'TYPE',
'img_signature': 'VALID'
}
image_stub = ImageStub(UUID2, status='queued',
extra_properties=extra_properties)
self.stubs.Set(signature_utils, 'get_verifier',
unit_test_utils.fake_get_verifier)
image = glance.location.ImageProxy(image_stub, context,
self.store_api, self.store_utils)
image.set_data('YYYY', 4)
self.assertEqual('active', image.status)
mock_log.info.assert_called_once_with(
u'Successfully verified signature for image %s',
UUID2)
def test_image_set_data_invalid_signature(self):
context = glance.context.RequestContext(user=USER1)
extra_properties = {
'img_signature_certificate_uuid': 'UUID',
'img_signature_hash_method': 'METHOD',
'img_signature_key_type': 'TYPE',
'img_signature': 'INVALID'
}
image_stub = ImageStub(UUID2, status='queued',
extra_properties=extra_properties)
self.stubs.Set(signature_utils, 'get_verifier',
unit_test_utils.fake_get_verifier)
image = glance.location.ImageProxy(image_stub, context,
self.store_api, self.store_utils)
self.assertRaises(cursive_exception.SignatureVerificationError,
image.set_data,
'YYYY', 4)
def test_image_set_data_invalid_signature_missing_metadata(self):
context = glance.context.RequestContext(user=USER1)
extra_properties = {
'img_signature_hash_method': 'METHOD',
'img_signature_key_type': 'TYPE',
'img_signature': 'INVALID'
}
image_stub = ImageStub(UUID2, status='queued',
extra_properties=extra_properties)
self.stubs.Set(signature_utils, 'get_verifier',
unit_test_utils.fake_get_verifier)
image = glance.location.ImageProxy(image_stub, context,
self.store_api, self.store_utils)
image.set_data('YYYY', 4)
self.assertEqual(UUID2, image.locations[0]['url'])
self.assertEqual('Z', image.checksum)
# Image is still active, since invalid signature was ignored
self.assertEqual('active', image.status)
def _add_image(self, context, image_id, data, len):
image_stub = ImageStub(image_id, status='queued', locations=[])
image = glance.location.ImageProxy(image_stub, context,
self.store_api, self.store_utils)
image.set_data(data, len)
self.assertEqual(len, image.size)
# NOTE(markwash): FakeStore returns image_id for location
location = {'url': image_id, 'metadata': {}, 'status': 'active'}
self.assertEqual([location], image.locations)
self.assertEqual([location], image_stub.locations)
self.assertEqual('active', image.status)
return (image, image_stub)
def test_image_change_append_invalid_location_uri(self):
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
image1.locations.append, location_bad)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
def test_image_change_append_invalid_location_metatdata(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
# Using only one test rule here is enough to make sure
# 'store.check_location_metadata()' can be triggered
# in Location proxy layer. Complete test rule for
# 'store.check_location_metadata()' testing please
# check below cases within 'TestStoreMetaDataChecker'.
location_bad = {'url': UUID3, 'metadata': b"a invalid metadata"}
self.assertRaises(glance_store.BackendException,
image1.locations.append, location_bad)
image1.delete()
image2.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
def test_image_change_append_locations(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
image1.locations.append(location3)
self.assertEqual([location2, location3], image_stub1.locations)
self.assertEqual([location2, location3], image1.locations)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image2.delete()
def test_image_change_pop_location(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
image1.locations.append(location3)
self.assertEqual([location2, location3], image_stub1.locations)
self.assertEqual([location2, location3], image1.locations)
image1.locations.pop()
self.assertEqual([location2], image_stub1.locations)
self.assertEqual([location2], image1.locations)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image2.delete()
def test_image_change_extend_invalid_locations_uri(self):
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
image1.locations.extend, [location_bad])
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
def test_image_change_extend_invalid_locations_metadata(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location_bad = {'url': UUID3, 'metadata': b"a invalid metadata"}
self.assertRaises(glance_store.BackendException,
image1.locations.extend, [location_bad])
image1.delete()
image2.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
def test_image_change_extend_locations(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
image1.locations.extend([location3])
self.assertEqual([location2, location3], image_stub1.locations)
self.assertEqual([location2, location3], image1.locations)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image2.delete()
def test_image_change_remove_location(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
location_bad = {'url': 'unknown://location', 'metadata': {}}
image1.locations.extend([location3])
image1.locations.remove(location2)
self.assertEqual([location3], image_stub1.locations)
self.assertEqual([location3], image1.locations)
self.assertRaises(ValueError,
image1.locations.remove, location_bad)
image1.delete()
image2.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
def test_image_change_delete_location(self):
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
del image1.locations[0]
self.assertEqual([], image_stub1.locations)
self.assertEqual(0, len(image1.locations))
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
image1.delete()
def test_image_change_insert_invalid_location_uri(self):
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
image1.locations.insert, 0, location_bad)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
def test_image_change_insert_invalid_location_metadata(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location_bad = {'url': UUID3, 'metadata': b"a invalid metadata"}
self.assertRaises(glance_store.BackendException,
image1.locations.insert, 0, location_bad)
image1.delete()
image2.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
def test_image_change_insert_location(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
image1.locations.insert(0, location3)
self.assertEqual([location3, location2], image_stub1.locations)
self.assertEqual([location3, location2], image1.locations)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image2.delete()
def test_image_change_delete_locations(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image1.locations.insert(0, location3)
del image1.locations[0:100]
self.assertEqual([], image_stub1.locations)
self.assertEqual(0, len(image1.locations))
self.assertRaises(exception.BadStoreUri,
image1.locations.insert, 0, location2)
self.assertRaises(exception.BadStoreUri,
image2.locations.insert, 0, location3)
image1.delete()
image2.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
def test_image_change_adding_invalid_location_uri(self):
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
image_stub1 = ImageStub('fake_image_id', status='queued', locations=[])
image1 = glance.location.ImageProxy(image_stub1, context,
self.store_api, self.store_utils)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
image1.locations.__iadd__, [location_bad])
self.assertEqual([], image_stub1.locations)
self.assertEqual([], image1.locations)
image1.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
def test_image_change_adding_invalid_location_metadata(self):
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
image_stub2 = ImageStub('fake_image_id', status='queued', locations=[])
image2 = glance.location.ImageProxy(image_stub2, context,
self.store_api, self.store_utils)
location_bad = {'url': UUID2, 'metadata': b"a invalid metadata"}
self.assertRaises(glance_store.BackendException,
image2.locations.__iadd__, [location_bad])
self.assertEqual([], image_stub2.locations)
self.assertEqual([], image2.locations)
image1.delete()
image2.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
def test_image_change_adding_locations(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.location.ImageProxy(image_stub3, context,
self.store_api, self.store_utils)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image3.locations += [location2, location3]
self.assertEqual([location2, location3], image_stub3.locations)
self.assertEqual([location2, location3], image3.locations)
image3.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image1.delete()
image2.delete()
def test_image_get_location_index(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.location.ImageProxy(image_stub3, context,
self.store_api, self.store_utils)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image3.locations += [location2, location3]
self.assertEqual(1, image_stub3.locations.index(location3))
image3.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image1.delete()
image2.delete()
def test_image_get_location_by_index(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.location.ImageProxy(image_stub3, context,
self.store_api, self.store_utils)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image3.locations += [location2, location3]
self.assertEqual(1, image_stub3.locations.index(location3))
self.assertEqual(location2, image_stub3.locations[0])
image3.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image1.delete()
image2.delete()
def test_image_checking_location_exists(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.location.ImageProxy(image_stub3, context,
self.store_api, self.store_utils)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
location_bad = {'url': 'unknown://location', 'metadata': {}}
image3.locations += [location2, location3]
self.assertIn(location3, image_stub3.locations)
self.assertNotIn(location_bad, image_stub3.locations)
image3.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image1.delete()
image2.delete()
def test_image_reverse_locations_order(self):
UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
self.assertEqual(2, len(self.store_api.data.keys()))
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.location.ImageProxy(image_stub3, context,
self.store_api, self.store_utils)
image3.locations += [location2, location3]
image_stub3.locations.reverse()
self.assertEqual([location3, location2], image_stub3.locations)
self.assertEqual([location3, location2], image3.locations)
image3.delete()
self.assertEqual(2, len(self.store_api.data.keys()))
self.assertNotIn(UUID2, self.store_api.data.keys())
self.assertNotIn(UUID3, self.store_api.data.keys())
image1.delete()
image2.delete()
class TestStoreImageRepo(utils.BaseTestCase):
def setUp(self):
super(TestStoreImageRepo, self).setUp()
self.store_api = unit_test_utils.FakeStoreAPI()
store_utils = unit_test_utils.FakeStoreUtils(self.store_api)
self.image_stub = ImageStub(UUID1)
self.image = glance.location.ImageProxy(self.image_stub, {},
self.store_api, store_utils)
self.image_repo_stub = ImageRepoStub()
self.image_repo = glance.location.ImageRepoProxy(self.image_repo_stub,
{}, self.store_api,
store_utils)
patcher = mock.patch("glance.location._get_member_repo_for_store",
self.get_fake_member_repo)
patcher.start()
self.addCleanup(patcher.stop)
self.fake_member_repo = FakeMemberRepo(self.image, [TENANT1, TENANT2])
self.image_member_repo = glance.location.ImageMemberRepoProxy(
self.fake_member_repo,
self.image,
{}, self.store_api)
def get_fake_member_repo(self, image, context, db_api, store_api):
return FakeMemberRepo(self.image, [TENANT1, TENANT2])
def test_add_updates_acls(self):
self.image_stub.locations = [{'url': 'foo', 'metadata': {},
'status': 'active'},
{'url': 'bar', 'metadata': {},
'status': 'active'}]
self.image_stub.visibility = 'public'
self.image_repo.add(self.image)
self.assertTrue(self.store_api.acls['foo']['public'])
self.assertEqual([], self.store_api.acls['foo']['read'])
self.assertEqual([], self.store_api.acls['foo']['write'])
self.assertTrue(self.store_api.acls['bar']['public'])
self.assertEqual([], self.store_api.acls['bar']['read'])
self.assertEqual([], self.store_api.acls['bar']['write'])
def test_add_ignores_acls_if_no_locations(self):
self.image_stub.locations = []
self.image_stub.visibility = 'public'
self.image_repo.add(self.image)
self.assertEqual(0, len(self.store_api.acls))
def test_save_updates_acls(self):
self.image_stub.locations = [{'url': 'foo', 'metadata': {},
'status': 'active'}]
self.image_repo.save(self.image)
self.assertIn('foo', self.store_api.acls)
def test_add_fetches_members_if_private(self):
self.image_stub.locations = [{'url': 'glue', 'metadata': {},
'status': 'active'}]
self.image_stub.visibility = 'private'
self.image_repo.add(self.image)
self.assertIn('glue', self.store_api.acls)
acls = self.store_api.acls['glue']
self.assertFalse(acls['public'])
self.assertEqual([], acls['write'])
self.assertEqual([TENANT1, TENANT2], acls['read'])
def test_save_fetches_members_if_private(self):
self.image_stub.locations = [{'url': 'glue', 'metadata': {},
'status': 'active'}]
self.image_stub.visibility = 'private'
self.image_repo.save(self.image)
self.assertIn('glue', self.store_api.acls)
acls = self.store_api.acls['glue']
self.assertFalse(acls['public'])
self.assertEqual([], acls['write'])
self.assertEqual([TENANT1, TENANT2], acls['read'])
def test_member_addition_updates_acls(self):
self.image_stub.locations = [{'url': 'glug', 'metadata': {},
'status': 'active'}]
self.image_stub.visibility = 'private'
membership = glance.domain.ImageMembership(
UUID1, TENANT3, None, None, status='accepted')
self.image_member_repo.add(membership)
self.assertIn('glug', self.store_api.acls)
acls = self.store_api.acls['glug']
self.assertFalse(acls['public'])
self.assertEqual([], acls['write'])
self.assertEqual([TENANT1, TENANT2, TENANT3], acls['read'])
def test_member_removal_updates_acls(self):
self.image_stub.locations = [{'url': 'glug', 'metadata': {},
'status': 'active'}]
self.image_stub.visibility = 'private'
membership = glance.domain.ImageMembership(
UUID1, TENANT1, None, None, status='accepted')
self.image_member_repo.remove(membership)
self.assertIn('glug', self.store_api.acls)
acls = self.store_api.acls['glug']
self.assertFalse(acls['public'])
self.assertEqual([], acls['write'])
self.assertEqual([TENANT2], acls['read'])
class TestImageFactory(unit_test_base.StoreClearingUnitTest):
def setUp(self):
super(TestImageFactory, self).setUp()
store_api = unit_test_utils.FakeStoreAPI()
store_utils = unit_test_utils.FakeStoreUtils(store_api)
self.image_factory = glance.location.ImageFactoryProxy(
ImageFactoryStub(),
glance.context.RequestContext(user=USER1),
store_api,
store_utils)
def test_new_image(self):
image = self.image_factory.new_image()
self.assertIsNone(image.image_id)
self.assertIsNone(image.status)
self.assertEqual('private', image.visibility)
self.assertEqual([], image.locations)
def test_new_image_with_location(self):
locations = [{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}}]
image = self.image_factory.new_image(locations=locations)
self.assertEqual(locations, image.locations)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
self.image_factory.new_image,
locations=[location_bad])
class TestStoreMetaDataChecker(utils.BaseTestCase):
def test_empty(self):
glance_store.check_location_metadata({})
def test_unicode(self):
m = {'key': u'somevalue'}
glance_store.check_location_metadata(m)
def test_unicode_list(self):
m = {'key': [u'somevalue', u'2']}
glance_store.check_location_metadata(m)
def test_unicode_dict(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
m = {'topkey': inner}
glance_store.check_location_metadata(m)
def test_unicode_dict_list(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
m = {'topkey': inner, 'list': [u'somevalue', u'2'], 'u': u'2'}
glance_store.check_location_metadata(m)
def test_nested_dict(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
inner = {'newkey': inner}
inner = {'anotherkey': inner}
m = {'topkey': inner}
glance_store.check_location_metadata(m)
def test_simple_bad(self):
m = {'key1': object()}
self.assertRaises(glance_store.BackendException,
glance_store.check_location_metadata,
m)
def test_list_bad(self):
m = {'key1': [u'somevalue', object()]}
self.assertRaises(glance_store.BackendException,
glance_store.check_location_metadata,
m)
def test_nested_dict_bad(self):
inner = {'key1': u'somevalue', 'key2': object()}
inner = {'newkey': inner}
inner = {'anotherkey': inner}
m = {'topkey': inner}
self.assertRaises(glance_store.BackendException,
glance_store.check_location_metadata,
m)
glance-16.0.1/glance/tests/unit/fake_rados.py 0000666 0001750 0001750 00000006116 13267672245 021120 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 Canonical Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class mock_rados(object):
class ioctx(object):
def __init__(self, *args, **kwargs):
pass
def __enter__(self, *args, **kwargs):
return self
def __exit__(self, *args, **kwargs):
return False
def close(self, *args, **kwargs):
pass
class Rados(object):
def __init__(self, *args, **kwargs):
pass
def __enter__(self, *args, **kwargs):
return self
def __exit__(self, *args, **kwargs):
return False
def connect(self, *args, **kwargs):
pass
def open_ioctx(self, *args, **kwargs):
return mock_rados.ioctx()
def shutdown(self, *args, **kwargs):
pass
class mock_rbd(object):
class ImageExists(Exception):
pass
class ImageBusy(Exception):
pass
class ImageNotFound(Exception):
pass
class Image(object):
def __init__(self, *args, **kwargs):
pass
def __enter__(self, *args, **kwargs):
return self
def __exit__(self, *args, **kwargs):
pass
def create_snap(self, *args, **kwargs):
pass
def remove_snap(self, *args, **kwargs):
pass
def protect_snap(self, *args, **kwargs):
pass
def unprotect_snap(self, *args, **kwargs):
pass
def read(self, *args, **kwargs):
raise NotImplementedError()
def write(self, *args, **kwargs):
raise NotImplementedError()
def resize(self, *args, **kwargs):
raise NotImplementedError()
def discard(self, offset, length):
raise NotImplementedError()
def close(self):
pass
def list_snaps(self):
raise NotImplementedError()
def parent_info(self):
raise NotImplementedError()
def size(self):
raise NotImplementedError()
class RBD(object):
def __init__(self, *args, **kwargs):
pass
def __enter__(self, *args, **kwargs):
return self
def __exit__(self, *args, **kwargs):
return False
def create(self, *args, **kwargs):
pass
def remove(self, *args, **kwargs):
pass
def list(self, *args, **kwargs):
raise NotImplementedError()
def clone(self, *args, **kwargs):
raise NotImplementedError()
glance-16.0.1/glance/tests/unit/test_domain_proxy.py 0000666 0001750 0001750 00000024641 13267672245 022574 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 OpenStack Foundation.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.domain import proxy
import glance.tests.utils as test_utils
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
class FakeProxy(object):
def __init__(self, base, *args, **kwargs):
self.base = base
self.args = args
self.kwargs = kwargs
class FakeRepo(object):
def __init__(self, result=None):
self.args = None
self.kwargs = None
self.result = result
def fake_method(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
return self.result
get = fake_method
list = fake_method
add = fake_method
save = fake_method
remove = fake_method
class TestProxyRepoPlain(test_utils.BaseTestCase):
def setUp(self):
super(TestProxyRepoPlain, self).setUp()
self.fake_repo = FakeRepo()
self.proxy_repo = proxy.Repo(self.fake_repo)
def _test_method(self, name, base_result, *args, **kwargs):
self.fake_repo.result = base_result
method = getattr(self.proxy_repo, name)
proxy_result = method(*args, **kwargs)
self.assertEqual(base_result, proxy_result)
self.assertEqual(args, self.fake_repo.args)
self.assertEqual(kwargs, self.fake_repo.kwargs)
def test_get(self):
self._test_method('get', 'snarf', 'abcd')
def test_list(self):
self._test_method('list', ['sniff', 'snarf'], 2, filter='^sn')
def test_add(self):
self._test_method('add', 'snuff', 'enough')
def test_save(self):
self._test_method('save', 'snuff', 'enough', from_state=None)
def test_remove(self):
self._test_method('add', None, 'flying')
class TestProxyRepoWrapping(test_utils.BaseTestCase):
def setUp(self):
super(TestProxyRepoWrapping, self).setUp()
self.fake_repo = FakeRepo()
self.proxy_repo = proxy.Repo(self.fake_repo,
item_proxy_class=FakeProxy,
item_proxy_kwargs={'a': 1})
def _test_method(self, name, base_result, *args, **kwargs):
self.fake_repo.result = base_result
method = getattr(self.proxy_repo, name)
proxy_result = method(*args, **kwargs)
self.assertIsInstance(proxy_result, FakeProxy)
self.assertEqual(base_result, proxy_result.base)
self.assertEqual(0, len(proxy_result.args))
self.assertEqual({'a': 1}, proxy_result.kwargs)
self.assertEqual(args, self.fake_repo.args)
self.assertEqual(kwargs, self.fake_repo.kwargs)
def test_get(self):
self.fake_repo.result = 'snarf'
result = self.proxy_repo.get('some-id')
self.assertIsInstance(result, FakeProxy)
self.assertEqual(('some-id',), self.fake_repo.args)
self.assertEqual({}, self.fake_repo.kwargs)
self.assertEqual('snarf', result.base)
self.assertEqual(tuple(), result.args)
self.assertEqual({'a': 1}, result.kwargs)
def test_list(self):
self.fake_repo.result = ['scratch', 'sniff']
results = self.proxy_repo.list(2, prefix='s')
self.assertEqual((2,), self.fake_repo.args)
self.assertEqual({'prefix': 's'}, self.fake_repo.kwargs)
self.assertEqual(2, len(results))
for i in range(2):
self.assertIsInstance(results[i], FakeProxy)
self.assertEqual(self.fake_repo.result[i], results[i].base)
self.assertEqual(tuple(), results[i].args)
self.assertEqual({'a': 1}, results[i].kwargs)
def _test_method_with_proxied_argument(self, name, result, **kwargs):
self.fake_repo.result = result
item = FakeProxy('snoop')
method = getattr(self.proxy_repo, name)
proxy_result = method(item)
self.assertEqual(('snoop',), self.fake_repo.args)
self.assertEqual(kwargs, self.fake_repo.kwargs)
if result is None:
self.assertIsNone(proxy_result)
else:
self.assertIsInstance(proxy_result, FakeProxy)
self.assertEqual(result, proxy_result.base)
self.assertEqual(tuple(), proxy_result.args)
self.assertEqual({'a': 1}, proxy_result.kwargs)
def test_add(self):
self._test_method_with_proxied_argument('add', 'dog')
def test_add_with_no_result(self):
self._test_method_with_proxied_argument('add', None)
def test_save(self):
self._test_method_with_proxied_argument('save', 'dog',
from_state=None)
def test_save_with_no_result(self):
self._test_method_with_proxied_argument('save', None,
from_state=None)
def test_remove(self):
self._test_method_with_proxied_argument('remove', 'dog')
def test_remove_with_no_result(self):
self._test_method_with_proxied_argument('remove', None)
class FakeImageFactory(object):
def __init__(self, result=None):
self.result = None
self.kwargs = None
def new_image(self, **kwargs):
self.kwargs = kwargs
return self.result
class TestImageFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestImageFactory, self).setUp()
self.factory = FakeImageFactory()
def test_proxy_plain(self):
proxy_factory = proxy.ImageFactory(self.factory)
self.factory.result = 'eddard'
image = proxy_factory.new_image(a=1, b='two')
self.assertEqual('eddard', image)
self.assertEqual({'a': 1, 'b': 'two'}, self.factory.kwargs)
def test_proxy_wrapping(self):
proxy_factory = proxy.ImageFactory(self.factory,
proxy_class=FakeProxy,
proxy_kwargs={'dog': 'bark'})
self.factory.result = 'stark'
image = proxy_factory.new_image(a=1, b='two')
self.assertIsInstance(image, FakeProxy)
self.assertEqual('stark', image.base)
self.assertEqual({'a': 1, 'b': 'two'}, self.factory.kwargs)
class FakeImageMembershipFactory(object):
def __init__(self, result=None):
self.result = None
self.image = None
self.member_id = None
def new_image_member(self, image, member_id):
self.image = image
self.member_id = member_id
return self.result
class TestImageMembershipFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestImageMembershipFactory, self).setUp()
self.factory = FakeImageMembershipFactory()
def test_proxy_plain(self):
proxy_factory = proxy.ImageMembershipFactory(self.factory)
self.factory.result = 'tyrion'
membership = proxy_factory.new_image_member('jaime', 'cersei')
self.assertEqual('tyrion', membership)
self.assertEqual('jaime', self.factory.image)
self.assertEqual('cersei', self.factory.member_id)
def test_proxy_wrapped_membership(self):
proxy_factory = proxy.ImageMembershipFactory(
self.factory, proxy_class=FakeProxy, proxy_kwargs={'a': 1})
self.factory.result = 'tyrion'
membership = proxy_factory.new_image_member('jaime', 'cersei')
self.assertIsInstance(membership, FakeProxy)
self.assertEqual('tyrion', membership.base)
self.assertEqual({'a': 1}, membership.kwargs)
self.assertEqual('jaime', self.factory.image)
self.assertEqual('cersei', self.factory.member_id)
def test_proxy_wrapped_image(self):
proxy_factory = proxy.ImageMembershipFactory(
self.factory, proxy_class=FakeProxy)
self.factory.result = 'tyrion'
image = FakeProxy('jaime')
membership = proxy_factory.new_image_member(image, 'cersei')
self.assertIsInstance(membership, FakeProxy)
self.assertIsInstance(self.factory.image, FakeProxy)
self.assertEqual('cersei', self.factory.member_id)
def test_proxy_both_wrapped(self):
class FakeProxy2(FakeProxy):
pass
proxy_factory = proxy.ImageMembershipFactory(
self.factory,
proxy_class=FakeProxy,
proxy_kwargs={'b': 2})
self.factory.result = 'tyrion'
image = FakeProxy2('jaime')
membership = proxy_factory.new_image_member(image, 'cersei')
self.assertIsInstance(membership, FakeProxy)
self.assertEqual('tyrion', membership.base)
self.assertEqual({'b': 2}, membership.kwargs)
self.assertIsInstance(self.factory.image, FakeProxy2)
self.assertEqual('cersei', self.factory.member_id)
class FakeImage(object):
def __init__(self, result=None):
self.result = result
class TestTaskFactory(test_utils.BaseTestCase):
def setUp(self):
super(TestTaskFactory, self).setUp()
self.factory = mock.Mock()
self.fake_type = 'import'
self.fake_owner = "owner"
def test_proxy_plain(self):
proxy_factory = proxy.TaskFactory(self.factory)
proxy_factory.new_task(
type=self.fake_type,
owner=self.fake_owner
)
self.factory.new_task.assert_called_once_with(
type=self.fake_type,
owner=self.fake_owner
)
def test_proxy_wrapping(self):
proxy_factory = proxy.TaskFactory(
self.factory,
task_proxy_class=FakeProxy,
task_proxy_kwargs={'dog': 'bark'})
self.factory.new_task.return_value = 'fake_task'
task = proxy_factory.new_task(
type=self.fake_type,
owner=self.fake_owner
)
self.factory.new_task.assert_called_once_with(
type=self.fake_type,
owner=self.fake_owner
)
self.assertIsInstance(task, FakeProxy)
self.assertEqual('fake_task', task.base)
glance-16.0.1/glance/tests/unit/test_glance_replicator.py 0000666 0001750 0001750 00000056027 13267672245 023544 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 Michael Still and Canonical Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import copy
import os
import sys
import uuid
import fixtures
import mock
from oslo_serialization import jsonutils
import six
from six import moves
from six.moves import http_client as http
import webob
from glance.cmd import replicator as glance_replicator
from glance.common import exception
from glance.tests.unit import utils as unit_test_utils
from glance.tests import utils as test_utils
IMG_RESPONSE_ACTIVE = {
'content-length': '0',
'property-image_state': 'available',
'min_ram': '0',
'disk_format': 'aki',
'updated_at': '2012-06-25T02:10:36',
'date': 'Thu, 28 Jun 2012 07:20:05 GMT',
'owner': '8aef75b5c0074a59aa99188fdb4b9e90',
'id': '6d55dd55-053a-4765-b7bc-b30df0ea3861',
'size': '4660272',
'property-image_location': 'ubuntu-bucket/oneiric-server-cloudimg-amd64-'
'vmlinuz-generic.manifest.xml',
'property-architecture': 'x86_64',
'etag': 'f46cfe7fb3acaff49a3567031b9b53bb',
'location': 'http://127.0.0.1:9292/v1/images/'
'6d55dd55-053a-4765-b7bc-b30df0ea3861',
'container_format': 'aki',
'status': 'active',
'deleted': 'False',
'min_disk': '0',
'is_public': 'False',
'name': 'ubuntu-bucket/oneiric-server-cloudimg-amd64-vmlinuz-generic',
'checksum': 'f46cfe7fb3acaff49a3567031b9b53bb',
'created_at': '2012-06-25T02:10:32',
'protected': 'False',
'content-type': 'text/html; charset=UTF-8'
}
IMG_RESPONSE_QUEUED = copy.copy(IMG_RESPONSE_ACTIVE)
IMG_RESPONSE_QUEUED['status'] = 'queued'
IMG_RESPONSE_QUEUED['id'] = '49b2c782-ee10-4692-84f8-3942e9432c4b'
IMG_RESPONSE_QUEUED['location'] = ('http://127.0.0.1:9292/v1/images/'
+ IMG_RESPONSE_QUEUED['id'])
class FakeHTTPConnection(object):
def __init__(self):
self.count = 0
self.reqs = {}
self.last_req = None
self.host = 'localhost'
self.port = 9292
def prime_request(self, method, url, in_body, in_headers,
out_code, out_body, out_headers):
if not url.startswith('/'):
url = '/' + url
url = unit_test_utils.sort_url_by_qs_keys(url)
hkeys = sorted(in_headers.keys())
hashable = (method, url, in_body, ' '.join(hkeys))
flat_headers = []
for key in out_headers:
flat_headers.append((key, out_headers[key]))
self.reqs[hashable] = (out_code, out_body, flat_headers)
def request(self, method, url, body, headers):
self.count += 1
url = unit_test_utils.sort_url_by_qs_keys(url)
hkeys = sorted(headers.keys())
hashable = (method, url, body, ' '.join(hkeys))
if hashable not in self.reqs:
options = []
for h in self.reqs:
options.append(repr(h))
raise Exception('No such primed request: %s "%s"\n'
'%s\n\n'
'Available:\n'
'%s'
% (method, url, hashable, '\n\n'.join(options)))
self.last_req = hashable
def getresponse(self):
class FakeResponse(object):
def __init__(self, args):
(code, body, headers) = args
self.body = six.StringIO(body)
self.headers = headers
self.status = code
def read(self, count=1000000):
return self.body.read(count)
def getheaders(self):
return self.headers
return FakeResponse(self.reqs[self.last_req])
class ImageServiceTestCase(test_utils.BaseTestCase):
def test_rest_errors(self):
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
for code, exc in [(http.BAD_REQUEST, webob.exc.HTTPBadRequest),
(http.UNAUTHORIZED, webob.exc.HTTPUnauthorized),
(http.FORBIDDEN, webob.exc.HTTPForbidden),
(http.CONFLICT, webob.exc.HTTPConflict),
(http.INTERNAL_SERVER_ERROR,
webob.exc.HTTPInternalServerError)]:
c.conn.prime_request('GET',
('v1/images/'
'5dcddce0-cba5-4f18-9cf4-9853c7b207a6'), '',
{'x-auth-token': 'noauth'}, code, '', {})
self.assertRaises(exc, c.get_image,
'5dcddce0-cba5-4f18-9cf4-9853c7b207a6')
def test_rest_get_images(self):
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
# Two images, one of which is queued
resp = {'images': [IMG_RESPONSE_ACTIVE, IMG_RESPONSE_QUEUED]}
c.conn.prime_request('GET', 'v1/images/detail?is_public=None', '',
{'x-auth-token': 'noauth'},
http.OK, jsonutils.dumps(resp), {})
c.conn.prime_request('GET',
('v1/images/detail?marker=%s&is_public=None'
% IMG_RESPONSE_QUEUED['id']),
'', {'x-auth-token': 'noauth'},
http.OK, jsonutils.dumps({'images': []}), {})
imgs = list(c.get_images())
self.assertEqual(2, len(imgs))
self.assertEqual(2, c.conn.count)
def test_rest_get_image(self):
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
image_contents = 'THISISTHEIMAGEBODY'
c.conn.prime_request('GET',
'v1/images/%s' % IMG_RESPONSE_ACTIVE['id'],
'', {'x-auth-token': 'noauth'},
http.OK, image_contents, IMG_RESPONSE_ACTIVE)
body = c.get_image(IMG_RESPONSE_ACTIVE['id'])
self.assertEqual(image_contents, body.read())
def test_rest_header_list_to_dict(self):
i = [('x-image-meta-banana', 42),
('gerkin', 12),
('x-image-meta-property-frog', 11),
('x-image-meta-property-duck', 12)]
o = glance_replicator.ImageService._header_list_to_dict(i)
self.assertIn('banana', o)
self.assertIn('gerkin', o)
self.assertIn('properties', o)
self.assertIn('frog', o['properties'])
self.assertIn('duck', o['properties'])
self.assertNotIn('x-image-meta-banana', o)
def test_rest_get_image_meta(self):
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
c.conn.prime_request('HEAD',
'v1/images/%s' % IMG_RESPONSE_ACTIVE['id'],
'', {'x-auth-token': 'noauth'},
http.OK, '', IMG_RESPONSE_ACTIVE)
header = c.get_image_meta(IMG_RESPONSE_ACTIVE['id'])
self.assertIn('id', header)
def test_rest_dict_to_headers(self):
i = {'banana': 42,
'gerkin': 12,
'properties': {'frog': 1,
'kernel_id': None}
}
o = glance_replicator.ImageService._dict_to_headers(i)
self.assertIn('x-image-meta-banana', o)
self.assertIn('x-image-meta-gerkin', o)
self.assertIn('x-image-meta-property-frog', o)
self.assertIn('x-image-meta-property-kernel_id', o)
self.assertEqual(o['x-image-meta-property-kernel_id'], '')
self.assertNotIn('properties', o)
def test_rest_add_image(self):
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
image_body = 'THISISANIMAGEBODYFORSURE!'
image_meta_with_proto = {
'x-auth-token': 'noauth',
'Content-Type': 'application/octet-stream',
'Content-Length': len(image_body)
}
for key in IMG_RESPONSE_ACTIVE:
image_meta_with_proto[
'x-image-meta-%s' % key] = IMG_RESPONSE_ACTIVE[key]
c.conn.prime_request('POST', 'v1/images',
image_body, image_meta_with_proto,
http.OK, '', IMG_RESPONSE_ACTIVE)
headers, body = c.add_image(IMG_RESPONSE_ACTIVE, image_body)
self.assertEqual(IMG_RESPONSE_ACTIVE, headers)
self.assertEqual(1, c.conn.count)
def test_rest_add_image_meta(self):
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
image_meta = {'id': '5dcddce0-cba5-4f18-9cf4-9853c7b207a6'}
image_meta_headers = glance_replicator.ImageService._dict_to_headers(
image_meta)
image_meta_headers['x-auth-token'] = 'noauth'
image_meta_headers['Content-Type'] = 'application/octet-stream'
c.conn.prime_request('PUT', 'v1/images/%s' % image_meta['id'],
'', image_meta_headers, http.OK, '', '')
headers, body = c.add_image_meta(image_meta)
class FakeHttpResponse(object):
def __init__(self, headers, data):
self.headers = headers
self.data = six.BytesIO(data)
def getheaders(self):
return self.headers
def read(self, amt=None):
return self.data.read(amt)
FAKEIMAGES = [{'status': 'active', 'size': 100, 'dontrepl': 'banana',
'id': '5dcddce0-cba5-4f18-9cf4-9853c7b207a6', 'name': 'x1'},
{'status': 'deleted', 'size': 200, 'dontrepl': 'banana',
'id': 'f4da1d2a-40e8-4710-b3aa-0222a4cc887b', 'name': 'x2'},
{'status': 'active', 'size': 300, 'dontrepl': 'banana',
'id': '37ff82db-afca-48c7-ae0b-ddc7cf83e3db', 'name': 'x3'}]
FAKEIMAGES_LIVEMASTER = [{'status': 'active', 'size': 100,
'dontrepl': 'banana', 'name': 'x1',
'id': '5dcddce0-cba5-4f18-9cf4-9853c7b207a6'},
{'status': 'deleted', 'size': 200,
'dontrepl': 'banana', 'name': 'x2',
'id': 'f4da1d2a-40e8-4710-b3aa-0222a4cc887b'},
{'status': 'deleted', 'size': 300,
'dontrepl': 'banana', 'name': 'x3',
'id': '37ff82db-afca-48c7-ae0b-ddc7cf83e3db'},
{'status': 'active', 'size': 100,
'dontrepl': 'banana', 'name': 'x4',
'id': '15648dd7-8dd0-401c-bd51-550e1ba9a088'}]
class FakeImageService(object):
def __init__(self, http_conn, authtoken):
self.authtoken = authtoken
def get_images(self):
if self.authtoken == 'livesourcetoken':
return FAKEIMAGES_LIVEMASTER
return FAKEIMAGES
def get_image(self, id):
return FakeHttpResponse({}, b'data')
def get_image_meta(self, id):
for img in FAKEIMAGES:
if img['id'] == id:
return img
return {}
def add_image_meta(self, meta):
return {'status': http.OK}, None
def add_image(self, meta, data):
return {'status': http.OK}, None
def get_image_service():
return FakeImageService
def check_no_args(command, args):
options = moves.UserDict()
no_args_error = False
orig_img_service = glance_replicator.get_image_service
try:
glance_replicator.get_image_service = get_image_service
command(options, args)
except TypeError as e:
if str(e) == "Too few arguments.":
no_args_error = True
finally:
glance_replicator.get_image_service = orig_img_service
return no_args_error
def check_bad_args(command, args):
options = moves.UserDict()
bad_args_error = False
orig_img_service = glance_replicator.get_image_service
try:
glance_replicator.get_image_service = get_image_service
command(options, args)
except ValueError:
bad_args_error = True
finally:
glance_replicator.get_image_service = orig_img_service
return bad_args_error
class ReplicationCommandsTestCase(test_utils.BaseTestCase):
@mock.patch.object(glance_replicator, 'lookup_command')
def test_help(self, mock_lookup_command):
option = mock.Mock()
mock_lookup_command.return_value = "fake_return"
glance_replicator.print_help(option, [])
glance_replicator.print_help(option, ['dump'])
glance_replicator.print_help(option, ['fake_command'])
self.assertEqual(2, mock_lookup_command.call_count)
def test_replication_size(self):
options = moves.UserDict()
options.targettoken = 'targettoken'
args = ['localhost:9292']
stdout = sys.stdout
orig_img_service = glance_replicator.get_image_service
sys.stdout = six.StringIO()
try:
glance_replicator.get_image_service = get_image_service
glance_replicator.replication_size(options, args)
sys.stdout.seek(0)
output = sys.stdout.read()
finally:
sys.stdout = stdout
glance_replicator.get_image_service = orig_img_service
output = output.rstrip()
self.assertEqual(
'Total size is 400 bytes (400.0 B) across 2 images',
output
)
def test_replication_size_with_no_args(self):
args = []
command = glance_replicator.replication_size
self.assertTrue(check_no_args(command, args))
def test_replication_size_with_args_is_None(self):
args = None
command = glance_replicator.replication_size
self.assertTrue(check_no_args(command, args))
def test_replication_size_with_bad_args(self):
args = ['aaa']
command = glance_replicator.replication_size
self.assertTrue(check_bad_args(command, args))
def test_human_readable_size(self):
_human_readable_size = glance_replicator._human_readable_size
self.assertEqual('0.0 B', _human_readable_size(0))
self.assertEqual('1.0 B', _human_readable_size(1))
self.assertEqual('512.0 B', _human_readable_size(512))
self.assertEqual('1.0 KiB', _human_readable_size(1024))
self.assertEqual('2.0 KiB', _human_readable_size(2048))
self.assertEqual('8.0 KiB', _human_readable_size(8192))
self.assertEqual('64.0 KiB', _human_readable_size(65536))
self.assertEqual('93.3 KiB', _human_readable_size(95536))
self.assertEqual('117.7 MiB', _human_readable_size(123456789))
self.assertEqual('36.3 GiB', _human_readable_size(39022543360))
def test_replication_dump(self):
tempdir = self.useFixture(fixtures.TempDir()).path
options = moves.UserDict()
options.chunksize = 4096
options.sourcetoken = 'sourcetoken'
options.metaonly = False
args = ['localhost:9292', tempdir]
orig_img_service = glance_replicator.get_image_service
self.addCleanup(setattr, glance_replicator,
'get_image_service', orig_img_service)
glance_replicator.get_image_service = get_image_service
glance_replicator.replication_dump(options, args)
for active in ['5dcddce0-cba5-4f18-9cf4-9853c7b207a6',
'37ff82db-afca-48c7-ae0b-ddc7cf83e3db']:
imgfile = os.path.join(tempdir, active)
self.assertTrue(os.path.exists(imgfile))
self.assertTrue(os.path.exists('%s.img' % imgfile))
with open(imgfile) as f:
d = jsonutils.loads(f.read())
self.assertIn('status', d)
self.assertIn('id', d)
self.assertIn('size', d)
for inactive in ['f4da1d2a-40e8-4710-b3aa-0222a4cc887b']:
imgfile = os.path.join(tempdir, inactive)
self.assertTrue(os.path.exists(imgfile))
self.assertFalse(os.path.exists('%s.img' % imgfile))
with open(imgfile) as f:
d = jsonutils.loads(f.read())
self.assertIn('status', d)
self.assertIn('id', d)
self.assertIn('size', d)
def test_replication_dump_with_no_args(self):
args = []
command = glance_replicator.replication_dump
self.assertTrue(check_no_args(command, args))
def test_replication_dump_with_bad_args(self):
args = ['aaa', 'bbb']
command = glance_replicator.replication_dump
self.assertTrue(check_bad_args(command, args))
def test_replication_load(self):
tempdir = self.useFixture(fixtures.TempDir()).path
def write_image(img, data):
imgfile = os.path.join(tempdir, img['id'])
with open(imgfile, 'w') as f:
f.write(jsonutils.dumps(img))
if data:
with open('%s.img' % imgfile, 'w') as f:
f.write(data)
for img in FAKEIMAGES:
cimg = copy.copy(img)
# We need at least one image where the stashed metadata on disk
# is newer than what the fake has
if cimg['id'] == '5dcddce0-cba5-4f18-9cf4-9853c7b207a6':
cimg['extra'] = 'thisissomeextra'
# This is an image where the metadata change should be ignored
if cimg['id'] == 'f4da1d2a-40e8-4710-b3aa-0222a4cc887b':
cimg['dontrepl'] = 'thisisyetmoreextra'
write_image(cimg, 'kjdhfkjshdfkjhsdkfd')
# And an image which isn't on the destination at all
new_id = str(uuid.uuid4())
cimg['id'] = new_id
write_image(cimg, 'dskjfhskjhfkfdhksjdhf')
# And an image which isn't on the destination, but lacks image
# data
new_id_missing_data = str(uuid.uuid4())
cimg['id'] = new_id_missing_data
write_image(cimg, None)
# A file which should be ignored
badfile = os.path.join(tempdir, 'kjdfhf')
with open(badfile, 'w') as f:
f.write(jsonutils.dumps([1, 2, 3, 4, 5]))
# Finally, we're ready to test
options = moves.UserDict()
options.dontreplicate = 'dontrepl dontreplabsent'
options.targettoken = 'targettoken'
args = ['localhost:9292', tempdir]
orig_img_service = glance_replicator.get_image_service
try:
glance_replicator.get_image_service = get_image_service
updated = glance_replicator.replication_load(options, args)
finally:
glance_replicator.get_image_service = orig_img_service
self.assertIn('5dcddce0-cba5-4f18-9cf4-9853c7b207a6', updated)
self.assertNotIn('f4da1d2a-40e8-4710-b3aa-0222a4cc887b', updated)
self.assertIn(new_id, updated)
self.assertNotIn(new_id_missing_data, updated)
def test_replication_load_with_no_args(self):
args = []
command = glance_replicator.replication_load
self.assertTrue(check_no_args(command, args))
def test_replication_load_with_bad_args(self):
args = ['aaa', 'bbb']
command = glance_replicator.replication_load
self.assertTrue(check_bad_args(command, args))
def test_replication_livecopy(self):
options = moves.UserDict()
options.chunksize = 4096
options.dontreplicate = 'dontrepl dontreplabsent'
options.sourcetoken = 'livesourcetoken'
options.targettoken = 'livetargettoken'
options.metaonly = False
args = ['localhost:9292', 'localhost:9393']
orig_img_service = glance_replicator.get_image_service
try:
glance_replicator.get_image_service = get_image_service
updated = glance_replicator.replication_livecopy(options, args)
finally:
glance_replicator.get_image_service = orig_img_service
self.assertEqual(2, len(updated))
def test_replication_livecopy_with_no_args(self):
args = []
command = glance_replicator.replication_livecopy
self.assertTrue(check_no_args(command, args))
def test_replication_livecopy_with_bad_args(self):
args = ['aaa', 'bbb']
command = glance_replicator.replication_livecopy
self.assertTrue(check_bad_args(command, args))
def test_replication_compare(self):
options = moves.UserDict()
options.chunksize = 4096
options.dontreplicate = 'dontrepl dontreplabsent'
options.sourcetoken = 'livesourcetoken'
options.targettoken = 'livetargettoken'
options.metaonly = False
args = ['localhost:9292', 'localhost:9393']
orig_img_service = glance_replicator.get_image_service
try:
glance_replicator.get_image_service = get_image_service
differences = glance_replicator.replication_compare(options, args)
finally:
glance_replicator.get_image_service = orig_img_service
self.assertIn('15648dd7-8dd0-401c-bd51-550e1ba9a088', differences)
self.assertEqual(differences['15648dd7-8dd0-401c-bd51-550e1ba9a088'],
'missing')
self.assertIn('37ff82db-afca-48c7-ae0b-ddc7cf83e3db', differences)
self.assertEqual(differences['37ff82db-afca-48c7-ae0b-ddc7cf83e3db'],
'diff')
def test_replication_compare_with_no_args(self):
args = []
command = glance_replicator.replication_compare
self.assertTrue(check_no_args(command, args))
def test_replication_compare_with_bad_args(self):
args = ['aaa', 'bbb']
command = glance_replicator.replication_compare
self.assertTrue(check_bad_args(command, args))
class ReplicationUtilitiesTestCase(test_utils.BaseTestCase):
def test_check_upload_response_headers(self):
glance_replicator._check_upload_response_headers({'status': 'active'},
None)
d = {'image': {'status': 'active'}}
glance_replicator._check_upload_response_headers({},
jsonutils.dumps(d))
self.assertRaises(
exception.UploadException,
glance_replicator._check_upload_response_headers, {}, None)
def test_image_present(self):
client = FakeImageService(None, 'noauth')
self.assertTrue(glance_replicator._image_present(
client, '5dcddce0-cba5-4f18-9cf4-9853c7b207a6'))
self.assertFalse(glance_replicator._image_present(
client, uuid.uuid4()))
def test_dict_diff(self):
a = {'a': 1, 'b': 2, 'c': 3}
b = {'a': 1, 'b': 2}
c = {'a': 1, 'b': 1, 'c': 3}
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
# Only things that the first dict has which the second dict doesn't
# matter here.
self.assertFalse(glance_replicator._dict_diff(a, a))
self.assertTrue(glance_replicator._dict_diff(a, b))
self.assertTrue(glance_replicator._dict_diff(a, c))
self.assertFalse(glance_replicator._dict_diff(a, d))
glance-16.0.1/glance/tests/unit/test_store_location.py 0000666 0001750 0001750 00000006200 13267672245 023077 0 ustar zuul zuul 0000000 0000000 # Copyright 2011-2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import glance_store
import mock
from glance.common import exception
from glance.common import store_utils
import glance.location
from glance.tests.unit import base
CONF = {'default_store': 'file',
'swift_store_auth_address': 'localhost:8080',
'swift_store_container': 'glance',
'swift_store_user': 'user',
'swift_store_key': 'key',
'default_swift_reference': 'store_1'
}
class TestStoreLocation(base.StoreClearingUnitTest):
class FakeImageProxy(object):
size = None
context = None
store_api = mock.Mock()
store_utils = store_utils
def test_add_location_for_image_without_size(self):
def fake_get_size_from_backend(uri, context=None):
return 1
self.stubs.Set(glance_store, 'get_size_from_backend',
fake_get_size_from_backend)
with mock.patch('glance.location._check_image_location'):
loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
# Test for insert location
image1 = TestStoreLocation.FakeImageProxy()
locations = glance.location.StoreLocations(image1, [])
locations.insert(0, loc2)
self.assertEqual(1, image1.size)
# Test for set_attr of _locations_proxy
image2 = TestStoreLocation.FakeImageProxy()
locations = glance.location.StoreLocations(image2, [loc1])
locations[0] = loc2
self.assertIn(loc2, locations)
self.assertEqual(1, image2.size)
def test_add_location_with_restricted_sources(self):
loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
loc3 = {'url': 'filesystem:///foo.img.tar.gz', 'metadata': {}}
# Test for insert location
image1 = TestStoreLocation.FakeImageProxy()
locations = glance.location.StoreLocations(image1, [])
self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc3)
self.assertNotIn(loc1, locations)
self.assertNotIn(loc3, locations)
# Test for set_attr of _locations_proxy
image2 = TestStoreLocation.FakeImageProxy()
locations = glance.location.StoreLocations(image2, [loc1])
self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
self.assertNotIn(loc2, locations)
glance-16.0.1/glance/tests/unit/test_manage.py 0000666 0001750 0001750 00000101020 13267672245 021277 0 ustar zuul zuul 0000000 0000000 # Copyright 2014 Rackspace Hosting
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import fixtures
import mock
from six.moves import StringIO
from glance.cmd import manage
from glance.common import exception
from glance.db.sqlalchemy import api as db_api
from glance.db.sqlalchemy import metadata as db_metadata
from glance.tests import utils as test_utils
class TestManageBase(test_utils.BaseTestCase):
def setUp(self):
super(TestManageBase, self).setUp()
def clear_conf():
manage.CONF.reset()
manage.CONF.unregister_opt(manage.command_opt)
clear_conf()
self.addCleanup(clear_conf)
self.useFixture(fixtures.MonkeyPatch(
'oslo_log.log.setup', lambda product_name, version='test': None))
patcher = mock.patch('glance.db.sqlalchemy.api.get_engine')
patcher.start()
self.addCleanup(patcher.stop)
def _main_test_helper(self, argv, func_name=None, *exp_args, **exp_kwargs):
self.useFixture(fixtures.MonkeyPatch('sys.argv', argv))
manage.main()
func_name.assert_called_once_with(*exp_args, **exp_kwargs)
class TestLegacyManage(TestManageBase):
@mock.patch.object(manage.DbCommands, 'version')
def test_legacy_db_version(self, db_upgrade):
self._main_test_helper(['glance.cmd.manage', 'db_version'],
manage.DbCommands.version)
@mock.patch.object(manage.DbCommands, 'sync')
def test_legacy_db_sync(self, db_sync):
self._main_test_helper(['glance.cmd.manage', 'db_sync'],
manage.DbCommands.sync, None)
@mock.patch.object(manage.DbCommands, 'upgrade')
def test_legacy_db_upgrade(self, db_upgrade):
self._main_test_helper(['glance.cmd.manage', 'db_upgrade'],
manage.DbCommands.upgrade, None)
@mock.patch.object(manage.DbCommands, 'version_control')
def test_legacy_db_version_control(self, db_version_control):
self._main_test_helper(['glance.cmd.manage', 'db_version_control'],
manage.DbCommands.version_control, None)
@mock.patch.object(manage.DbCommands, 'sync')
def test_legacy_db_sync_version(self, db_sync):
self._main_test_helper(['glance.cmd.manage', 'db_sync', 'liberty'],
manage.DbCommands.sync, 'liberty')
@mock.patch.object(manage.DbCommands, 'upgrade')
def test_legacy_db_upgrade_version(self, db_upgrade):
self._main_test_helper(['glance.cmd.manage', 'db_upgrade', 'liberty'],
manage.DbCommands.upgrade, 'liberty')
@mock.patch.object(manage.DbCommands, 'expand')
def test_legacy_db_expand(self, db_expand):
self._main_test_helper(['glance.cmd.manage', 'db_expand'],
manage.DbCommands.expand)
@mock.patch.object(manage.DbCommands, 'migrate')
def test_legacy_db_migrate(self, db_migrate):
self._main_test_helper(['glance.cmd.manage', 'db_migrate'],
manage.DbCommands.migrate)
@mock.patch.object(manage.DbCommands, 'contract')
def test_legacy_db_contract(self, db_contract):
self._main_test_helper(['glance.cmd.manage', 'db_contract'],
manage.DbCommands.contract)
def test_db_metadefs_unload(self):
db_metadata.db_unload_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_unload_metadefs'],
db_metadata.db_unload_metadefs,
db_api.get_engine())
def test_db_metadefs_load(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
None, None, None, None)
def test_db_metadefs_load_with_specified_path(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs',
'/mock/'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', None, None, None)
def test_db_metadefs_load_from_path_merge(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs',
'/mock/', 'True'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', 'True', None, None)
def test_db_metadefs_load_from_merge_and_prefer_new(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs',
'/mock/', 'True', 'True'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', 'True', 'True', None)
def test_db_metadefs_load_from_merge_and_prefer_new_and_overwrite(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs',
'/mock/', 'True', 'True', 'True'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', 'True', 'True', 'True')
def test_db_metadefs_export(self):
db_metadata.db_export_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_export_metadefs'],
db_metadata.db_export_metadefs,
db_api.get_engine(),
None)
def test_db_metadefs_export_with_specified_path(self):
db_metadata.db_export_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db_export_metadefs',
'/mock/'],
db_metadata.db_export_metadefs,
db_api.get_engine(),
'/mock/')
class TestManage(TestManageBase):
def setUp(self):
super(TestManage, self).setUp()
self.db = manage.DbCommands()
self.output = StringIO()
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
@mock.patch('glance.db.sqlalchemy.api.get_engine')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.data_migrations.'
'has_pending_migrations')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
def test_db_check_result(self, mock_get_alembic_branch_head,
mock_get_current_alembic_heads,
mock_has_pending_migrations,
get_mock_engine):
get_mock_engine.return_value = mock.Mock()
engine = get_mock_engine.return_value
engine.engine.name = 'postgresql'
exit = self.assertRaises(SystemExit, self.db.check)
self.assertIn('Rolling upgrades are currently supported only for '
'MySQL and Sqlite', exit.code)
engine = get_mock_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.return_value = 'pike_expand01'
exit = self.assertRaises(SystemExit, self.db.check)
self.assertEqual(3, exit.code)
self.assertIn('Your database is not up to date. '
'Your first step is to run `glance-manage db expand`.',
self.output.getvalue())
mock_get_current_alembic_heads.return_value = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01', None]
mock_has_pending_migrations.return_value = [mock.Mock()]
exit = self.assertRaises(SystemExit, self.db.check)
self.assertEqual(4, exit.code)
self.assertIn('Your database is not up to date. '
'Your next step is to run `glance-manage db migrate`.',
self.output.getvalue())
mock_get_current_alembic_heads.return_value = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
mock_has_pending_migrations.return_value = None
exit = self.assertRaises(SystemExit, self.db.check)
self.assertEqual(5, exit.code)
self.assertIn('Your database is not up to date. '
'Your next step is to run `glance-manage db contract`.',
self.output.getvalue())
mock_get_current_alembic_heads.return_value = ['pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
mock_has_pending_migrations.return_value = None
self.assertRaises(SystemExit, self.db.check)
self.assertIn('Database is up to date. No upgrades needed.',
self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, 'expand')
@mock.patch.object(manage.DbCommands, 'migrate')
@mock.patch.object(manage.DbCommands, 'contract')
def test_sync(self, mock_contract, mock_migrate, mock_expand,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.return_value = ['pike_contract01']
self.db.sync()
mock_expand.assert_called_once_with(online_migration=False)
mock_migrate.assert_called_once_with(online_migration=False)
mock_contract.assert_called_once_with(online_migration=False)
self.assertIn('Database is synced successfully.',
self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch('glance.db.sqlalchemy.alembic_migrations.'
'place_database_under_alembic_control')
@mock.patch('alembic.command.upgrade')
def test_sync_db_is_already_sync(self, mock_upgrade,
mock_db_under_alembic_control,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
mock_get_current_alembic_heads.return_value = ['pike_contract01']
mock_get_alembic_branch_head.return_value = ['pike_contract01']
self.assertRaises(SystemExit, self.db.sync)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
@mock.patch.object(manage.DbCommands, 'expand')
def test_sync_failed_to_sync(self, mock_expand, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01', '']
mock_expand.side_effect = exception.GlanceException
exit = self.assertRaises(SystemExit, self.db.sync)
self.assertIn('Failed to sync database: ERROR:', exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
@mock.patch.object(manage.DbCommands, '_sync')
def test_expand(self, mock_sync, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.side_effect = ['ocata_contract01',
'pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
self.db.expand()
mock_sync.assert_called_once_with(version='pike_expand01')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_expand_if_not_expand_head(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.return_value = []
exit = self.assertRaises(SystemExit, self.db.expand)
self.assertIn('Database expansion failed. Couldn\'t find head '
'revision of expand branch.', exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_expand_db_is_already_sync(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
self.assertRaises(SystemExit, self.db.expand)
self.assertIn('Database is up to date. No migrations needed.',
self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_expand_already_sync(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
self.db.expand()
self.assertIn('Database expansion is up to date. '
'No expansion needed.', self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
@mock.patch.object(manage.DbCommands, '_sync')
def test_expand_failed(self, mock_sync, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.side_effect = ['ocata_contract01',
'test']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
exit = self.assertRaises(SystemExit, self.db.expand)
mock_sync.assert_called_once_with(version='pike_expand01')
self.assertIn('Database expansion failed. Database expansion should '
'have brought the database version up to "pike_expand01"'
' revision. But, current revisions are: test ',
exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
@mock.patch.object(manage.DbCommands, '_sync')
def test_contract(self, mock_sync, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.side_effect = ['pike_expand01',
'pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
self.db.contract()
mock_sync.assert_called_once_with(version='pike_contract01')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_contract_if_not_contract_head(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.return_value = []
exit = self.assertRaises(SystemExit, self.db.contract)
self.assertIn('Database contraction failed. Couldn\'t find head '
'revision of contract branch.', exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_contract_db_is_already_sync(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
self.assertRaises(SystemExit, self.db.contract)
self.assertIn('Database is up to date. No migrations needed.',
self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_contract_before_expand(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
exit = self.assertRaises(SystemExit, self.db.contract)
self.assertIn('Database contraction did not run. Database '
'contraction cannot be run before database expansion. '
'Run database expansion first using "glance-manage db '
'expand"', exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.data_migrations.'
'has_pending_migrations')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_contract_before_migrate(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_curr_alembic_heads,
mock_has_pending_migrations):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_curr_alembic_heads.side_effect = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
mock_has_pending_migrations.return_value = [mock.Mock()]
exit = self.assertRaises(SystemExit, self.db.contract)
self.assertIn('Database contraction did not run. Database '
'contraction cannot be run before data migration is '
'complete. Run data migration using "glance-manage db '
'migrate".', exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.data_migrations.'
'has_pending_migrations')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_migrate(self, mock_validate_engine, mock_get_alembic_branch_head,
mock_get_current_alembic_heads,
mock_has_pending_migrations):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.side_effect = ['pike_expand01',
'pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
mock_has_pending_migrations.return_value = None
self.db.migrate()
self.assertIn('Database migration is up to date. '
'No migration needed.', self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_migrate_db_is_already_sync(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
self.assertRaises(SystemExit, self.db.migrate)
self.assertIn('Database is up to date. No migrations needed.',
self.output.getvalue())
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_migrate_already_sync(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
exit = self.assertRaises(SystemExit, self.db.migrate)
self.assertIn('Data migration did not run. Data migration cannot be '
'run before database expansion. Run database expansion '
'first using "glance-manage db expand"', exit.code)
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.data_migrations.'
'has_pending_migrations')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@mock.patch.object(manage.DbCommands, '_validate_engine')
def test_migrate_before_expand(self, mock_validate_engine,
mock_get_alembic_branch_head,
mock_get_current_alembic_heads,
mock_has_pending_migrations):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
mock_has_pending_migrations.return_value = None
self.db.migrate()
self.assertIn('Database migration is up to date. '
'No migration needed.', self.output.getvalue())
@mock.patch.object(manage.DbCommands, 'version')
def test_db_version(self, version):
self._main_test_helper(['glance.cmd.manage', 'db', 'version'],
manage.DbCommands.version)
@mock.patch.object(manage.DbCommands, 'check')
def test_db_check(self, check):
self._main_test_helper(['glance.cmd.manage', 'db', 'check'],
manage.DbCommands.check)
@mock.patch.object(manage.DbCommands, 'sync')
def test_db_sync(self, sync):
self._main_test_helper(['glance.cmd.manage', 'db', 'sync'],
manage.DbCommands.sync)
@mock.patch.object(manage.DbCommands, 'upgrade')
def test_db_upgrade(self, upgrade):
self._main_test_helper(['glance.cmd.manage', 'db', 'upgrade'],
manage.DbCommands.upgrade)
@mock.patch.object(manage.DbCommands, 'version_control')
def test_db_version_control(self, version_control):
self._main_test_helper(['glance.cmd.manage', 'db', 'version_control'],
manage.DbCommands.version_control)
@mock.patch.object(manage.DbCommands, 'sync')
def test_db_sync_version(self, sync):
self._main_test_helper(['glance.cmd.manage', 'db', 'sync', 'liberty'],
manage.DbCommands.sync, 'liberty')
@mock.patch.object(manage.DbCommands, 'upgrade')
def test_db_upgrade_version(self, upgrade):
self._main_test_helper(['glance.cmd.manage', 'db',
'upgrade', 'liberty'],
manage.DbCommands.upgrade, 'liberty')
@mock.patch.object(manage.DbCommands, 'expand')
def test_db_expand(self, expand):
self._main_test_helper(['glance.cmd.manage', 'db', 'expand'],
manage.DbCommands.expand)
@mock.patch.object(manage.DbCommands, 'migrate')
def test_db_migrate(self, migrate):
self._main_test_helper(['glance.cmd.manage', 'db', 'migrate'],
manage.DbCommands.migrate)
@mock.patch.object(manage.DbCommands, 'contract')
def test_db_contract(self, contract):
self._main_test_helper(['glance.cmd.manage', 'db', 'contract'],
manage.DbCommands.contract)
def test_db_metadefs_unload(self):
db_metadata.db_unload_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'unload_metadefs'],
db_metadata.db_unload_metadefs,
db_api.get_engine())
def test_db_metadefs_load(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
None, False, False, False)
def test_db_metadefs_load_with_specified_path(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
'--path', '/mock/'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', False, False, False)
def test_db_metadefs_load_prefer_new_with_path(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
'--path', '/mock/', '--merge', '--prefer_new'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', True, True, False)
def test_db_metadefs_load_prefer_new(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
'--merge', '--prefer_new'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
None, True, True, False)
def test_db_metadefs_load_overwrite_existing(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
'--merge', '--overwrite'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
None, True, False, True)
def test_db_metadefs_load_prefer_new_and_overwrite_existing(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
'--merge', '--prefer_new', '--overwrite'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
None, True, True, True)
def test_db_metadefs_load_from_path_overwrite_existing(self):
db_metadata.db_load_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
'--path', '/mock/', '--merge', '--overwrite'],
db_metadata.db_load_metadefs,
db_api.get_engine(),
'/mock/', True, False, True)
def test_db_metadefs_export(self):
db_metadata.db_export_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'export_metadefs'],
db_metadata.db_export_metadefs,
db_api.get_engine(),
None)
def test_db_metadefs_export_with_specified_path(self):
db_metadata.db_export_metadefs = mock.Mock()
self._main_test_helper(['glance.cmd.manage', 'db', 'export_metadefs',
'--path', '/mock/'],
db_metadata.db_export_metadefs,
db_api.get_engine(),
'/mock/')
glance-16.0.1/glance/tests/unit/test_context.py 0000666 0001750 0001750 00000014203 13267672245 021541 0 ustar zuul zuul 0000000 0000000 # Copyright 2010-2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance import context
from glance.tests.unit import utils as unit_utils
from glance.tests import utils
def _fake_image(owner, is_public):
return {
'id': None,
'owner': owner,
'visibility': 'public' if is_public else 'shared',
}
def _fake_membership(can_share=False):
return {'can_share': can_share}
class TestContext(utils.BaseTestCase):
def setUp(self):
super(TestContext, self).setUp()
self.db_api = unit_utils.FakeDB()
def do_visible(self, exp_res, img_owner, img_public, **kwargs):
"""
Perform a context visibility test. Creates a (fake) image
with the specified owner and is_public attributes, then
creates a context with the given keyword arguments and expects
exp_res as the result of an is_image_visible() call on the
context.
"""
img = _fake_image(img_owner, img_public)
ctx = context.RequestContext(**kwargs)
self.assertEqual(exp_res, self.db_api.is_image_visible(ctx, img))
def test_empty_public(self):
"""
Tests that an empty context (with is_admin set to True) can
access an image with is_public set to True.
"""
self.do_visible(True, None, True, is_admin=True)
def test_empty_public_owned(self):
"""
Tests that an empty context (with is_admin set to True) can
access an owned image with is_public set to True.
"""
self.do_visible(True, 'pattieblack', True, is_admin=True)
def test_empty_private(self):
"""
Tests that an empty context (with is_admin set to True) can
access an image with is_public set to False.
"""
self.do_visible(True, None, False, is_admin=True)
def test_empty_private_owned(self):
"""
Tests that an empty context (with is_admin set to True) can
access an owned image with is_public set to False.
"""
self.do_visible(True, 'pattieblack', False, is_admin=True)
def test_anon_public(self):
"""
Tests that an anonymous context (with is_admin set to False)
can access an image with is_public set to True.
"""
self.do_visible(True, None, True)
def test_anon_public_owned(self):
"""
Tests that an anonymous context (with is_admin set to False)
can access an owned image with is_public set to True.
"""
self.do_visible(True, 'pattieblack', True)
def test_anon_private(self):
"""
Tests that an anonymous context (with is_admin set to False)
can access an unowned image with is_public set to False.
"""
self.do_visible(True, None, False)
def test_anon_private_owned(self):
"""
Tests that an anonymous context (with is_admin set to False)
cannot access an owned image with is_public set to False.
"""
self.do_visible(False, 'pattieblack', False)
def test_auth_public(self):
"""
Tests that an authenticated context (with is_admin set to
False) can access an image with is_public set to True.
"""
self.do_visible(True, None, True, project_id='froggy')
def test_auth_public_unowned(self):
"""
Tests that an authenticated context (with is_admin set to
False) can access an image (which it does not own) with
is_public set to True.
"""
self.do_visible(True, 'pattieblack', True, project_id='froggy')
def test_auth_public_owned(self):
"""
Tests that an authenticated context (with is_admin set to
False) can access an image (which it does own) with is_public
set to True.
"""
self.do_visible(True, 'pattieblack', True, project_id='pattieblack')
def test_auth_private(self):
"""
Tests that an authenticated context (with is_admin set to
False) can access an image with is_public set to False.
"""
self.do_visible(True, None, False, project_id='froggy')
def test_auth_private_unowned(self):
"""
Tests that an authenticated context (with is_admin set to
False) cannot access an image (which it does not own) with
is_public set to False.
"""
self.do_visible(False, 'pattieblack', False, project_id='froggy')
def test_auth_private_owned(self):
"""
Tests that an authenticated context (with is_admin set to
False) can access an image (which it does own) with is_public
set to False.
"""
self.do_visible(True, 'pattieblack', False, project_id='pattieblack')
def test_request_id(self):
contexts = [context.RequestContext().request_id for _ in range(5)]
# Check for uniqueness -- set() will normalize its argument
self.assertEqual(5, len(set(contexts)))
def test_service_catalog(self):
ctx = context.RequestContext(service_catalog=['foo'])
self.assertEqual(['foo'], ctx.service_catalog)
def test_user_identity(self):
ctx = context.RequestContext(user_id="user",
project_id="tenant",
domain_id="domain",
user_domain_id="user-domain",
project_domain_id="project-domain")
self.assertEqual('user tenant domain user-domain project-domain',
ctx.to_dict()["user_identity"])
glance-16.0.1/glance/tests/unit/test_image_cache.py 0000666 0001750 0001750 00000047536 13267672245 022301 0 ustar zuul zuul 0000000 0000000 # Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from contextlib import contextmanager
import datetime
import hashlib
import os
import time
import fixtures
from oslo_utils import units
from oslotest import moxstubout
import six
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
from glance.common import exception
from glance import image_cache
# NOTE(bcwaldon): This is imported to load the registry config options
import glance.registry # noqa
from glance.tests import utils as test_utils
from glance.tests.utils import skip_if_disabled
from glance.tests.utils import xattr_writes_supported
FIXTURE_LENGTH = 1024
FIXTURE_DATA = b'*' * FIXTURE_LENGTH
class ImageCacheTestCase(object):
def _setup_fixture_file(self):
FIXTURE_FILE = six.BytesIO(FIXTURE_DATA)
self.assertFalse(self.cache.is_cached(1))
self.assertTrue(self.cache.cache_image_file(1, FIXTURE_FILE))
self.assertTrue(self.cache.is_cached(1))
@skip_if_disabled
def test_is_cached(self):
"""Verify is_cached(1) returns 0, then add something to the cache
and verify is_cached(1) returns 1.
"""
self._setup_fixture_file()
@skip_if_disabled
def test_read(self):
"""Verify is_cached(1) returns 0, then add something to the cache
and verify after a subsequent read from the cache that
is_cached(1) returns 1.
"""
self._setup_fixture_file()
buff = six.BytesIO()
with self.cache.open_for_read(1) as cache_file:
for chunk in cache_file:
buff.write(chunk)
self.assertEqual(FIXTURE_DATA, buff.getvalue())
@skip_if_disabled
def test_open_for_read(self):
"""Test convenience wrapper for opening a cache file via
its image identifier.
"""
self._setup_fixture_file()
buff = six.BytesIO()
with self.cache.open_for_read(1) as cache_file:
for chunk in cache_file:
buff.write(chunk)
self.assertEqual(FIXTURE_DATA, buff.getvalue())
@skip_if_disabled
def test_get_image_size(self):
"""Test convenience wrapper for querying cache file size via
its image identifier.
"""
self._setup_fixture_file()
size = self.cache.get_image_size(1)
self.assertEqual(FIXTURE_LENGTH, size)
@skip_if_disabled
def test_delete(self):
"""Test delete method that removes an image from the cache."""
self._setup_fixture_file()
self.cache.delete_cached_image(1)
self.assertFalse(self.cache.is_cached(1))
@skip_if_disabled
def test_delete_all(self):
"""Test delete method that removes an image from the cache."""
for image_id in (1, 2):
self.assertFalse(self.cache.is_cached(image_id))
for image_id in (1, 2):
FIXTURE_FILE = six.BytesIO(FIXTURE_DATA)
self.assertTrue(self.cache.cache_image_file(image_id,
FIXTURE_FILE))
for image_id in (1, 2):
self.assertTrue(self.cache.is_cached(image_id))
self.cache.delete_all_cached_images()
for image_id in (1, 2):
self.assertFalse(self.cache.is_cached(image_id))
@skip_if_disabled
def test_clean_stalled(self):
"""Test the clean method removes expected images."""
incomplete_file_path = os.path.join(self.cache_dir, 'incomplete', '1')
incomplete_file = open(incomplete_file_path, 'wb')
incomplete_file.write(FIXTURE_DATA)
incomplete_file.close()
self.assertTrue(os.path.exists(incomplete_file_path))
self.cache.clean(stall_time=0)
self.assertFalse(os.path.exists(incomplete_file_path))
@skip_if_disabled
def test_clean_stalled_nonzero_stall_time(self):
"""
Test the clean method removes the stalled images as expected
"""
incomplete_file_path_1 = os.path.join(self.cache_dir,
'incomplete', '1')
incomplete_file_path_2 = os.path.join(self.cache_dir,
'incomplete', '2')
for f in (incomplete_file_path_1, incomplete_file_path_2):
incomplete_file = open(f, 'wb')
incomplete_file.write(FIXTURE_DATA)
incomplete_file.close()
mtime = os.path.getmtime(incomplete_file_path_1)
pastday = (datetime.datetime.fromtimestamp(mtime) -
datetime.timedelta(days=1))
atime = int(time.mktime(pastday.timetuple()))
mtime = atime
os.utime(incomplete_file_path_1, (atime, mtime))
self.assertTrue(os.path.exists(incomplete_file_path_1))
self.assertTrue(os.path.exists(incomplete_file_path_2))
self.cache.clean(stall_time=3600)
self.assertFalse(os.path.exists(incomplete_file_path_1))
self.assertTrue(os.path.exists(incomplete_file_path_2))
@skip_if_disabled
def test_prune(self):
"""
Test that pruning the cache works as expected...
"""
self.assertEqual(0, self.cache.get_cache_size())
# Add a bunch of images to the cache. The max cache size for the cache
# is set to 5KB and each image is 1K. We use 11 images in this test.
# The first 10 are added to and retrieved from cache in the same order.
# Then, the 11th image is added to cache but not retrieved before we
# prune. We should see only 5 images left after pruning, and the
# images that are least recently accessed should be the ones pruned...
for x in range(10):
FIXTURE_FILE = six.BytesIO(FIXTURE_DATA)
self.assertTrue(self.cache.cache_image_file(x, FIXTURE_FILE))
self.assertEqual(10 * units.Ki, self.cache.get_cache_size())
# OK, hit the images that are now cached...
for x in range(10):
buff = six.BytesIO()
with self.cache.open_for_read(x) as cache_file:
for chunk in cache_file:
buff.write(chunk)
# Add a new image to cache.
# This is specifically to test the bug: 1438564
FIXTURE_FILE = six.BytesIO(FIXTURE_DATA)
self.assertTrue(self.cache.cache_image_file(99, FIXTURE_FILE))
self.cache.prune()
self.assertEqual(5 * units.Ki, self.cache.get_cache_size())
# Ensure images 0, 1, 2, 3, 4 & 5 are not cached anymore
for x in range(0, 6):
self.assertFalse(self.cache.is_cached(x),
"Image %s was cached!" % x)
# Ensure images 6, 7, 8 and 9 are still cached
for x in range(6, 10):
self.assertTrue(self.cache.is_cached(x),
"Image %s was not cached!" % x)
# Ensure the newly added image, 99, is still cached
self.assertTrue(self.cache.is_cached(99), "Image 99 was not cached!")
@skip_if_disabled
def test_prune_to_zero(self):
"""Test that an image_cache_max_size of 0 doesn't kill the pruner
This is a test specifically for LP #1039854
"""
self.assertEqual(0, self.cache.get_cache_size())
FIXTURE_FILE = six.BytesIO(FIXTURE_DATA)
self.assertTrue(self.cache.cache_image_file('xxx', FIXTURE_FILE))
self.assertEqual(1024, self.cache.get_cache_size())
# OK, hit the image that is now cached...
buff = six.BytesIO()
with self.cache.open_for_read('xxx') as cache_file:
for chunk in cache_file:
buff.write(chunk)
self.config(image_cache_max_size=0)
self.cache.prune()
self.assertEqual(0, self.cache.get_cache_size())
self.assertFalse(self.cache.is_cached('xxx'))
@skip_if_disabled
def test_queue(self):
"""
Test that queueing works properly
"""
self.assertFalse(self.cache.is_cached(1))
self.assertFalse(self.cache.is_queued(1))
FIXTURE_FILE = six.BytesIO(FIXTURE_DATA)
self.assertTrue(self.cache.queue_image(1))
self.assertTrue(self.cache.is_queued(1))
self.assertFalse(self.cache.is_cached(1))
# Should not return True if the image is already
# queued for caching...
self.assertFalse(self.cache.queue_image(1))
self.assertFalse(self.cache.is_cached(1))
# Test that we return False if we try to queue
# an image that has already been cached
self.assertTrue(self.cache.cache_image_file(1, FIXTURE_FILE))
self.assertFalse(self.cache.is_queued(1))
self.assertTrue(self.cache.is_cached(1))
self.assertFalse(self.cache.queue_image(1))
self.cache.delete_cached_image(1)
for x in range(3):
self.assertTrue(self.cache.queue_image(x))
self.assertEqual(['0', '1', '2'],
self.cache.get_queued_images())
def test_open_for_write_good(self):
"""
Test to see if open_for_write works in normal case
"""
# test a good case
image_id = '1'
self.assertFalse(self.cache.is_cached(image_id))
with self.cache.driver.open_for_write(image_id) as cache_file:
cache_file.write(b'a')
self.assertTrue(self.cache.is_cached(image_id),
"Image %s was NOT cached!" % image_id)
# make sure it has tidied up
incomplete_file_path = os.path.join(self.cache_dir,
'incomplete', image_id)
invalid_file_path = os.path.join(self.cache_dir, 'invalid', image_id)
self.assertFalse(os.path.exists(incomplete_file_path))
self.assertFalse(os.path.exists(invalid_file_path))
def test_open_for_write_with_exception(self):
"""
Test to see if open_for_write works in a failure case for each driver
This case is where an exception is raised while the file is being
written. The image is partially filled in cache and filling wont resume
so verify the image is moved to invalid/ directory
"""
# test a case where an exception is raised while the file is open
image_id = '1'
self.assertFalse(self.cache.is_cached(image_id))
try:
with self.cache.driver.open_for_write(image_id):
raise IOError
except Exception as e:
self.assertIsInstance(e, IOError)
self.assertFalse(self.cache.is_cached(image_id),
"Image %s was cached!" % image_id)
# make sure it has tidied up
incomplete_file_path = os.path.join(self.cache_dir,
'incomplete', image_id)
invalid_file_path = os.path.join(self.cache_dir, 'invalid', image_id)
self.assertFalse(os.path.exists(incomplete_file_path))
self.assertTrue(os.path.exists(invalid_file_path))
def test_caching_iterator(self):
"""
Test to see if the caching iterator interacts properly with the driver
When the iterator completes going through the data the driver should
have closed the image and placed it correctly
"""
# test a case where an exception NOT raised while the file is open,
# and a consuming iterator completes
def consume(image_id):
data = [b'a', b'b', b'c', b'd', b'e', b'f']
checksum = None
caching_iter = self.cache.get_caching_iter(image_id, checksum,
iter(data))
self.assertEqual(data, list(caching_iter))
image_id = '1'
self.assertFalse(self.cache.is_cached(image_id))
consume(image_id)
self.assertTrue(self.cache.is_cached(image_id),
"Image %s was NOT cached!" % image_id)
# make sure it has tidied up
incomplete_file_path = os.path.join(self.cache_dir,
'incomplete', image_id)
invalid_file_path = os.path.join(self.cache_dir, 'invalid', image_id)
self.assertFalse(os.path.exists(incomplete_file_path))
self.assertFalse(os.path.exists(invalid_file_path))
def test_caching_iterator_handles_backend_failure(self):
"""
Test that when the backend fails, caching_iter does not continue trying
to consume data, and rolls back the cache.
"""
def faulty_backend():
data = [b'a', b'b', b'c', b'Fail', b'd', b'e', b'f']
for d in data:
if d == b'Fail':
raise exception.GlanceException('Backend failure')
yield d
def consume(image_id):
caching_iter = self.cache.get_caching_iter(image_id, None,
faulty_backend())
# exercise the caching_iter
list(caching_iter)
image_id = '1'
self.assertRaises(exception.GlanceException, consume, image_id)
# make sure bad image was not cached
self.assertFalse(self.cache.is_cached(image_id))
def test_caching_iterator_falloffend(self):
"""
Test to see if the caching iterator interacts properly with the driver
in a case where the iterator is only partially consumed. In this case
the image is only partially filled in cache and filling wont resume.
When the iterator goes out of scope the driver should have closed the
image and moved it from incomplete/ to invalid/
"""
# test a case where a consuming iterator just stops.
def falloffend(image_id):
data = [b'a', b'b', b'c', b'd', b'e', b'f']
checksum = None
caching_iter = self.cache.get_caching_iter(image_id, checksum,
iter(data))
self.assertEqual(b'a', next(caching_iter))
image_id = '1'
self.assertFalse(self.cache.is_cached(image_id))
falloffend(image_id)
self.assertFalse(self.cache.is_cached(image_id),
"Image %s was cached!" % image_id)
# make sure it has tidied up
incomplete_file_path = os.path.join(self.cache_dir,
'incomplete', image_id)
invalid_file_path = os.path.join(self.cache_dir, 'invalid', image_id)
self.assertFalse(os.path.exists(incomplete_file_path))
self.assertTrue(os.path.exists(invalid_file_path))
def test_gate_caching_iter_good_checksum(self):
image = b"12345678990abcdefghijklmnop"
image_id = 123
md5 = hashlib.md5()
md5.update(image)
checksum = md5.hexdigest()
cache = image_cache.ImageCache()
img_iter = cache.get_caching_iter(image_id, checksum, [image])
for chunk in img_iter:
pass
# checksum is valid, fake image should be cached:
self.assertTrue(cache.is_cached(image_id))
def test_gate_caching_iter_bad_checksum(self):
image = b"12345678990abcdefghijklmnop"
image_id = 123
checksum = "foobar" # bad.
cache = image_cache.ImageCache()
img_iter = cache.get_caching_iter(image_id, checksum, [image])
def reader():
for chunk in img_iter:
pass
self.assertRaises(exception.GlanceException, reader)
# checksum is invalid, caching will fail:
self.assertFalse(cache.is_cached(image_id))
class TestImageCacheXattr(test_utils.BaseTestCase,
ImageCacheTestCase):
"""Tests image caching when xattr is used in cache"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-xattr installed and xattr support on the
filesystem)
"""
super(TestImageCacheXattr, self).setUp()
if getattr(self, 'disable', False):
return
self.cache_dir = self.useFixture(fixtures.TempDir()).path
if not getattr(self, 'inited', False):
try:
import xattr # noqa
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-xattr not installed.")
return
self.inited = True
self.disabled = False
self.config(image_cache_dir=self.cache_dir,
image_cache_driver='xattr',
image_cache_max_size=5 * units.Ki)
self.cache = image_cache.ImageCache()
if not xattr_writes_supported(self.cache_dir):
self.inited = True
self.disabled = True
self.disabled_message = ("filesystem does not support xattr")
return
class TestImageCacheSqlite(test_utils.BaseTestCase,
ImageCacheTestCase):
"""Tests image caching when SQLite is used in cache"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-sqlite3 installed)
"""
super(TestImageCacheSqlite, self).setUp()
if getattr(self, 'disable', False):
return
if not getattr(self, 'inited', False):
try:
import sqlite3 # noqa
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-sqlite3 not installed.")
return
self.inited = True
self.disabled = False
self.cache_dir = self.useFixture(fixtures.TempDir()).path
self.config(image_cache_dir=self.cache_dir,
image_cache_driver='sqlite',
image_cache_max_size=5 * units.Ki)
self.cache = image_cache.ImageCache()
class TestImageCacheNoDep(test_utils.BaseTestCase):
def setUp(self):
super(TestImageCacheNoDep, self).setUp()
self.driver = None
def init_driver(self2):
self2.driver = self.driver
mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.stubs = mox_fixture.stubs
self.stubs.Set(image_cache.ImageCache, 'init_driver', init_driver)
def test_get_caching_iter_when_write_fails(self):
class FailingFile(object):
def write(self, data):
if data == "Fail":
raise IOError
class FailingFileDriver(object):
def is_cacheable(self, *args, **kwargs):
return True
@contextmanager
def open_for_write(self, *args, **kwargs):
yield FailingFile()
self.driver = FailingFileDriver()
cache = image_cache.ImageCache()
data = [b'a', b'b', b'c', b'Fail', b'd', b'e', b'f']
caching_iter = cache.get_caching_iter('dummy_id', None, iter(data))
self.assertEqual(data, list(caching_iter))
def test_get_caching_iter_when_open_fails(self):
class OpenFailingDriver(object):
def is_cacheable(self, *args, **kwargs):
return True
@contextmanager
def open_for_write(self, *args, **kwargs):
raise IOError
self.driver = OpenFailingDriver()
cache = image_cache.ImageCache()
data = [b'a', b'b', b'c', b'd', b'e', b'f']
caching_iter = cache.get_caching_iter('dummy_id', None, iter(data))
self.assertEqual(data, list(caching_iter))
glance-16.0.1/glance/tests/unit/image_cache/ 0000775 0001750 0001750 00000000000 13267672475 020654 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/image_cache/drivers/ 0000775 0001750 0001750 00000000000 13267672475 022332 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/image_cache/drivers/test_sqlite.py 0000666 0001750 0001750 00000002304 13267672245 025240 0 ustar zuul zuul 0000000 0000000 # Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests for the sqlite image_cache driver.
"""
import os
import ddt
import mock
from glance.image_cache.drivers import sqlite
from glance.tests import utils
@ddt.ddt
class TestSqlite(utils.BaseTestCase):
@ddt.data(True, False)
def test_delete_cached_file(self, throw_not_exists):
with mock.patch.object(os, 'unlink') as mock_unlink:
if throw_not_exists:
mock_unlink.side_effect = OSError((2, 'File not found'))
# Should not raise an exception in all cases
sqlite.delete_cached_file('/tmp/dummy_file')
glance-16.0.1/glance/tests/unit/image_cache/drivers/__init__.py 0000666 0001750 0001750 00000000000 13267672245 024426 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/image_cache/__init__.py 0000666 0001750 0001750 00000000000 13267672245 022750 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/api/ 0000775 0001750 0001750 00000000000 13267672475 017220 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/api/test_common.py 0000666 0001750 0001750 00000013055 13267672245 022122 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
import webob
import glance.api.common
from glance.common import config
from glance.common import exception
from glance.tests import utils as test_utils
class SimpleIterator(object):
def __init__(self, file_object, chunk_size):
self.file_object = file_object
self.chunk_size = chunk_size
def __iter__(self):
def read_chunk():
return self.fobj.read(self.chunk_size)
chunk = read_chunk()
while chunk:
yield chunk
chunk = read_chunk()
else:
raise StopIteration()
class TestSizeCheckedIter(testtools.TestCase):
def _get_image_metadata(self):
return {'id': 'e31cb99c-fe89-49fb-9cc5-f5104fffa636'}
def _get_webob_response(self):
request = webob.Request.blank('/')
response = webob.Response()
response.request = request
return response
def test_uniform_chunk_size(self):
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(
resp, meta, 4, ['AB', 'CD'], None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('CD', next(checked_image))
self.assertRaises(StopIteration, next, checked_image)
def test_small_last_chunk(self):
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(
resp, meta, 3, ['AB', 'C'], None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('C', next(checked_image))
self.assertRaises(StopIteration, next, checked_image)
def test_variable_chunk_size(self):
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(
resp, meta, 6, ['AB', '', 'CDE', 'F'], None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('', next(checked_image))
self.assertEqual('CDE', next(checked_image))
self.assertEqual('F', next(checked_image))
self.assertRaises(StopIteration, next, checked_image)
def test_too_many_chunks(self):
"""An image should streamed regardless of expected_size"""
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(
resp, meta, 4, ['AB', 'CD', 'EF'], None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('CD', next(checked_image))
self.assertEqual('EF', next(checked_image))
self.assertRaises(exception.GlanceException, next, checked_image)
def test_too_few_chunks(self):
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(resp, meta, 6,
['AB', 'CD'],
None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('CD', next(checked_image))
self.assertRaises(exception.GlanceException, next, checked_image)
def test_too_much_data(self):
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(resp, meta, 3,
['AB', 'CD'],
None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('CD', next(checked_image))
self.assertRaises(exception.GlanceException, next, checked_image)
def test_too_little_data(self):
resp = self._get_webob_response()
meta = self._get_image_metadata()
checked_image = glance.api.common.size_checked_iter(resp, meta, 6,
['AB', 'CD', 'E'],
None)
self.assertEqual('AB', next(checked_image))
self.assertEqual('CD', next(checked_image))
self.assertEqual('E', next(checked_image))
self.assertRaises(exception.GlanceException, next, checked_image)
class TestMalformedRequest(test_utils.BaseTestCase):
def setUp(self):
"""Establish a clean test environment"""
super(TestMalformedRequest, self).setUp()
self.config(flavor='',
group='paste_deploy',
config_file='etc/glance-api-paste.ini')
self.api = config.load_paste_app('glance-api')
def test_redirect_incomplete_url(self):
"""Test Glance redirects /v# to /v#/ with correct Location header"""
req = webob.Request.blank('/v1.1')
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPFound.code, res.status_int)
self.assertEqual('http://localhost/v1/', res.location)
glance-16.0.1/glance/tests/unit/api/test_cmd_cache_manage.py 0000666 0001750 0001750 00000031771 13267672245 024035 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import sys
import mock
import prettytable
from glance.cmd import cache_manage
from glance.common import exception
import glance.common.utils
import glance.image_cache.client
from glance.tests import utils as test_utils
@mock.patch('sys.stdout', mock.Mock())
class TestGlanceCmdManage(test_utils.BaseTestCase):
def _run_command(self, cmd_args, return_code=None):
"""Runs the cache-manage command.
:param cmd_args: The command line arguments.
:param return_code: The expected return code of the command.
"""
testargs = ['cache_manage']
testargs.extend(cmd_args)
with mock.patch.object(sys, 'exit') as mock_exit:
with mock.patch.object(sys, 'argv', testargs):
try:
cache_manage.main()
except Exception:
# See if we expected this failure
if return_code is None:
raise
if return_code is not None:
mock_exit.called_with(return_code)
@mock.patch.object(argparse.ArgumentParser, 'print_help')
def test_help(self, mock_print_help):
self._run_command(['help'])
self.assertEqual(1, mock_print_help.call_count)
@mock.patch.object(cache_manage, 'lookup_command')
def test_help_with_command(self, mock_lookup_command):
mock_lookup_command.return_value = cache_manage.print_help
self._run_command(['help', 'list-cached'])
mock_lookup_command.assert_any_call('help')
mock_lookup_command.assert_any_call('list-cached')
def test_help_with_redundant_command(self):
self._run_command(['help', 'list-cached', '1'], cache_manage.FAILURE)
@mock.patch.object(glance.image_cache.client.CacheClient,
'get_cached_images')
@mock.patch.object(prettytable.PrettyTable, 'add_row')
def test_list_cached_images(self, mock_row_create, mock_images):
"""
Verify that list_cached() method correctly processes images with all
filled data and images with not filled 'last_accessed' field.
"""
mock_images.return_value = [
{'last_accessed': float(0),
'last_modified': float(1378985797.124511),
'image_id': '1', 'size': '128', 'hits': '1'},
{'last_accessed': float(1378985797.124511),
'last_modified': float(1378985797.124511),
'image_id': '2', 'size': '255', 'hits': '2'}]
self._run_command(['list-cached'], cache_manage.SUCCESS)
self.assertEqual(len(mock_images.return_value),
mock_row_create.call_count)
@mock.patch.object(glance.image_cache.client.CacheClient,
'get_cached_images')
def test_list_cached_images_empty(self, mock_images):
"""
Verify that list_cached() method handles a case when no images are
cached without errors.
"""
self._run_command(['list-cached'], cache_manage.SUCCESS)
@mock.patch.object(glance.image_cache.client.CacheClient,
'get_queued_images')
@mock.patch.object(prettytable.PrettyTable, 'add_row')
def test_list_queued_images(self, mock_row_create, mock_images):
"""Verify that list_queued() method correctly processes images."""
mock_images.return_value = [
{'image_id': '1'}, {'image_id': '2'}]
# cache_manage.list_queued(mock.Mock())
self._run_command(['list-queued'], cache_manage.SUCCESS)
self.assertEqual(len(mock_images.return_value),
mock_row_create.call_count)
@mock.patch.object(glance.image_cache.client.CacheClient,
'get_queued_images')
def test_list_queued_images_empty(self, mock_images):
"""
Verify that list_queued() method handles a case when no images were
queued without errors.
"""
mock_images.return_value = []
self._run_command(['list-queued'], cache_manage.SUCCESS)
def test_queue_image_without_index(self):
self._run_command(['queue-image'], cache_manage.FAILURE)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_queue_image_not_forced_not_confirmed(self,
mock_client, mock_confirm):
# --force not set and queue confirmation return False.
mock_confirm.return_value = False
self._run_command(['queue-image', 'fakeimageid'], cache_manage.SUCCESS)
self.assertFalse(mock_client.called)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_queue_image_not_forced_confirmed(self, mock_get_client,
mock_confirm):
# --force not set and confirmation return True.
mock_confirm.return_value = True
mock_client = mock.MagicMock()
mock_get_client.return_value = mock_client
# verbose to cover additional condition and line
self._run_command(['queue-image', 'fakeimageid', '-v'],
cache_manage.SUCCESS)
self.assertTrue(mock_get_client.called)
mock_client.queue_image_for_caching.assert_called_with('fakeimageid')
def test_delete_cached_image_without_index(self):
self._run_command(['delete-cached-image'], cache_manage.FAILURE)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_cached_image_not_forced_not_confirmed(self,
mock_client,
mock_confirm):
# --force not set and confirmation return False.
mock_confirm.return_value = False
self._run_command(['delete-cached-image', 'fakeimageid'],
cache_manage.SUCCESS)
self.assertFalse(mock_client.called)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_cached_image_not_forced_confirmed(self, mock_get_client,
mock_confirm):
# --force not set and confirmation return True.
mock_confirm.return_value = True
mock_client = mock.MagicMock()
mock_get_client.return_value = mock_client
# verbose to cover additional condition and line
self._run_command(['delete-cached-image', 'fakeimageid', '-v'],
cache_manage.SUCCESS)
self.assertTrue(mock_get_client.called)
mock_client.delete_cached_image.assert_called_with('fakeimageid')
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_cached_images_not_forced_not_confirmed(self,
mock_client,
mock_confirm):
# --force not set and confirmation return False.
mock_confirm.return_value = False
self._run_command(['delete-all-cached-images'], cache_manage.SUCCESS)
self.assertFalse(mock_client.called)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_cached_images_not_forced_confirmed(self, mock_get_client,
mock_confirm):
# --force not set and confirmation return True.
mock_confirm.return_value = True
mock_client = mock.MagicMock()
mock_get_client.return_value = mock_client
# verbose to cover additional condition and line
self._run_command(['delete-all-cached-images', '-v'],
cache_manage.SUCCESS)
self.assertTrue(mock_get_client.called)
mock_client.delete_all_cached_images.assert_called()
def test_delete_queued_image_without_index(self):
self._run_command(['delete-queued-image'], cache_manage.FAILURE)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_queued_image_not_forced_not_confirmed(self,
mock_client,
mock_confirm):
# --force not set and confirmation set to False.
mock_confirm.return_value = False
self._run_command(['delete-queued-image', 'img_id'],
cache_manage.SUCCESS)
self.assertFalse(mock_client.called)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_queued_image_not_forced_confirmed(self, mock_get_client,
mock_confirm):
# --force not set and confirmation set to True.
mock_confirm.return_value = True
mock_client = mock.MagicMock()
mock_get_client.return_value = mock_client
self._run_command(['delete-queued-image', 'img_id', '-v'],
cache_manage.SUCCESS)
self.assertTrue(mock_get_client.called)
mock_client.delete_queued_image.assert_called_with('img_id')
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_queued_images_not_forced_not_confirmed(self,
mock_client,
mock_confirm):
# --force not set and confirmation set to False.
mock_confirm.return_value = False
self._run_command(['delete-all-queued-images'],
cache_manage.SUCCESS)
self.assertFalse(mock_client.called)
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_delete_queued_images_not_forced_confirmed(self, mock_get_client,
mock_confirm):
# --force not set and confirmation set to True.
mock_confirm.return_value = True
mock_client = mock.MagicMock()
mock_get_client.return_value = mock_client
self._run_command(['delete-all-queued-images', '-v'],
cache_manage.SUCCESS)
self.assertTrue(mock_get_client.called)
mock_client.delete_all_queued_images.assert_called()
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_catch_error_not_found(self, mock_function):
mock_function.side_effect = exception.NotFound()
self.assertEqual(cache_manage.FAILURE,
cache_manage.list_cached(mock.Mock()))
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_catch_error_forbidden(self, mock_function):
mock_function.side_effect = exception.Forbidden()
self.assertEqual(cache_manage.FAILURE,
cache_manage.list_cached(mock.Mock()))
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_catch_error_unhandled(self, mock_function):
mock_function.side_effect = exception.Duplicate()
my_mock = mock.Mock()
my_mock.debug = False
self.assertEqual(cache_manage.FAILURE,
cache_manage.list_cached(my_mock))
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
def test_catch_error_unhandled_debug_mode(self, mock_function):
mock_function.side_effect = exception.Duplicate()
my_mock = mock.Mock()
my_mock.debug = True
self.assertRaises(exception.Duplicate,
cache_manage.list_cached, my_mock)
def test_cache_manage_env(self):
def_value = 'sometext12345678900987654321'
self.assertNotEqual(def_value,
cache_manage.env('PATH', default=def_value))
def test_cache_manage_env_default(self):
def_value = 'sometext12345678900987654321'
self.assertEqual(def_value,
cache_manage.env('TMPVALUE1234567890',
default=def_value))
def test_lookup_command_unsupported_command(self):
self._run_command(['unsupported_command'], cache_manage.FAILURE)
glance-16.0.1/glance/tests/unit/api/__init__.py 0000666 0001750 0001750 00000000000 13267672245 021314 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/api/test_property_protections.py 0000666 0001750 0001750 00000033252 13267672245 025150 0 ustar zuul zuul 0000000 0000000 # Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.api import policy
from glance.api import property_protections
from glance.common import exception
from glance.common import property_utils
import glance.domain
from glance.tests import utils
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
class TestProtectedImageRepoProxy(utils.BaseTestCase):
class ImageRepoStub(object):
def __init__(self, fixtures):
self.fixtures = fixtures
def get(self, image_id):
for f in self.fixtures:
if f.image_id == image_id:
return f
else:
raise ValueError(image_id)
def list(self, *args, **kwargs):
return self.fixtures
def add(self, image):
self.fixtures.append(image)
def setUp(self):
super(TestProtectedImageRepoProxy, self).setUp()
self.set_property_protections()
self.policy = policy.Enforcer()
self.property_rules = property_utils.PropertyRules(self.policy)
self.image_factory = glance.domain.ImageFactory()
extra_props = {'spl_create_prop': 'c',
'spl_read_prop': 'r',
'spl_update_prop': 'u',
'spl_delete_prop': 'd',
'forbidden': 'prop'}
extra_props_2 = {'spl_read_prop': 'r', 'forbidden': 'prop'}
self.fixtures = [
self.image_factory.new_image(image_id='1', owner=TENANT1,
extra_properties=extra_props),
self.image_factory.new_image(owner=TENANT2, visibility='public'),
self.image_factory.new_image(image_id='3', owner=TENANT1,
extra_properties=extra_props_2),
]
self.context = glance.context.RequestContext(roles=['spl_role'])
image_repo = self.ImageRepoStub(self.fixtures)
self.image_repo = property_protections.ProtectedImageRepoProxy(
image_repo, self.context, self.property_rules)
def test_get_image(self):
image_id = '1'
result_image = self.image_repo.get(image_id)
result_extra_props = result_image.extra_properties
self.assertEqual('c', result_extra_props['spl_create_prop'])
self.assertEqual('r', result_extra_props['spl_read_prop'])
self.assertEqual('u', result_extra_props['spl_update_prop'])
self.assertEqual('d', result_extra_props['spl_delete_prop'])
self.assertNotIn('forbidden', result_extra_props.keys())
def test_list_image(self):
result_images = self.image_repo.list()
self.assertEqual(3, len(result_images))
result_extra_props = result_images[0].extra_properties
self.assertEqual('c', result_extra_props['spl_create_prop'])
self.assertEqual('r', result_extra_props['spl_read_prop'])
self.assertEqual('u', result_extra_props['spl_update_prop'])
self.assertEqual('d', result_extra_props['spl_delete_prop'])
self.assertNotIn('forbidden', result_extra_props.keys())
result_extra_props = result_images[1].extra_properties
self.assertEqual({}, result_extra_props)
result_extra_props = result_images[2].extra_properties
self.assertEqual('r', result_extra_props['spl_read_prop'])
self.assertNotIn('forbidden', result_extra_props.keys())
class TestProtectedImageProxy(utils.BaseTestCase):
def setUp(self):
super(TestProtectedImageProxy, self).setUp()
self.set_property_protections()
self.policy = policy.Enforcer()
self.property_rules = property_utils.PropertyRules(self.policy)
class ImageStub(object):
def __init__(self, extra_prop):
self.extra_properties = extra_prop
def test_read_image_with_extra_prop(self):
context = glance.context.RequestContext(roles=['spl_role'])
extra_prop = {'spl_read_prop': 'read', 'spl_fake_prop': 'prop'}
image = self.ImageStub(extra_prop)
result_image = property_protections.ProtectedImageProxy(
image, context, self.property_rules)
result_extra_props = result_image.extra_properties
self.assertEqual('read', result_extra_props['spl_read_prop'])
self.assertNotIn('spl_fake_prop', result_extra_props.keys())
class TestExtraPropertiesProxy(utils.BaseTestCase):
def setUp(self):
super(TestExtraPropertiesProxy, self).setUp()
self.set_property_protections()
self.policy = policy.Enforcer()
self.property_rules = property_utils.PropertyRules(self.policy)
def test_read_extra_property_as_admin_role(self):
extra_properties = {'foo': 'bar', 'ping': 'pong'}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
test_result = extra_prop_proxy['foo']
self.assertEqual('bar', test_result)
def test_read_extra_property_as_unpermitted_role(self):
extra_properties = {'foo': 'bar', 'ping': 'pong'}
context = glance.context.RequestContext(roles=['unpermitted_role'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
self.assertRaises(KeyError, extra_prop_proxy.__getitem__, 'foo')
def test_update_extra_property_as_permitted_role_after_read(self):
extra_properties = {'foo': 'bar', 'ping': 'pong'}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
extra_prop_proxy['foo'] = 'par'
self.assertEqual('par', extra_prop_proxy['foo'])
def test_update_extra_property_as_unpermitted_role_after_read(self):
extra_properties = {'spl_read_prop': 'bar'}
context = glance.context.RequestContext(roles=['spl_role'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
self.assertRaises(exception.ReservedProperty,
extra_prop_proxy.__setitem__,
'spl_read_prop', 'par')
def test_update_reserved_extra_property(self):
extra_properties = {'spl_create_prop': 'bar'}
context = glance.context.RequestContext(roles=['spl_role'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
self.assertRaises(exception.ReservedProperty,
extra_prop_proxy.__setitem__, 'spl_create_prop',
'par')
def test_update_empty_extra_property(self):
extra_properties = {'foo': ''}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
extra_prop_proxy['foo'] = 'bar'
self.assertEqual('bar', extra_prop_proxy['foo'])
def test_create_extra_property_admin(self):
extra_properties = {}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
extra_prop_proxy['boo'] = 'doo'
self.assertEqual('doo', extra_prop_proxy['boo'])
def test_create_reserved_extra_property(self):
extra_properties = {}
context = glance.context.RequestContext(roles=['spl_role'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
self.assertRaises(exception.ReservedProperty,
extra_prop_proxy.__setitem__, 'boo',
'doo')
def test_delete_extra_property_as_admin_role(self):
extra_properties = {'foo': 'bar'}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
del extra_prop_proxy['foo']
self.assertRaises(KeyError, extra_prop_proxy.__getitem__, 'foo')
def test_delete_nonexistant_extra_property_as_admin_role(self):
extra_properties = {}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
self.assertRaises(KeyError, extra_prop_proxy.__delitem__, 'foo')
def test_delete_reserved_extra_property(self):
extra_properties = {'spl_read_prop': 'r'}
context = glance.context.RequestContext(roles=['spl_role'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
# Ensure property has been created and can be read
self.assertEqual('r', extra_prop_proxy['spl_read_prop'])
self.assertRaises(exception.ReservedProperty,
extra_prop_proxy.__delitem__, 'spl_read_prop')
def test_delete_nonexistant_extra_property(self):
extra_properties = {}
roles = ['spl_role']
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
roles, extra_properties, self.property_rules)
self.assertRaises(KeyError,
extra_prop_proxy.__delitem__, 'spl_read_prop')
def test_delete_empty_extra_property(self):
extra_properties = {'foo': ''}
context = glance.context.RequestContext(roles=['admin'])
extra_prop_proxy = property_protections.ExtraPropertiesProxy(
context, extra_properties, self.property_rules)
del extra_prop_proxy['foo']
self.assertNotIn('foo', extra_prop_proxy)
class TestProtectedImageFactoryProxy(utils.BaseTestCase):
def setUp(self):
super(TestProtectedImageFactoryProxy, self).setUp()
self.set_property_protections()
self.policy = policy.Enforcer()
self.property_rules = property_utils.PropertyRules(self.policy)
self.factory = glance.domain.ImageFactory()
def test_create_image_no_extra_prop(self):
self.context = glance.context.RequestContext(tenant=TENANT1,
roles=['spl_role'])
self.image_factory = property_protections.ProtectedImageFactoryProxy(
self.factory, self.context,
self.property_rules)
extra_props = {}
image = self.image_factory.new_image(extra_properties=extra_props)
expected_extra_props = {}
self.assertEqual(expected_extra_props, image.extra_properties)
def test_create_image_extra_prop(self):
self.context = glance.context.RequestContext(tenant=TENANT1,
roles=['spl_role'])
self.image_factory = property_protections.ProtectedImageFactoryProxy(
self.factory, self.context,
self.property_rules)
extra_props = {'spl_create_prop': 'c'}
image = self.image_factory.new_image(extra_properties=extra_props)
expected_extra_props = {'spl_create_prop': 'c'}
self.assertEqual(expected_extra_props, image.extra_properties)
def test_create_image_extra_prop_reserved_property(self):
self.context = glance.context.RequestContext(tenant=TENANT1,
roles=['spl_role'])
self.image_factory = property_protections.ProtectedImageFactoryProxy(
self.factory, self.context,
self.property_rules)
extra_props = {'foo': 'bar', 'spl_create_prop': 'c'}
# no reg ex for property 'foo' is mentioned for spl_role in config
self.assertRaises(exception.ReservedProperty,
self.image_factory.new_image,
extra_properties=extra_props)
def test_create_image_extra_prop_admin(self):
self.context = glance.context.RequestContext(tenant=TENANT1,
roles=['admin'])
self.image_factory = property_protections.ProtectedImageFactoryProxy(
self.factory, self.context,
self.property_rules)
extra_props = {'foo': 'bar', 'spl_create_prop': 'c'}
image = self.image_factory.new_image(extra_properties=extra_props)
expected_extra_props = {'foo': 'bar', 'spl_create_prop': 'c'}
self.assertEqual(expected_extra_props, image.extra_properties)
def test_create_image_extra_prop_invalid_role(self):
self.context = glance.context.RequestContext(tenant=TENANT1,
roles=['imaginary-role'])
self.image_factory = property_protections.ProtectedImageFactoryProxy(
self.factory, self.context,
self.property_rules)
extra_props = {'foo': 'bar', 'spl_create_prop': 'c'}
self.assertRaises(exception.ReservedProperty,
self.image_factory.new_image,
extra_properties=extra_props)
glance-16.0.1/glance/tests/unit/api/test_cmd.py 0000666 0001750 0001750 00000012706 13267672245 021377 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import glance_store as store
import mock
from oslo_config import cfg
from oslo_log import log as logging
import six
import glance.cmd.api
import glance.cmd.cache_cleaner
import glance.cmd.cache_pruner
import glance.common.config
from glance.common import exception as exc
import glance.common.wsgi
import glance.image_cache.cleaner
import glance.image_cache.pruner
from glance.tests import utils as test_utils
CONF = cfg.CONF
class TestGlanceApiCmd(test_utils.BaseTestCase):
__argv_backup = None
def _do_nothing(self, *args, **kwargs):
pass
def _raise(self, exc):
def fake(*args, **kwargs):
raise exc
return fake
def setUp(self):
super(TestGlanceApiCmd, self).setUp()
self.__argv_backup = sys.argv
sys.argv = ['glance-api']
self.stderr = six.StringIO()
sys.stderr = self.stderr
store.register_opts(CONF)
self.stubs.Set(glance.common.config, 'load_paste_app',
self._do_nothing)
self.stubs.Set(glance.common.wsgi.Server, 'start',
self._do_nothing)
self.stubs.Set(glance.common.wsgi.Server, 'wait',
self._do_nothing)
def tearDown(self):
sys.stderr = sys.__stderr__
sys.argv = self.__argv_backup
super(TestGlanceApiCmd, self).tearDown()
def test_supported_default_store(self):
self.config(group='glance_store', default_store='file')
glance.cmd.api.main()
def test_worker_creation_failure(self):
failure = exc.WorkerCreationFailure(reason='test')
self.stubs.Set(glance.common.wsgi.Server, 'start',
self._raise(failure))
exit = self.assertRaises(SystemExit, glance.cmd.api.main)
self.assertEqual(2, exit.code)
@mock.patch.object(glance.common.config, 'parse_cache_args')
@mock.patch.object(logging, 'setup')
@mock.patch.object(glance.image_cache.ImageCache, 'init_driver')
@mock.patch.object(glance.image_cache.ImageCache, 'clean')
def test_cache_cleaner_main(self, mock_cache_clean,
mock_cache_init_driver, mock_log_setup,
mock_parse_config):
mock_cache_init_driver.return_value = None
manager = mock.MagicMock()
manager.attach_mock(mock_log_setup, 'mock_log_setup')
manager.attach_mock(mock_parse_config, 'mock_parse_config')
manager.attach_mock(mock_cache_init_driver, 'mock_cache_init_driver')
manager.attach_mock(mock_cache_clean, 'mock_cache_clean')
glance.cmd.cache_cleaner.main()
expected_call_sequence = [mock.call.mock_parse_config(),
mock.call.mock_log_setup(CONF, 'glance'),
mock.call.mock_cache_init_driver(),
mock.call.mock_cache_clean()]
self.assertEqual(expected_call_sequence, manager.mock_calls)
@mock.patch.object(glance.image_cache.base.CacheApp, '__init__')
def test_cache_cleaner_main_runtime_exception_handling(self, mock_cache):
mock_cache.return_value = None
self.stubs.Set(glance.image_cache.cleaner.Cleaner, 'run',
self._raise(RuntimeError))
exit = self.assertRaises(SystemExit, glance.cmd.cache_cleaner.main)
self.assertEqual('ERROR: ', exit.code)
@mock.patch.object(glance.common.config, 'parse_cache_args')
@mock.patch.object(logging, 'setup')
@mock.patch.object(glance.image_cache.ImageCache, 'init_driver')
@mock.patch.object(glance.image_cache.ImageCache, 'prune')
def test_cache_pruner_main(self, mock_cache_prune,
mock_cache_init_driver, mock_log_setup,
mock_parse_config):
mock_cache_init_driver.return_value = None
manager = mock.MagicMock()
manager.attach_mock(mock_log_setup, 'mock_log_setup')
manager.attach_mock(mock_parse_config, 'mock_parse_config')
manager.attach_mock(mock_cache_init_driver, 'mock_cache_init_driver')
manager.attach_mock(mock_cache_prune, 'mock_cache_prune')
glance.cmd.cache_pruner.main()
expected_call_sequence = [mock.call.mock_parse_config(),
mock.call.mock_log_setup(CONF, 'glance'),
mock.call.mock_cache_init_driver(),
mock.call.mock_cache_prune()]
self.assertEqual(expected_call_sequence, manager.mock_calls)
@mock.patch.object(glance.image_cache.base.CacheApp, '__init__')
def test_cache_pruner_main_runtime_exception_handling(self, mock_cache):
mock_cache.return_value = None
self.stubs.Set(glance.image_cache.pruner.Pruner, 'run',
self._raise(RuntimeError))
exit = self.assertRaises(SystemExit, glance.cmd.cache_pruner.main)
self.assertEqual('ERROR: ', exit.code)
glance-16.0.1/glance/tests/unit/api/middleware/ 0000775 0001750 0001750 00000000000 13267672475 021335 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/api/middleware/test_cache_manage.py 0000666 0001750 0001750 00000014302 13267672245 025316 0 ustar zuul zuul 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from glance.api import cached_images
from glance.api.middleware import cache_manage
import glance.common.config
import glance.common.wsgi
import glance.image_cache
from glance.tests import utils as test_utils
import mock
import webob
class TestCacheManageFilter(test_utils.BaseTestCase):
@mock.patch.object(glance.image_cache.ImageCache, "init_driver")
def setUp(self, mock_init_driver):
super(TestCacheManageFilter, self).setUp()
self.stub_application_name = "stubApplication"
self.stub_value = "Stub value"
self.image_id = "image_id_stub"
mock_init_driver.return_value = None
self.cache_manage_filter = cache_manage.CacheManageFilter(
self.stub_application_name)
def test_bogus_request(self):
# prepare
bogus_request = webob.Request.blank("/bogus/")
# call
resource = self.cache_manage_filter.process_request(bogus_request)
# check
self.assertIsNone(resource)
@mock.patch.object(cached_images.Controller, "get_cached_images")
def test_get_cached_images(self,
mock_get_cached_images):
# setup
mock_get_cached_images.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/cached_images")
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_get_cached_images.assert_called_with(request)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
@mock.patch.object(cached_images.Controller, "delete_cached_image")
def test_delete_cached_image(self,
mock_delete_cached_image):
# setup
mock_delete_cached_image.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/cached_images/" + self.image_id,
environ={'REQUEST_METHOD': "DELETE"})
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_delete_cached_image.assert_called_with(request,
image_id=self.image_id)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
@mock.patch.object(cached_images.Controller, "delete_cached_images")
def test_delete_cached_images(self,
mock_delete_cached_images):
# setup
mock_delete_cached_images.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/cached_images",
environ={'REQUEST_METHOD': "DELETE"})
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_delete_cached_images.assert_called_with(request)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
@mock.patch.object(cached_images.Controller, "queue_image")
def test_put_queued_image(self,
mock_queue_image):
# setup
mock_queue_image.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/queued_images/" + self.image_id,
environ={'REQUEST_METHOD': "PUT"})
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_queue_image.assert_called_with(request, image_id=self.image_id)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
@mock.patch.object(cached_images.Controller, "get_queued_images")
def test_get_queued_images(self,
mock_get_queued_images):
# setup
mock_get_queued_images.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/queued_images")
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_get_queued_images.assert_called_with(request)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
@mock.patch.object(cached_images.Controller, "delete_queued_image")
def test_delete_queued_image(self,
mock_delete_queued_image):
# setup
mock_delete_queued_image.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/queued_images/" + self.image_id,
environ={'REQUEST_METHOD': 'DELETE'})
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_delete_queued_image.assert_called_with(request,
image_id=self.image_id)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
@mock.patch.object(cached_images.Controller, "delete_queued_images")
def test_delete_queued_images(self,
mock_delete_queued_images):
# setup
mock_delete_queued_images.return_value = self.stub_value
# prepare
request = webob.Request.blank("/v1/queued_images",
environ={'REQUEST_METHOD': 'DELETE'})
# call
resource = self.cache_manage_filter.process_request(request)
# check
mock_delete_queued_images.assert_called_with(request)
self.assertEqual('"' + self.stub_value + '"',
resource.body.decode('utf-8'))
glance-16.0.1/glance/tests/unit/api/middleware/__init__.py 0000666 0001750 0001750 00000000000 13267672245 023431 0 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/unit/utils.py 0000666 0001750 0001750 00000024035 13267672245 020162 0 ustar zuul zuul 0000000 0000000 # Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cryptography import exceptions as crypto_exception
import glance_store as store
import mock
from oslo_config import cfg
from six.moves import urllib
from glance.common import exception
from glance.common import store_utils
from glance.common import wsgi
import glance.context
import glance.db.simple.api as simple_db
CONF = cfg.CONF
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
USER2 = '0b3b3006-cb76-4517-ae32-51397e22c754'
USER3 = '2hss8dkl-d8jh-88yd-uhs9-879sdjsd8skd'
BASE_URI = 'http://storeurl.com/container'
def sort_url_by_qs_keys(url):
# NOTE(kragniz): this only sorts the keys of the query string of a url.
# For example, an input of '/v2/tasks?sort_key=id&sort_dir=asc&limit=10'
# returns '/v2/tasks?limit=10&sort_dir=asc&sort_key=id'. This is to prevent
# non-deterministic ordering of the query string causing problems with unit
# tests.
parsed = urllib.parse.urlparse(url)
queries = urllib.parse.parse_qsl(parsed.query, True)
sorted_query = sorted(queries, key=lambda x: x[0])
encoded_sorted_query = urllib.parse.urlencode(sorted_query, True)
url_parts = (parsed.scheme, parsed.netloc, parsed.path,
parsed.params, encoded_sorted_query,
parsed.fragment)
return urllib.parse.urlunparse(url_parts)
def get_fake_request(path='', method='POST', is_admin=False, user=USER1,
roles=None, tenant=TENANT1):
if roles is None:
roles = ['member']
req = wsgi.Request.blank(path)
req.method = method
kwargs = {
'user': user,
'tenant': tenant,
'roles': roles,
'is_admin': is_admin,
}
req.context = glance.context.RequestContext(**kwargs)
return req
def fake_get_size_from_backend(uri, context=None):
return 1
def fake_get_verifier(context, img_signature_certificate_uuid,
img_signature_hash_method, img_signature,
img_signature_key_type):
verifier = mock.Mock()
if (img_signature is not None and img_signature == 'VALID'):
verifier.verify.return_value = None
else:
ex = crypto_exception.InvalidSignature()
verifier.verify.side_effect = ex
return verifier
def get_fake_context(user=USER1, tenant=TENANT1, roles=None, is_admin=False):
if roles is None:
roles = ['member']
kwargs = {
'user': user,
'tenant': tenant,
'roles': roles,
'is_admin': is_admin,
}
context = glance.context.RequestContext(**kwargs)
return context
class FakeDB(object):
def __init__(self, initialize=True):
self.reset()
if initialize:
self.init_db()
@staticmethod
def init_db():
images = [
{'id': UUID1, 'owner': TENANT1, 'status': 'queued',
'locations': [{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}, 'status': 'queued'}],
'disk_format': 'raw', 'container_format': 'bare'},
{'id': UUID2, 'owner': TENANT1, 'status': 'queued',
'disk_format': 'raw', 'container_format': 'bare'},
]
[simple_db.image_create(None, image) for image in images]
members = [
{'image_id': UUID1, 'member': TENANT1, 'can_share': True},
{'image_id': UUID1, 'member': TENANT2, 'can_share': False},
]
[simple_db.image_member_create(None, member) for member in members]
simple_db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
@staticmethod
def reset():
simple_db.reset()
def __getattr__(self, key):
return getattr(simple_db, key)
class FakeStoreUtils(object):
def __init__(self, store_api):
self.store_api = store_api
def safe_delete_from_backend(self, context, id, location):
try:
del self.store_api.data[location['url']]
except KeyError:
pass
def schedule_delayed_delete_from_backend(self, context, id, location):
pass
def delete_image_location_from_backend(self, context,
image_id, location):
if CONF.delayed_delete:
self.schedule_delayed_delete_from_backend(context, image_id,
location)
else:
self.safe_delete_from_backend(context, image_id, location)
def validate_external_location(self, uri):
if uri and urllib.parse.urlparse(uri).scheme:
return store_utils.validate_external_location(uri)
else:
return True
class FakeStoreAPI(object):
def __init__(self, store_metadata=None):
self.data = {
'%s/%s' % (BASE_URI, UUID1): ('XXX', 3),
'%s/fake_location' % (BASE_URI): ('YYY', 3)
}
self.acls = {}
if store_metadata is None:
self.store_metadata = {}
else:
self.store_metadata = store_metadata
def create_stores(self):
pass
def set_acls(self, uri, public=False, read_tenants=None,
write_tenants=None, context=None):
if read_tenants is None:
read_tenants = []
if write_tenants is None:
write_tenants = []
self.acls[uri] = {
'public': public,
'read': read_tenants,
'write': write_tenants,
}
def get_from_backend(self, location, offset=0,
chunk_size=None, context=None):
try:
scheme = location[:location.find('/') - 1]
if scheme == 'unknown':
raise store.UnknownScheme(scheme=scheme)
return self.data[location]
except KeyError:
raise store.NotFound(image=location)
def get_size_from_backend(self, location, context=None):
return self.get_from_backend(location, context=context)[1]
def add_to_backend(self, conf, image_id, data, size,
scheme=None, context=None, verifier=None):
store_max_size = 7
current_store_size = 2
for location in self.data.keys():
if image_id in location:
raise exception.Duplicate()
if not size:
# 'data' is a string wrapped in a LimitingReader|CooperativeReader
# pipeline, so peek under the hood of those objects to get at the
# string itself.
size = len(data.data.fd)
if (current_store_size + size) > store_max_size:
raise exception.StorageFull()
if context.user == USER2:
raise exception.Forbidden()
if context.user == USER3:
raise exception.StorageWriteDenied()
self.data[image_id] = (data, size)
checksum = 'Z'
return (image_id, size, checksum, self.store_metadata)
def check_location_metadata(self, val, key=''):
store.check_location_metadata(val)
def delete_from_backend(self, uri, context=None):
pass
class FakePolicyEnforcer(object):
def __init__(self, *_args, **kwargs):
self.rules = {}
def enforce(self, _ctxt, action, target=None, **kwargs):
"""Raise Forbidden if a rule for given action is set to false."""
if self.rules.get(action) is False:
raise exception.Forbidden()
def set_rules(self, rules):
self.rules = rules
class FakeNotifier(object):
def __init__(self, *_args, **kwargs):
self.log = []
def _notify(self, event_type, payload, level):
log = {
'notification_type': level,
'event_type': event_type,
'payload': payload
}
self.log.append(log)
def warn(self, event_type, payload):
self._notify(event_type, payload, 'WARN')
def info(self, event_type, payload):
self._notify(event_type, payload, 'INFO')
def error(self, event_type, payload):
self._notify(event_type, payload, 'ERROR')
def debug(self, event_type, payload):
self._notify(event_type, payload, 'DEBUG')
def critical(self, event_type, payload):
self._notify(event_type, payload, 'CRITICAL')
def get_logs(self):
return self.log
class FakeGateway(object):
def __init__(self, image_factory=None, image_member_factory=None,
image_repo=None, task_factory=None, task_repo=None):
self.image_factory = image_factory
self.image_member_factory = image_member_factory
self.image_repo = image_repo
self.task_factory = task_factory
self.task_repo = task_repo
def get_image_factory(self, context):
return self.image_factory
def get_image_member_factory(self, context):
return self.image_member_factory
def get_repo(self, context):
return self.image_repo
def get_task_factory(self, context):
return self.task_factory
def get_task_repo(self, context):
return self.task_repo
class FakeTask(object):
def __init__(self, task_id, type=None, status=None):
self.task_id = task_id
self.type = type
self.message = None
self.input = None
self._status = status
self._executor = None
def success(self, result):
self.result = result
self._status = 'success'
def fail(self, message):
self.message = message
self._status = 'failure'
glance-16.0.1/glance/tests/var/ 0000775 0001750 0001750 00000000000 13267672475 016260 5 ustar zuul zuul 0000000 0000000 glance-16.0.1/glance/tests/var/certificate.crt 0000666 0001750 0001750 00000012616 13267672245 021257 0 ustar zuul zuul 0000000 0000000 # > openssl x509 -in glance/tests/var/certificate.crt -noout -text
# Certificate:
# Data:
# Version: 1 (0x0)
# Serial Number: 1 (0x1)
# Signature Algorithm: sha1WithRSAEncryption
# Issuer: C=AU, ST=Some-State, O=OpenStack, OU=Glance, CN=Glance CA
# Validity
# Not Before: Feb 2 20:22:13 2015 GMT
# Not After : Jan 31 20:22:13 2024 GMT
# Subject: C=AU, ST=Some-State, O=OpenStack, OU=Glance, CN=127.0.0.1
# Subject Public Key Info:
# Public Key Algorithm: rsaEncryption
# RSA Public Key: (4096 bit)
# Modulus (4096 bit):
# 00:9f:44:13:51:de:e9:5a:f7:ac:33:2a:1a:4c:91:
# a1:73:bc:f3:a6:d3:e6:59:ae:e8:e2:34:68:3e:f4:
# 40:c1:a1:1a:65:9a:a3:67:e9:2c:b9:79:9c:00:b1:
# 7c:c1:e6:9e:de:47:bf:f1:cb:f2:73:d4:c3:62:fe:
# 82:90:6f:b4:75:ca:7e:56:8f:99:3d:06:51:3c:40:
# f4:ff:74:97:4f:0d:d2:e6:66:76:8d:97:bf:89:ce:
# fe:b2:d7:89:71:f2:a0:d9:f5:26:7c:1a:7a:bf:2b:
# 8f:72:80:e7:1f:4d:4a:40:a3:b9:9e:33:f6:55:e0:
# 40:2b:1e:49:e4:8c:71:9d:11:32:cf:21:41:e1:13:
# 28:c6:d6:f6:e0:b3:26:10:6d:5b:63:1d:c3:ee:d0:
# c4:66:63:38:89:6b:8f:2a:c2:bd:4f:e4:bc:03:8f:
# a2:f2:5c:1d:73:11:9c:7b:93:3d:d6:a3:d1:2d:cd:
# 64:23:24:bc:65:3c:71:20:28:60:a0:ea:fe:77:0e:
# 1d:95:36:76:ad:e7:2f:1c:27:62:55:e3:9d:11:c1:
# fb:43:3e:e5:21:ac:fd:0e:7e:3d:c9:44:d2:bd:6f:
# 89:7e:0f:cb:88:54:57:fd:8d:21:c8:34:e1:47:01:
# 28:0f:45:a1:7e:60:1a:9c:4c:0c:b8:c1:37:2d:46:
# ab:18:9e:ca:49:d3:77:b7:92:3a:d2:7f:ca:d5:02:
# f1:75:81:66:39:51:aa:bc:d7:f0:91:23:69:e8:71:
# ae:44:76:5e:87:54:eb:72:fc:ac:fd:60:22:e0:6a:
# e4:ad:37:b7:f6:e5:24:b4:95:2c:26:0e:75:a0:e9:
# ed:57:be:37:42:64:1f:02:49:0c:bd:5d:74:6d:e6:
# f2:da:5c:54:82:fa:fc:ff:3a:e4:1a:7a:a9:3c:3d:
# ee:b5:df:09:0c:69:c3:51:92:67:80:71:9b:10:8b:
# 20:ff:a2:5e:c5:f2:86:a0:06:65:1c:42:f9:91:24:
# 54:29:ed:7e:ec:db:4c:7b:54:ee:b1:25:1b:38:53:
# ae:01:b6:c5:93:1e:a3:4d:1b:e8:73:47:50:57:e8:
# ec:a0:80:53:b1:34:74:37:9a:c1:8c:14:64:2e:16:
# dd:a1:2e:d3:45:3e:2c:46:62:20:2a:93:7a:92:4c:
# b2:cc:64:47:ad:63:32:0b:68:0c:24:98:20:83:08:
# 35:74:a7:68:7a:ef:d6:84:07:d1:5e:d7:c0:6c:3f:
# a7:4a:78:62:a8:70:75:37:fb:ce:1f:09:1e:7c:11:
# 35:cc:b3:5a:a3:cc:3f:35:c9:ee:24:6f:63:f8:54:
# 6f:7c:5b:b4:76:3d:f2:81:6d:ad:64:66:10:d0:c4:
# 0b:2c:2f
# Exponent: 65537 (0x10001)
# Signature Algorithm: sha1WithRSAEncryption
# 5f:e8:a8:93:20:6c:0f:12:90:a6:e2:64:21:ed:63:0e:8c:e0:
# 0f:d5:04:13:4d:2a:e9:a5:91:b7:e4:51:94:bd:0a:70:4b:94:
# c7:1c:94:ed:d7:64:95:07:6b:a1:4a:bc:0b:53:b5:1a:7e:f1:
# 9c:12:59:24:5f:36:72:34:ca:33:ee:28:46:fd:21:e6:52:19:
# 0c:3d:94:6b:bd:cb:76:a1:45:7f:30:7b:71:f1:84:b6:3c:e0:
# ac:af:13:81:9c:0e:6e:3c:9b:89:19:95:de:8e:9c:ef:70:ac:
# 07:ae:74:42:47:35:50:88:36:ec:32:1a:55:24:08:f2:44:57:
# 67:fe:0a:bb:6b:a7:bd:bc:af:bf:2a:e4:dd:53:84:6b:de:1d:
# 2a:28:21:38:06:7a:5b:d8:83:15:65:31:6d:61:67:00:9e:1a:
# 61:85:15:a2:4c:9a:eb:6d:59:8e:34:ac:2c:d5:24:4e:00:ff:
# 30:4d:a3:d5:80:63:17:52:65:ac:7f:f4:0a:8e:56:a4:97:51:
# 39:81:ae:e8:cb:52:09:b3:47:b4:fd:1b:e2:04:f9:f2:76:e3:
# 63:ef:90:aa:54:98:96:05:05:a9:91:76:18:ed:5d:9e:6e:88:
# 50:9a:f7:2c:ce:5e:54:ba:15:ec:62:ff:5d:be:af:35:03:b1:
# 3f:32:3e:0e
-----BEGIN CERTIFICATE-----
MIIEKjCCAxICAQEwDQYJKoZIhvcNAQEFBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV
BAgMClNvbWUtU3RhdGUxEjAQBgNVBAoMCU9wZW5TdGFjazEPMA0GA1UECwwGR2xh
bmNlMRIwEAYDVQQDDAlHbGFuY2UgQ0EwHhcNMTUwMjAyMjAyMjEzWhcNMjQwMTMx
MjAyMjEzWjBbMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTESMBAG
A1UEChMJT3BlblN0YWNrMQ8wDQYDVQQLEwZHbGFuY2UxEjAQBgNVBAMTCTEyNy4w
LjAuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ9EE1He6Vr3rDMq
GkyRoXO886bT5lmu6OI0aD70QMGhGmWao2fpLLl5nACxfMHmnt5Hv/HL8nPUw2L+
gpBvtHXKflaPmT0GUTxA9P90l08N0uZmdo2Xv4nO/rLXiXHyoNn1Jnwaer8rj3KA
5x9NSkCjuZ4z9lXgQCseSeSMcZ0RMs8hQeETKMbW9uCzJhBtW2Mdw+7QxGZjOIlr
jyrCvU/kvAOPovJcHXMRnHuTPdaj0S3NZCMkvGU8cSAoYKDq/ncOHZU2dq3nLxwn
YlXjnRHB+0M+5SGs/Q5+PclE0r1viX4Py4hUV/2NIcg04UcBKA9FoX5gGpxMDLjB
Ny1GqxieyknTd7eSOtJ/ytUC8XWBZjlRqrzX8JEjaehxrkR2XodU63L8rP1gIuBq
5K03t/blJLSVLCYOdaDp7Ve+N0JkHwJJDL1ddG3m8tpcVIL6/P865Bp6qTw97rXf
CQxpw1GSZ4BxmxCLIP+iXsXyhqAGZRxC+ZEkVCntfuzbTHtU7rElGzhTrgG2xZMe
o00b6HNHUFfo7KCAU7E0dDeawYwUZC4W3aEu00U+LEZiICqTepJMssxkR61jMgto
DCSYIIMINXSnaHrv1oQH0V7XwGw/p0p4YqhwdTf7zh8JHnwRNcyzWqPMPzXJ7iRv
Y/hUb3xbtHY98oFtrWRmENDECywvAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAF/o
qJMgbA8SkKbiZCHtYw6M4A/VBBNNKumlkbfkUZS9CnBLlMcclO3XZJUHa6FKvAtT
tRp+8ZwSWSRfNnI0yjPuKEb9IeZSGQw9lGu9y3ahRX8we3HxhLY84KyvE4GcDm48
m4kZld6OnO9wrAeudEJHNVCINuwyGlUkCPJEV2f+Crtrp728r78q5N1ThGveHSoo
ITgGelvYgxVlMW1hZwCeGmGFFaJMmuttWY40rCzVJE4A/zBNo9WAYxdSZax/9AqO
VqSXUTmBrujLUgmzR7T9G+IE+fJ242PvkKpUmJYFBamRdhjtXZ5uiFCa9yzOXlS6
Fexi/12+rzUDsT8yPg4=
-----END CERTIFICATE-----
glance-16.0.1/glance/tests/var/ca.key 0000666 0001750 0001750 00000003250 13267672245 017352 0 ustar zuul zuul 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcW4cRtw96/ZYs
x3UB1jWWT0pAlsMQ03En7dueh9o4UZYChY2NMqTJ3gVqy1vf4wyRU1ROb/N5L4Kd
QiJARH/ARbV+qrWoRvkcWBfg9w/4uZ9ZFhCBbaa2cAtTIGzVta6HP9UPeyfXrS+j
gjqU2QN3bcc0ZCMAiQbtW7Vpw8RNr0NvTJDaSCzmpGQ7TQtB0jXm1nSG7FZUbojU
CYB6TBGd01Cg8GzAai3ngXDq6foVJEwfmaV2Zapb0A4FLquXOzebskY5EL/okQGP
ofSRCu/ar+HV4HN3+PgIIrfa8RhDDdlv6qE1iEuS6isSH1s+7BA2ZKfzT5t8G/8l
SjKa/r2pAgMBAAECggEABeoS+v+906BAypzj4BO+xnUEWi1xuN7j951juqKM0dwm
uZSaEwMb9ysVXCNvKNgwOypQZfaNQ2BqEgx3XOA5yZBVabvtOkIFZ6RZp7kZ3aQl
yb9U3BR0WAsz0pxZL3c74vdsoYi9rgVA9ROGvP4CIM96fEZ/xgDnhbFjch5GA4u2
8XQ/kJUwLl0Uzxyo10sqGu3hgMwpM8lpaRW6d5EQ628rJEtA/Wmy5GpyCUhTD/5B
jE1IzhjT4T5LqiPjA/Dsmz4Sa0+MyKRmA+zfSH6uS4szSaj53GVMHh4K+Xg2/EeD
6I3hGOtzZuYp5HBHE6J8VgeuErBQf32CCglHqN/dLQKBgQD4XaXa+AZtB10cRUV4
LZDB1AePJLloBhKikeTboZyhZEwbNuvw3JSQBAfUdpx3+8Na3Po1Tfy3DlZaVCU2
0PWh2UYrtwA3dymp8GCuSvnsLz1kNGv0Q7WEYaepyKRO8qHCjrTDUFuGVztU+H6O
OWPHRd4DnyF3pKN7K4j6pU76HwKBgQDjIXylwPb6TD9ln13ijJ06t9l1E13dSS0B
+9QU3f4abjMmW0K7icrNdmsjHafWLGXP2dxB0k4sx448buH+L8uLjC8G80wLQMSJ
NAKpxIsmkOMpPUl80ks8bmzsqztmtql6kAgSwSW84vftJyNrFnp2kC2O4ZYGwz1+
8rj3nBrfNwKBgQDrCJxCyoIyPUy0yy0BnIUnmAILSSKXuV97LvtXiOnTpTmMa339
8pA4dUf/nLtXpA3r98BkH0gu50d6tbR92mMI5bdM+SIgWwk3g33KkrNN+iproFwk
zMqC23Mx7ejnuR6xIiEXz/y89eH0+C+zYcX1tz1xSe7+7PO0RK+dGkDR2wKBgHGR
L+MtPhDfCSAF9IqvpnpSrR+2BEv+J8wDIAMjEMgka9z06sQc3NOpL17KmD4lyu6H
z3L19fK8ASnEg6l2On9XI7iE9HP3+Y1k/SPny3AIKB1ZsKICAG6CBGK+J6BvGwTW
ecLu4rC0iCUDWdlUzvzzkGQN9dcBzoDoWoYsft83AoGAAh4MyrM32gwlUgQD8/jX
8rsJlKnme0qMjX4A66caBomjztsH2Qt6cH7DIHx+hU75pnDAuEmR9xqnX7wFTR9Y
0j/XqTVsTjDINRLgMkrg7wIqKtWdicibBx1ER9LzwfNwht/ZFeMLdeUUUYMNv3cg
cMSLxlxgFaUggYj/dsF6ypQ=
-----END PRIVATE KEY-----
glance-16.0.1/glance/tests/var/testserver-bad-ovf.ova 0000666 0001750 0001750 00000024000 13267672245 022502 0 ustar zuul zuul 0000000 0000000 illegal-xml.ovf 0000644 0001750 0001750 00000000076 12662226344 012147 0 ustar otc otc
does not match <>
testserver-disk1.vmdk 0000644 0001750 0001750 00000000004 12562114301 013301 0 ustar otc otc ABCD glance-16.0.1/glance/tests/var/ca.crt 0000666 0001750 0001750 00000002405 13267672245 017353 0 ustar zuul zuul 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDiTCCAnGgAwIBAgIJAMj+Lfpqc9lLMA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQKDAlPcGVuU3RhY2sx
DzANBgNVBAsMBkdsYW5jZTESMBAGA1UEAwwJR2xhbmNlIENBMB4XDTE1MDEzMTA1
MzAyNloXDTI1MDEyODA1MzAyNlowWzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNv
bWUtU3RhdGUxEjAQBgNVBAoMCU9wZW5TdGFjazEPMA0GA1UECwwGR2xhbmNlMRIw
EAYDVQQDDAlHbGFuY2UgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDcW4cRtw96/ZYsx3UB1jWWT0pAlsMQ03En7dueh9o4UZYChY2NMqTJ3gVqy1vf
4wyRU1ROb/N5L4KdQiJARH/ARbV+qrWoRvkcWBfg9w/4uZ9ZFhCBbaa2cAtTIGzV
ta6HP9UPeyfXrS+jgjqU2QN3bcc0ZCMAiQbtW7Vpw8RNr0NvTJDaSCzmpGQ7TQtB
0jXm1nSG7FZUbojUCYB6TBGd01Cg8GzAai3ngXDq6foVJEwfmaV2Zapb0A4FLquX
OzebskY5EL/okQGPofSRCu/ar+HV4HN3+PgIIrfa8RhDDdlv6qE1iEuS6isSH1s+
7BA2ZKfzT5t8G/8lSjKa/r2pAgMBAAGjUDBOMB0GA1UdDgQWBBT3M/WuigtS7JYZ
QD0XJEDD8JSZrTAfBgNVHSMEGDAWgBT3M/WuigtS7JYZQD0XJEDD8JSZrTAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCWOhC9kBZAJalQhAeNGIiiJ2bV
HpvzSCEXSEAdh3A0XDK1KxoMHy1LhNGYrMmN2a+2O3SoX0FLB4p9zOifq4ACwaMD
CjQeB/whsfPt5s0gV3mGMCR+V2b8r5H/30KRbIzQGXmy+/r6Wfe012jcVVXsQawW
Omd4d+Bduf5iiL1OCKEMepqjQLu7Yg41ucRpUewBA+A9hoKp7jpwSnzSALX7FWEQ
TBJtJ9jEnZl36S81eZJvOXSzeptHyomSAt8eGFCVuPB0dZCXuBNLu4Gsn+dIhfyj
NwK4noYZXMndPwGy92KDhjxVnHzd9HwImgr6atmWhPPz5hm50BrA7sv06Nto
-----END CERTIFICATE-----
glance-16.0.1/glance/tests/var/testserver.ova 0000666 0001750 0001750 00000050000 13267672245 021165 0 ustar zuul zuul 0000000 0000000 testserver.ovf 0000644 € ±!Ñ0004256 00000032107 12562113043 014337 0 ustar jjasekx intelall
List of the virtual disks used in the package
Logical networks used in the package
Logical network used by this appliance.
A virtual machine
The kind of installed guest operating system
Ubuntu_64
Ubuntu_64
Virtual hardware requirements for a virtual machine
Virtual Hardware Family
0
testserver
virtualbox-2.2
-
1 virtual CPU
Number of virtual CPUs
1 virtual CPU
1
3
1
-
MegaBytes
512 MB of memory
Memory Size
512 MB of memory
2
4
512
-
0
ideController0
IDE Controller
ideController0
3
PIIX4
5
-
1
ideController1
IDE Controller
ideController1
4
PIIX4
5
-
0
sataController0
SATA Controller
sataController0
5
AHCI
20
-
0
usb
USB Controller
usb
6
23
-
3
false
sound
Sound Card
sound
7
ensoniq1371
35
-
0
true
cdrom1
CD-ROM Drive
cdrom1
8
4
15
-
0
disk2
Disk Image
disk2
/disk/vmdisk2
9
5
17
-
true
Ethernet adapter on 'NAT'
NAT
Ethernet adapter on 'NAT'
10
E1000
10
DMTF:x86:64
DMTF:x86:VT-d
Complete VirtualBox machine configuration in VirtualBox format
testserver-disk1.vmdk 0000644 € ±!Ñ0004256 00000000004 12562114301 015504 0 ustar jjasekx intelall ABCD glance-16.0.1/glance/tests/var/testserver-no-disk.ova 0000666 0001750 0001750 00000050000 13267672245 022527 0 ustar zuul zuul 0000000 0000000 testserver.ovf 0000644 € ±!Ñ0004256 00000031307 12561117144 014345 0 ustar jjasekx intelall
List of the virtual disks used in the package
Logical networks used in the package
Logical network used by this appliance.
A virtual machine
The kind of installed guest operating system
Ubuntu_64
Ubuntu_64
Virtual hardware requirements for a virtual machine
Virtual Hardware Family
0
testserver
virtualbox-2.2
-
1 virtual CPU
Number of virtual CPUs
1 virtual CPU
1
3
1
-
MegaBytes
512 MB of memory
Memory Size
512 MB of memory
2
4
512
-
0
ideController0
IDE Controller
ideController0
3
PIIX4
5
-
1
ideController1
IDE Controller
ideController1
4
PIIX4
5
-
0
sataController0
SATA Controller
sataController0
5
AHCI
20
-
0
usb
USB Controller
usb
6
23
-
3
false
sound
Sound Card
sound
7
ensoniq1371
35
-
0
true
cdrom1
CD-ROM Drive
cdrom1
8
4
15
-
0
disk2
Disk Image
disk2
/disk/vmdisk2
9
5
17
-
true
Ethernet adapter on 'NAT'
NAT
Ethernet adapter on 'NAT'
10
E1000
10
Complete VirtualBox machine configuration in VirtualBox format
glance-16.0.1/glance/tests/var/privatekey.key 0000666 0001750 0001750 00000006253 13267672245 021160 0 ustar zuul zuul 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAn0QTUd7pWvesMyoaTJGhc7zzptPmWa7o4jRoPvRAwaEaZZqj
Z+ksuXmcALF8weae3ke/8cvyc9TDYv6CkG+0dcp+Vo+ZPQZRPED0/3SXTw3S5mZ2
jZe/ic7+steJcfKg2fUmfBp6vyuPcoDnH01KQKO5njP2VeBAKx5J5IxxnREyzyFB
4RMoxtb24LMmEG1bYx3D7tDEZmM4iWuPKsK9T+S8A4+i8lwdcxGce5M91qPRLc1k
IyS8ZTxxIChgoOr+dw4dlTZ2recvHCdiVeOdEcH7Qz7lIaz9Dn49yUTSvW+Jfg/L
iFRX/Y0hyDThRwEoD0WhfmAanEwMuME3LUarGJ7KSdN3t5I60n/K1QLxdYFmOVGq
vNfwkSNp6HGuRHZeh1Trcvys/WAi4GrkrTe39uUktJUsJg51oOntV743QmQfAkkM
vV10beby2lxUgvr8/zrkGnqpPD3utd8JDGnDUZJngHGbEIsg/6JexfKGoAZlHEL5
kSRUKe1+7NtMe1TusSUbOFOuAbbFkx6jTRvoc0dQV+jsoIBTsTR0N5rBjBRkLhbd
oS7TRT4sRmIgKpN6kkyyzGRHrWMyC2gMJJgggwg1dKdoeu/WhAfRXtfAbD+nSnhi
qHB1N/vOHwkefBE1zLNao8w/NcnuJG9j+FRvfFu0dj3ygW2tZGYQ0MQLLC8CAwEA
AQKCAgBL4IvvymqUu0CgE6P57LvlvxS522R4P7uV4W/05jtfxJgl5fmJzO5Q4x4u
umB8pJn1vms1EHxPMQNxS1364C0ynSl5pepUx4i2UyAmAG8B680ZlaFPrgdD6Ykw
vT0vO2/kx0XxhFAMef1aiQ0TvaftidMqCwmGOlN393Mu3rZWJVZ2lhqj15Pqv4lY
3iD5XJBYdVrekTmwqf7KgaLwtVyqDoiAjdMM8lPZeX965FhmxR8oWh0mHR9gf95J
etMmdy6Km//+EbeS/HxWRnE0CD/RsQA7NmDFnXvmhsB6/j4EoHn5xB6ssbpGAxIg
JwlY4bUrKXpaEgE7i4PYFb1q5asnTDdUZYAGAGXSBbDiUZM2YOe1aaFB/SA3Y3K2
47brnx7UXhAXSPJ16EZHejSeFbzZfWgj2J1t3DLk18Fpi/5AxxIy/N5J38kcP7xZ
RIcSV1QEasYUrHI9buhuJ87tikDBDFEIIeLZxlyeIdwmKrQ7Vzny5Ls94Wg+2UtI
XFLDak5SEugdp3LmmTJaugF+s/OiglBVhcaosoKRXb4K29M7mQv2huEAerFA14Bd
dp2KByd8ue+fJrAiSxhAyMDAe/uv0ixnmBBtMH0YYHbfUIgl+kR1Ns/bxrJu7T7F
kBQWZV4NRbSRB+RGOG2/Ai5jxu0uLu3gtHMO4XzzElWqzHEDoQKCAQEAzfaSRA/v
0831TDL8dmOCO61TQ9GtAa8Ouj+SdyTwk9f9B7NqQWg7qdkbQESpaDLvWYiftoDw
mBFHLZe/8RHBaQpEAfbC/+DO6c7O+g1/0Cls33D5VaZOzFnnbHktT3r5xwkZfVBS
aPPWl/IZOU8TtNqujQA+mmSnrJ7IuXSsBVq71xgBQT9JBZpUcjZ4eQducmtC43CP
GqcSjq559ZKc/sa3PkAtNlKzSUS1abiMcJ86C9PgQ9gOu7y8SSqQ3ivZkVM99rxm
wo8KehCcHOPOcIUQKmx4Bs4V3chm8rvygf3aanUHi83xaMeFtIIuOgAJmE9wGQeo
k0UGvKBUDIenfwKCAQEAxfVFVxMBfI4mHrgTj/HOq7GMts8iykJK1PuELU6FZhex
XOqXRbQ5dCLsyehrKlVPFqUENhXNHaOQrCOZxiVoRje2PfU/1fSqRaPxI7+W1Fsh
Fq4PkdJ66NJZJkK5NHwE8SyQf+wpLdL3YhY5LM3tWdX5U9Rr6N8qelE3sLPssAak
1km4/428+rkp1BlCffr3FyL0KJmOYfMiAr8m6hRZWbhkvm5YqX1monxUrKdFJ218
dxzyniqoS1yU5RClY6783dql1UO4AvxpzpCPYDFIwbEb9zkUo0przhmi4KzyxknB
/n/viMWzSnsM9YbakH6KunDTUteme1Dri3Drrq9TUQKCAQAVdvL7YOXPnxFHZbDl
7azu5ztcQAfVuxa/1kw/WnwwDDx0hwA13NUK+HNcmUtGbrh/DjwG2x032+UdHUmF
qCIN/mHkCoF8BUPLHiB38tw1J3wPNUjm4jQoG96AcYiFVf2d/pbHdo2AHplosHRs
go89M+UpELN1h7Ppy4qDuWMME86rtfa7hArqKJFQbdjUVC/wgLkx1tMzJeJLOGfB
bgwqiS8jr7CGjsvcgOqfH/qS6iU0glpG98dhTWQaA/OhE9TSzmgQxMW41Qt0eTKr
2Bn1pAhxQ2im3Odue6ou9eNqJLiUi6nDqizUjKakj0SeCs71LqIyGZg58OGo2tSn
kaOlAoIBAQCE/fO4vQcJpAJOLwLNePmM9bqAcoZ/9auKjPNO8OrEHPTGZMB+Tscu
k+wa9a9RgICiyPgcUec8m0+tpjlAGo+EZRdlZqedWUMviCWQC74MKrD/KK9DG3IB
ipfkEX2VmiBD2tm1Z3Z+17XlSuLci/iCmzNnM1XP3GYQSRIt/6Lq23vQjzTfU1z7
4HwOh23Zb0qjW5NG12sFuS9HQx6kskkY8r2UBlRAggP686Z7W+EkzPSKnYMN6cCo
6KkLf3RtlPlDHwq8TUOJlgSLhykbyeCEaDVOkSWhUnU8wJJheS+dMZ5IGbFWZOPA
DQ02woOCAdG30ebXSBQL0uB8DL/52sYRAoIBAHtW3NomlxIMqWX8ZYRJIoGharx4
ikTOR/jeETb9t//n6kV19c4ICiXOQp062lwEqFvHkKzxKECFhJZuwFc09hVxUXxC
LJjvDfauHWFHcrDTWWbd25CNeZ4Sq79GKf+HJ+Ov87WYcjuBFlCh8ES+2N4WZGCn
B5oBq1g6E4p1k6xA5eE6VRiHPuFH8N9t1x6IlCZvZBhuVWdDrDd4qMSDEUTlcxSY
mtcAIXTPaPcdb3CjdE5a38r59x7dZ/Te2K7FKETffjSmku7BrJITz3iXEk+sn8ex
o3mdnFgeQ6/hxvMGgdK2qNb5ER/s0teFjnfnwHuTSXngMDIDb3kLL0ecWlQ=
-----END RSA PRIVATE KEY-----
glance-16.0.1/glance/tests/var/testserver-not-tar.ova 0000666 0001750 0001750 00000501041 13267672245 022555 0 ustar zuul zuul 0000000 0000000 ‰PNG
IHDR Ð Ð š8Äy bKGD ÿ ÿ ÿ ½§“ IDATxœìÝ{°ßõß÷×ç§£÷ƒ±àn²À‹ #Ë—µM@™É?Ùf:Ó‹·N›]Ï&›ŒÁN“íÎtšië’Nþj“i;“Ä©/é:»$í&m³Û7‹¸-`ƒ±± „$0F@ÂB Ëù}¿Ÿwÿ8Gn¶Á #¡Çã?~çwÄ÷óŸ†'¯Ï7 à”Ó–û 83Ü}ý³+²æâ!ýâ±Õúiµƒ™<´¢¦ç´¾bl3õÂtºâ†Iµb÷CO<±çËI_îç Î: ïÈ=›7Χ³Õ'siã|2™Mj6Í¥j>Él’¹$óIÖ¼îן?2Öycòü˜vyÿ;ÕBUûfUýz’ôªïTÚúJík=>Vîo}ü“é»úÕd<‰G Î: I’;¶nY¹Ïº†¹´6ŸÔlªÍ¥Õ|Òf“šK-}ÞÚlÞÅß®ŽŒuîØòüXí²$“$UõͪöëIÒ+÷÷dK’•Iª6¤ÕŽ¡ò`Kî™™¬þÃÿè±Çöü‚Ï D@ x_ûÖ–
ç¯&—%}i)~,€_‰WfÓ2—ÅÅøÉöü‘ÞÏÒööj—æhDó;Õòù$«}§R7$Y]É*µ¾§¿0övvÒ†¡çžÖê_ÍMûïÿùÝ»/à €÷ à4RÉäÞÍ/yÃÕéæR9~mzÕü»´?^8ÒûÙ=íÅáÄ%ú ½ª}g<ÑX©+{òÂØëìžÇjC*/©ÿu&+þù¾cÇËËz" à´$ ,³G7mZµo2½¬M†ùªÉlª^·Ïü2¯ÄO‚¶ïHÕê±²o¬]¢O{µ¯¦ê¯&icåÁJ®K²&-õªK*õ“iÏYimz
I?0T›«Ô?X™™ÿQH Þ à]VI»wóÆ¹Ÿ±_\Š'ó9=Vâ'ÁbD•‹“Ì${µ¯§êóIZ*K½''uñ‰}¬šVµƒCõ¹¡å;½ò•ßܱëöå= pºÐ ~wlÝ:³rÿžu5Ç×áÕæÒj>i³IÍ¥ŽÆO—«ÓOAmß‘W}²LÖåhDïùZ’Ï'™TòÐX¹6ÉÚ£½'û‡êkZµÉX9ÐÓÆÞ/é-ß_Hß1þëÛÿñÞå< pêÐ €3Ö7®¿`eÍ\šô¥¥ø‰×¦×ñ÷‰[‰ŸdmßBõUCo/¿&¢W¾žÊ¯çu=Éãcúº¤½